changeset 761:cb9f4fe71f2d

Merge 1.5 mysql group authz changes
author nwp@mrrush.otago.ac.nz
date Tue, 16 May 2006 09:43:18 +1200
parents 7bf4afe634ed (diff) 85db0f6b5bd6 (current diff)
children 1ff03ffb345e
files MoinMoin/auth.py
diffstat 118 files changed, 4758 insertions(+), 5060 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Mon May 15 10:58:04 2006 +1200
+++ b/.hgtags	Tue May 16 09:43:18 2006 +1200
@@ -12,3 +12,4 @@
 400516d5adc36ba0f22787f9df37ca4908ef483c 1.5.3-rc1
 9aebec40e7f9095832874120c596019c939510d7 1.5.3-rc2
 e5bd284ca29e5ce69a4593c45ba4cf4d1b74183d 1.5.3
+cdfb01bec1224c9ac9c83ef9bae75ed48ca66340 1.6a
--- a/MoinMoin/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-*.pyc
-*.pyo
-dict
-{arch}
-.arch-ids
--- a/MoinMoin/Page.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/Page.py	Tue May 16 09:43:18 2006 +1200
@@ -10,7 +10,7 @@
 
 from MoinMoin import config, caching, user, util, wikiutil
 from MoinMoin.logfile import eventlog
-from MoinMoin.util import filesys, MoinMoinNoFooter, timefuncs
+from MoinMoin.util import filesys, timefuncs
 
 class Page:
     """Page - Manage an (immutable) page associated with a WikiName.
@@ -20,7 +20,7 @@
     # Header regular expression, used to get header boundaries
     header_re = r'(^#+.*(?:\n\s*)+)+'
 
-    def __init__(self, request, page_name, **keywords):
+    def __init__(self, request, page_name, **kw):
         """
         Create page object.
 
@@ -30,29 +30,39 @@
 
         @param page_name: WikiName of the page
         @keyword rev: number of older revision
-        @keyword formatter: formatter instance
+        @keyword formatter: formatter instance or mimetype str,
+                            None or no kw arg will use default formatter
         @keyword include_self: if 1, include current user (default: 0)
         """
-        self.rev = keywords.get('rev', 0) # revision of this page
-        self.is_rootpage = keywords.get('is_rootpage', 0) # is this __init__ of rootpage?
-        self.include_self = keywords.get('include_self', 0)
         self.request = request
         self.cfg = request.cfg
-
         self.page_name = page_name
+        self.rev = kw.get('rev', 0) # revision of this page
+        self.is_rootpage = kw.get('is_rootpage', 0) # is this __init__ of rootpage?
+        self.include_self = kw.get('include_self', 0)
 
         # XXX uncomment to see how many pages we create....
         #import sys, traceback
         #print >>sys.stderr, "page %s" % repr(page_name)
         #traceback.print_stack(limit=4, file=sys.stderr)
 
+        formatter = kw.get('formatter', None)
+        if isinstance(formatter, (str, unicode)): # mimetype given
+            mimetype = str(formatter)
+            self.formatter = None
+            self.output_mimetype = mimetype
+            self.default_formatter = mimetype == "text/html"
+        elif formatter is not None: # formatter instance given
+            self.formatter = formatter
+            self.default_formatter = 0
+            self.output_mimetype = "text/todo" # TODO where do we get this value from?
+        else:
+            self.formatter = None
+            self.default_formatter = 1
+            self.output_mimetype = "text/html"
 
-        if keywords.has_key('formatter'):
-            self.formatter = keywords.get('formatter')
-            self.default_formatter = 0
-        else:
-            self.default_formatter = 1
-
+        self.output_charset = config.charset # correct for wiki pages
+        
         self._raw_body = None
         self._raw_body_modified = 0
         self.hilite_re = None
@@ -403,7 +413,7 @@
     def last_edit(self, request):
         """
         Return the last edit.
-        This is used by wikirpc(2).py.
+        This is used by MoinMoin/xmlrpc/__init__.py.
 
         @param request: the request object
         @rtype: dict
@@ -949,7 +959,6 @@
         text = self.get_raw_body()
         text = self.encodeTextMimeType(text)
         self.request.write(text)
-        raise MoinMoinNoFooter
 
 
     def send_page(self, request, msg=None, **keywords):
@@ -993,13 +1002,25 @@
         # load the text
         body = self.get_raw_body()
 
-        # if necessary, load the default formatter
+        # if necessary, load the formatter
         if self.default_formatter:
             from MoinMoin.formatter.text_html import Formatter
             self.formatter = Formatter(request, store_pagelinks=1)
+        elif not self.formatter:
+            formatterName = self.output_mimetype.replace('/', '_').replace('.', '_') # XXX use existing fn for that
+            try:
+                Formatter = wikiutil.importPlugin(request.cfg, "formatter", formatterName, "Formatter")
+                self.formatter = Formatter(request)
+            except wikiutil.PluginMissingError:
+                from MoinMoin.formatter.text_html import Formatter
+                self.formatter = Formatter(request, store_pagelinks=1)
+                self.output_mimetype = "text/html"
+        request.formatter = self.formatter
         self.formatter.setPage(self)
-        if self.hilite_re: self.formatter.set_highlight_re(self.hilite_re)
-        request.formatter = self.formatter
+        if self.hilite_re:
+            self.formatter.set_highlight_re(self.hilite_re)
+        
+        request.http_headers(["Content-Type: %s; charset=%s" % (self.output_mimetype, self.output_charset)])
 
         # default is wiki markup
         pi_format = self.cfg.default_markup or "wiki"
@@ -1174,14 +1195,13 @@
                         _('This page redirects to page "%(page)s"') % {'page': wikiutil.escape(pi_redirect)},
                         msg)
 
-
                 # Page trail
                 trail = None
                 if not print_mode:
                     request.user.addTrail(self.page_name)
                     trail = request.user.getTrail()
 
-                wikiutil.send_title(request, title,  page=self, link=link, msg=msg,
+                request.theme.send_title(title,  page=self, link=link, msg=msg,
                                     pagename=self.page_name, print_mode=print_mode,
                                     media=media, pi_refresh=pi_refresh,
                                     allow_doubleclick=1, trail=trail,
@@ -1248,7 +1268,7 @@
         if not content_only:
             # send the page footer
             if self.default_formatter:
-                wikiutil.send_footer(request, self.page_name, print_mode=print_mode)
+                request.theme.send_footer(self.page_name, print_mode=print_mode)
 
             request.write(doc_trailer)
 
@@ -1260,6 +1280,8 @@
                 cache.update('\n'.join(links) + '\n', True)
 
         request.clock.stop('send_page')
+        if not content_only and self.default_formatter:
+            request.theme.send_closing_html()
 
     def getFormatterName(self):
         """ Return a formatter name as used in the caching system
@@ -1267,7 +1289,7 @@
         @rtype: string
         @return: formatter name as used in caching
         """
-        if not hasattr(self, 'formatter'):
+        if not hasattr(self, 'formatter') or self.formatter is None:
             return ''
         module = self.formatter.__module__
         return module[module.rfind('.') + 1:]
@@ -1334,8 +1356,8 @@
     def execute(self, request, parser, code):
         """ Write page content by executing cache code """            
         formatter = self.formatter
-        from MoinMoin import wikimacro        
-        macro_obj = wikimacro.Macro(parser)        
+        from MoinMoin.macro import Macro
+        macro_obj = Macro(parser)        
         # Fix __file__ when running from a zip package
         import MoinMoin
         if hasattr(MoinMoin, '__loader__'):
--- a/MoinMoin/PageEditor.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/PageEditor.py	Tue May 16 09:43:18 2006 +1200
@@ -243,7 +243,7 @@
         status = ' '.join(status)
         status = Status(self.request, content=status)
         
-        wikiutil.send_title(self.request,
+        self.request.theme.send_title(
             title % {'pagename': self.split_title(self.request),},
             page=self,
             pagename=self.page_name, msg=status,
@@ -428,8 +428,9 @@
                            hilite_re=badwords_re)
 
         self.request.write(self.request.formatter.endContent())
-        wikiutil.send_footer(self.request, self.page_name)
-        
+        self.request.theme.send_footer(self.page_name)
+        self.request.theme.send_closing_html()
+
     def sendCancel(self, newtext, rev):
         """
         User clicked on Cancel button. If edit locking is active,
--- a/MoinMoin/PageGraphicalEditor.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/PageGraphicalEditor.py	Tue May 16 09:43:18 2006 +1200
@@ -150,7 +150,7 @@
         status = ' '.join(status)
         status = Status(self.request, content=status)
         
-        wikiutil.send_title(self.request,
+        self.request.theme.send_title(
             title % {'pagename': self.split_title(self.request),},
             page=self,
             pagename=self.page_name, msg=status,
@@ -352,5 +352,5 @@
                            hilite_re=badwords_re)
 
         self.request.write(self.request.formatter.endContent()) # end content div
-        wikiutil.send_footer(self.request, self.page_name)
-
+        self.request.theme.send_footer(self.page_name)
+        self.request.theme.send_closing_html()
--- a/MoinMoin/_tests/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
--- a/MoinMoin/_tests/__init__.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/_tests/__init__.py	Tue May 16 09:43:18 2006 +1200
@@ -177,9 +177,9 @@
         e.g MoinMoin._tests.test_error
     """
     if request is None:
-        from MoinMoin.request import RequestCLI
+        from MoinMoin.request import CLI
         from MoinMoin.user import User
-        request = RequestCLI()   
+        request = CLI.Request()   
         request.form = request.args = request.setup_args()
         request.user = User(request)
         
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/_tests/test_macro.py	Tue May 16 09:43:18 2006 +1200
@@ -0,0 +1,33 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - MoinMoin.macro Tests
+
+    @copyright: 2003-2004 by Jürgen Hermann <jh@web.de>,
+                2006 MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import unittest, os
+
+from MoinMoin import macro, wikiutil
+from MoinMoin.parser.plain import Parser
+from MoinMoin.formatter.text_html import Formatter
+
+
+class MacroTestCase(unittest.TestCase):
+    def testTrivialMacro(self):
+        """macro: trivial macro works"""
+        m = self._make_macro()
+        expected = m.formatter.linebreak(0)
+        result = m.execute("BR", "")
+        self.assertEqual(result, expected,
+            'Expected "%(expected)s" but got "%(result)s"' % locals())        
+
+    def _make_macro(self):
+        """Test helper"""
+        p = Parser("##\n", self.request)
+        p.formatter = Formatter(self.request)
+        self.request.formatter = p.formatter
+        p.form = self.request.form
+        m = macro.Macro(p)
+        return m
--- a/MoinMoin/_tests/test_wikimacro.py	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - MoinMoin.wikimacro Tests
-
-    @copyright: 2003-2004 by Jürgen Hermann <jh@web.de>
-    @license: GNU GPL, see COPYING for details.
-"""
-
-import unittest, os
-
-from MoinMoin import wikimacro, wikiutil
-from MoinMoin.parser.plain import Parser
-from MoinMoin.formatter.text_html import Formatter
-
-
-class MacroTestCase(unittest.TestCase):
-    def testTrivialMacro(self):
-        """wikimacro: trivial macro works"""
-        m = self._make_macro()
-        expected = m.formatter.linebreak(0)
-        result = m.execute("BR", "")
-        self.assertEqual(result, expected,
-            'Expected "%(expected)s" but got "%(result)s"' % locals())        
-
-    def _make_macro(self):
-        """Test helper"""
-        p = Parser("##\n", self.request)
-        p.formatter = Formatter(self.request)
-        self.request.formatter = p.formatter
-        p.form = self.request.form
-        m = wikimacro.Macro(p)
-        return m
--- a/MoinMoin/action/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
--- a/MoinMoin/action/AttachFile.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/AttachFile.py	Tue May 16 09:43:18 2006 +1200
@@ -29,7 +29,7 @@
 import os, mimetypes, time, zipfile
 from MoinMoin import config, user, util, wikiutil, packages
 from MoinMoin.Page import Page
-from MoinMoin.util import MoinMoinNoFooter, filesys
+from MoinMoin.util import filesys
 
 action_name = __name__.split('.')[-1]
 
@@ -348,7 +348,10 @@
     querystr = wikiutil.escape(wikiutil.makeQueryString(querystr))
     pagelink = '%s/%s?%s' % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename), querystr)
     helplink = Page(request, "HelpOnActions/AttachFile").url(request)
-    savelink = Page(request, pagename).url(request) # XXX include target filename param here for twisted
+    querystr = {'action': 'AttachFile', 'do': 'savedrawing'}
+    querystr = wikiutil.escape(wikiutil.makeQueryString(querystr))
+    savelink = '%s/%s?%s' % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename), querystr)
+    #savelink = Page(request, pagename).url(request) # XXX include target filename param here for twisted
                                            # request, {'savename': request.form['drawing'][0]+'.draw'}
     #savelink = '/cgi-bin/dumpform.bat'
 
@@ -445,15 +448,15 @@
     msg = None
     if action_name in request.cfg.actions_excluded:
         msg = _('File attachments are not allowed in this wiki!')
-    elif request.form.has_key('filepath'):
+    elif not request.form.has_key('do'):
+        upload_form(pagename, request)
+    elif request.form['do'][0] == 'savedrawing':
         if request.user.may.write(pagename):
             save_drawing(pagename, request)
             request.http_headers()
             request.write("OK")
         else:
             msg = _('You are not allowed to save a drawing on this page.')
-    elif not request.form.has_key('do'):
-        upload_form(pagename, request)
     elif request.form['do'][0] == 'upload':
         if request.user.may.write(pagename):
             if request.form.has_key('file'):
@@ -495,19 +498,18 @@
     if msg:
         error_msg(pagename, request, msg)
 
-
 def upload_form(pagename, request, msg=''):
     _ = request.getText
 
     request.http_headers()
     # Use user interface language for this generated page
     request.setContentLanguage(request.lang)
-    wikiutil.send_title(request, _('Attachments for "%(pagename)s"') % {'pagename': pagename}, pagename=pagename, msg=msg)
+    request.theme.send_title(_('Attachments for "%(pagename)s"') % {'pagename': pagename}, pagename=pagename, msg=msg)
     request.write('<div id="content">\n') # start content div
     send_uploadform(pagename, request)
     request.write('</div>\n') # end content div
-    wikiutil.send_footer(request, pagename)
-
+    request.theme.send_footer(pagename)
+    request.theme.send_closing_html()
 
 def do_upload(pagename, request):
     _ = request.getText
@@ -650,8 +652,6 @@
     # send data
     shutil.copyfileobj(open(fpath, 'rb'), request, 8192)
 
-    raise MoinMoinNoFooter
-
 def install_package(pagename, request):
     _ = request.getText
 
@@ -817,7 +817,7 @@
     request.setContentLanguage(request.lang)
     title = _('attachment:%(filename)s of %(pagename)s', formatted=True) % {
         'filename': filename, 'pagename': pagename}
-    wikiutil.send_title(request, title, pagename=pagename)
+    request.theme.send_title(title, pagename=pagename)
 
     # send body
     # TODO: use formatter startContent?
@@ -826,9 +826,8 @@
     send_uploadform(pagename, request)
     request.write('</div>\n') # end content div
 
-    # send footer
-    wikiutil.send_footer(request, pagename)
-
+    request.theme.send_footer(pagename)
+    request.theme.send_closing_html()
 
 #############################################################################
 ### File attachment administration
--- a/MoinMoin/action/Despam.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/Despam.py	Tue May 16 09:43:18 2006 +1200
@@ -15,7 +15,6 @@
 from MoinMoin.widget.browser import DataBrowserWidget
 from MoinMoin import wikiutil, Page, PageEditor
 from MoinMoin.macro import RecentChanges
-from MoinMoin.formatter.text_html import Formatter
 
 def show_editors(request, pagename, timestamp):
     _ =  request.getText
@@ -65,7 +64,7 @@
     #  mimic macro object for use of RecentChanges subfunctions
     macro = tmp()
     macro.request = request
-    macro.formatter = Formatter(request)
+    macro.formatter = request.html_formatter
 
     request.write("<table>")
     for line in log.reverse():
@@ -171,7 +170,7 @@
     ok = request.form.get('ok', [0])[0]
 
     request.http_headers()
-    wikiutil.send_title(request, "Despam", pagename=pagename)    
+    request.theme.send_title("Despam", pagename=pagename)    
     # Start content (important for RTL support)
     request.write(request.formatter.startContent("content"))
     
@@ -184,5 +183,6 @@
 
     # End content and send footer
     request.write(request.formatter.endContent())
-    wikiutil.send_footer(request, pagename)
+    request.theme.send_footer(pagename)
+    request.theme.send_closing_html()
 
--- a/MoinMoin/action/LikePages.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/LikePages.py	Tue May 16 09:43:18 2006 +1200
@@ -44,8 +44,7 @@
     # This action generate data using the user language
     request.setContentLanguage(request.lang)
 
-    wikiutil.send_title(request, _('Pages like "%s"') % (pagename),
-                        pagename=pagename)
+    request.theme.send_title(_('Pages like "%s"') % (pagename), pagename=pagename)
         
     # Start content - IMPORTANT - without content div, there is no
     # direction support!
@@ -55,8 +54,8 @@
 
     # End content and send footer
     request.write(request.formatter.endContent())
-    wikiutil.send_footer(request, pagename)
-        
+    request.theme.send_footer(pagename)
+    request.theme.send_closing_html()
 
 def findMatches(pagename, request, s_re=None, e_re=None,):
     """ Find like pages
--- a/MoinMoin/action/LocalSiteMap.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/LocalSiteMap.py	Tue May 16 09:43:18 2006 +1200
@@ -33,8 +33,7 @@
     # This action generate data using the user language
     request.setContentLanguage(request.lang)
 
-    wikiutil.send_title(request, _('Local Site Map for "%s"') % (pagename),  
-        pagename=pagename)
+    request.theme.send_title(_('Local Site Map for "%s"') % (pagename), pagename=pagename)
 
     # Start content - IMPORTANT - witout content div, there is no
     # direction support!
@@ -42,11 +41,9 @@
 
     request.write(LocalSiteMap(pagename).output(request))
 
-    # End content
     request.write(request.formatter.endContent()) # end content div
-    # Footer
-    wikiutil.send_footer(request, pagename)
-
+    request.theme.send_footer(pagename)
+    request.theme.send_closing_html()
 
 class LocalSiteMap:
     def __init__(self, name):
--- a/MoinMoin/action/MyPages.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/MyPages.py	Tue May 16 09:43:18 2006 +1200
@@ -19,8 +19,7 @@
         username = ''
 
     if not username:
-        return thispage.send_page(request,
-            msg = _('Please log in first.'))
+        return thispage.send_page(request, msg=_('Please log in first.'))
 
     userhomewiki = request.cfg.user_homewiki
     if userhomewiki != 'Self' and userhomewiki != request.cfg.interwikiname:
@@ -59,35 +58,21 @@
 
     from MoinMoin.Page import Page
     from MoinMoin.parser.wiki import Parser
-    from MoinMoin.formatter.text_html import Formatter
-    pagename = username
     request.http_headers()
     
     # This action generate data using the user language
     request.setContentLanguage(request.lang)
-
-    wikiutil.send_title(request, _('MyPages management'), pagename=pagename)
+    request.theme.send_title(_('MyPages management'), page=homepage)
         
-    # Start content - IMPORTANT - without content div, there is no
-    # direction support!
+    # Start content - IMPORTANT - without content div, there is no direction support!
     request.write(request.formatter.startContent("content"))
 
     parser = Parser(pagecontent, request)
-    formatter = Formatter(request)
-    reqformatter = None
-    if hasattr(request, 'formatter'):
-        reqformatter = request.formatter
-    request.formatter = formatter
     p = Page(request, "$$$")
-    formatter.setPage(p)
-    parser.format(formatter)
-    if reqformatter == None:
-        del request.formatter
-    else:
-        request.formatter = reqformatter
+    request.formatter.setPage(p)
+    parser.format(request.formatter)
+    
+    request.write(request.formatter.endContent())
+    request.theme.send_footer(homepage.page_name)
+    request.theme.send_closing_html()
 
-    # End content and send footer
-    request.write(request.formatter.endContent())
-    wikiutil.send_footer(request, pagename)
-
-
--- a/MoinMoin/action/PackagePages.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/PackagePages.py	Tue May 16 09:43:18 2006 +1200
@@ -17,7 +17,6 @@
 from MoinMoin import wikiutil, config, user
 from MoinMoin.PageEditor import PageEditor
 from MoinMoin.Page import Page
-from MoinMoin.util import MoinMoinNoFooter
 from MoinMoin.action.AttachFile import _addLogEntry
 from MoinMoin.packages import MOIN_PACKAGE_FILE, packLine, unpackLine
 
--- a/MoinMoin/action/RenderAsDocbook.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/RenderAsDocbook.py	Tue May 16 09:43:18 2006 +1200
@@ -6,10 +6,9 @@
 """
 
 from MoinMoin.Page import Page
-from MoinMoin.util import MoinMoinNoFooter
 
 def execute(pagename, request):
     url = Page(request, pagename).url(request, {'action': 'format',
                                                 'mimetype': 'xml/docbook'}, 0)
     request.http_redirect(url)
-    raise MoinMoinNoFooter
+
--- a/MoinMoin/action/SubscribeUser.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/SubscribeUser.py	Tue May 16 09:43:18 2006 +1200
@@ -18,7 +18,7 @@
 def show_form(pagename, request):
     _ = request.getText
     request.http_headers()
-    wikiutil.send_title(request, _("Subscribe users to the page %s") % pagename, pagename=pagename)
+    request.theme.send_title(_("Subscribe users to the page %s") % pagename, pagename=pagename)
 
     request.write("""
 <form action="" method="POST" enctype="multipart/form-data">
@@ -27,13 +27,14 @@
 <input type="submit" value="Subscribe">
 </form>
 """)
-    wikiutil.send_footer(request, pagename)
+    request.theme.send_footer(pagename)
+    request.theme.send_closing_html()
 
 def show_result(pagename, request):
     _ = request.getText
     request.http_headers()
 
-    wikiutil.send_title(request, _("Subscribed for %s:") % pagename, pagename=pagename)
+    request.theme.send_title(_("Subscribed for %s:") % pagename, pagename=pagename)
 
     from MoinMoin.formatter.text_html import Formatter
     formatter = Formatter(request)
@@ -41,8 +42,8 @@
     result = subscribe_users(request, request.form['users'][0].split(","), pagename, formatter)
     request.write(result)
 
-    wikiutil.send_footer(request, pagename)
-
+    request.theme.send_footer(pagename)
+    request.theme.send_closing_html()
 
 def subscribe_users(request, usernamelist, pagename, formatter):
     _ = request.getText
@@ -86,7 +87,8 @@
     _ = request.getText
     if not request.user.may.admin(pagename):
         request.http_headers()
-        wikiutil.send_title(request, _("You are not allowed to perform this action."), pagename=pagename)
+        request.theme.send_title(_("You are not allowed to perform this action."), pagename=pagename)
+        request.theme.send_closing_html()
     elif not request.form.has_key('users'):
         show_form(pagename, request)
     else:
@@ -117,8 +119,8 @@
         request_url = "localhost/"
 
     # Setup MoinMoin environment
-    from MoinMoin.request import RequestCLI
-    request = RequestCLI(url=request_url)
+    from MoinMoin.request import CLI
+    request = CLI.Request(url=request_url)
     request.form = request.args = request.setup_args()
 
     from MoinMoin.formatter.text_plain import Formatter
--- a/MoinMoin/action/__init__.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/__init__.py	Tue May 16 09:43:18 2006 +1200
@@ -1,13 +1,27 @@
 # -*- coding: iso-8859-1 -*-
 """
-    MoinMoin - Extension Action Package
+    MoinMoin - Action Implementation
 
+    Actions are triggered by the user clicking on special links on the page
+    (e.g. the "edit" link). The name of the action is passed in the "action"
+    CGI parameter.
+
+    The sub-package "MoinMoin.action" contains external actions, you can
+    place your own extensions there (similar to extension macros). User
+    actions that start with a capital letter will be displayed in a list
+    at the bottom of each page.
+
+    User actions starting with a lowercase letter can be used to work
+    together with a user macro; those actions a likely to work only if
+    invoked BY that macro, and are thus hidden from the user interface.
+    
     Additionally to the usual stuff, we provide an ActionBase class here with
     some of the usual base functionality for an action, like checking
     actions_excluded, making and checking tickets, rendering some form,
     displaying errors and doing stuff after an action.
     
-    @copyright: 2006 MoinMoin:ThomasWaldmann
+    @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>,
+                2006 MoinMoin:ThomasWaldmann
     @license: GNU GPL, see COPYING for details.
 """
 
@@ -187,3 +201,791 @@
         else:
             self.render_msg(self.make_form()) # display the form again
 
+# from wikiaction.py ---------------------------------------------------------
+
+import os, re, time
+from MoinMoin import config, util
+from MoinMoin.logfile import editlog
+
+#############################################################################
+### Misc Actions
+#############################################################################
+
+def do_raw(pagename, request):
+    """ send raw content of a page (e.g. wiki markup) """
+    if not request.user.may.read(pagename):
+        Page(request, pagename).send_page(request)
+    else:
+        try:
+            rev = int(request.form.get('rev', [0])[0])
+        except StandardError:
+            rev = 0
+        Page(request, pagename, rev=rev).send_raw()
+
+def do_show(pagename, request, count_hit=1, cacheable=1):
+    """ show a page, either current revision or the revision given by rev form value.
+        if count_hit is non-zero, we count the request for statistics.
+    """
+    # We must check if the current page has different ACLs.
+    if not request.user.may.read(pagename):
+        Page(request, pagename).send_page(request)
+    else:
+        mimetype = request.form.get('mimetype', [u"text/html"])[0]
+        try:
+            rev = int(request.form.get('rev', [0])[0])
+        except StandardError:
+            rev = 0
+        if rev == 0:
+            request.cacheable = cacheable
+        Page(request, pagename, rev=rev, formatter=mimetype).send_page(request, count_hit=count_hit)
+
+def do_format(pagename, request):
+    """ send a page using a specific formatter given by mimetype form key.
+        Since 5.5.2006 this functionality is also done by do_show, but do_format
+        has a default of text/plain when no format is given.
+        It also does not count in statistics and also does not set the cacheable flag.
+        TODO: remove this action when we don't need it any more for compatibility.
+    """
+    # get the MIME type
+    if not request.form.has_key('mimetype'):
+        request.form['mimetype'] = [u"text/plain"]
+    do_show(pagename, request, count_hit=0, cacheable=0)
+
+def do_content(pagename, request):
+    """ same as do_show, but we only show the content """
+    request.http_headers()
+    page = Page(request, pagename)
+    request.write('<!-- Transclusion of %s -->' % request.getQualifiedURL(page.url(request)))
+    page.send_page(request, count_hit=0, content_only=1)
+
+def do_print(pagename, request):
+    """ same as do_show, but send_page will notice the print mode """
+    do_show(pagename, request)
+
+def do_recall(pagename, request):
+    """ same as do_show, but never caches and never counts hits """
+    do_show(pagename, request, count_hit=0, cacheable=0)
+
+def do_refresh(pagename, request):
+    """ Handle refresh action """
+    # Without arguments, refresh action will refresh the page text_html cache.
+    arena = request.form.get('arena', ['Page.py'])[0]
+    if arena == 'Page.py':
+        arena = Page(request, pagename)
+    key = request.form.get('key', ['text_html'])[0]
+
+    # Remove cache entry (if exists), and send the page
+    from MoinMoin import caching
+    caching.CacheEntry(request, arena, key).remove()
+    caching.CacheEntry(request, arena, "pagelinks").remove()
+    do_show(pagename, request)
+
+def do_revert(pagename, request):
+    """ restore another revision of a page as a new current revision """
+    from MoinMoin.PageEditor import PageEditor
+    _ = request.getText
+
+    if not request.user.may.revert(pagename):
+        return Page(request, pagename).send_page(request,
+            msg = _('You are not allowed to revert this page!'))
+
+    rev = int(request.form['rev'][0])
+    revstr = '%08d' % rev
+    oldpg = Page(request, pagename, rev=rev)
+    pg = PageEditor(request, pagename)
+
+    try:
+        savemsg = pg.saveText(oldpg.get_raw_body(), 0, extra=revstr,
+                              action="SAVE/REVERT")
+    except pg.SaveError, msg:
+        # msg contain a unicode string
+        savemsg = unicode(msg)
+    request.reset()
+    pg.send_page(request, msg=savemsg)
+    return None
+
+def do_edit(pagename, request):
+    """ edit a page """
+    _ = request.getText
+
+    if not request.user.may.write(pagename):
+        Page(request, pagename).send_page(request,
+            msg = _('You are not allowed to edit this page.'))
+        return
+
+    valideditors = ['text', 'gui',]
+    editor = ''
+    if request.user.valid:
+        editor = request.user.editor_default
+    if editor not in valideditors:
+        editor = request.cfg.editor_default
+    
+    editorparam = request.form.get('editor', [editor])[0]
+    if editorparam == "guipossible":
+        lasteditor = editor
+    elif editorparam == "textonly":
+        editor = lasteditor = 'text'
+    else:
+        editor = lasteditor = editorparam
+
+    if request.cfg.editor_force:
+        editor = request.cfg.editor_default
+
+    # if it is still nothing valid, we just use the text editor
+    if editor not in valideditors:
+        editor = 'text'
+            
+    savetext = request.form.get('savetext', [None])[0]
+    rev = int(request.form.get('rev', ['0'])[0])
+    comment = request.form.get('comment', [u''])[0]
+    category = request.form.get('category', [None])[0]
+    rstrip = int(request.form.get('rstrip', ['0'])[0])
+    trivial = int(request.form.get('trivial', ['0'])[0])
+
+    if request.form.has_key('button_switch'):
+        if editor == 'text':
+            editor = 'gui'
+        else: # 'gui'
+            editor = 'text'
+
+    # load right editor class
+    if editor == 'gui':
+        from MoinMoin.PageGraphicalEditor import PageGraphicalEditor
+        pg = PageGraphicalEditor(request, pagename)
+    else: # 'text'
+        from MoinMoin.PageEditor import PageEditor
+        pg = PageEditor(request, pagename)
+
+    # is invoked without savetext start editing
+    if savetext is None:
+        pg.sendEditor()
+        return
+  
+    # did user hit cancel button?
+    cancelled = request.form.has_key('button_cancel')
+
+    # convert input from Graphical editor
+    from MoinMoin.converter.text_html_text_x_moin import convert, ConvertError
+    try:
+        if lasteditor == 'gui':
+            savetext = convert(request, pagename, savetext)
+                
+        # IMPORTANT: normalize text from the form. This should be done in
+        # one place before we manipulate the text.
+        savetext = pg.normalizeText(savetext, stripspaces=rstrip)
+    except ConvertError:
+        # we don't want to throw an exception if user cancelled anyway
+        if not cancelled:
+            raise
+
+    if cancelled:
+        pg.sendCancel(savetext or "", rev)
+        return
+
+    comment = wikiutil.clean_comment(comment)
+
+    # Add category
+
+    # TODO: this code does not work with extended links, and is doing
+    # things behind your back, and in general not needed. Either we have
+    # a full interface for categories (add, delete) or just add them by
+    # markup.
+    
+    if category and category != _('<No addition>', formatted=False): # opera 8.5 needs this
+        # strip trailing whitespace
+        savetext = savetext.rstrip()
+
+        # Add category separator if last non-empty line contains
+        # non-categories.
+        lines = filter(None, savetext.splitlines())
+        if lines:
+            
+            #TODO: this code is broken, will not work for extended links
+            #categories, e.g ["category hebrew"]
+            categories = lines[-1].split()
+            
+            if categories:
+                confirmed = wikiutil.filterCategoryPages(request, categories)
+                if len(confirmed) < len(categories):
+                    # This was not a categories line, add separator
+                    savetext += u'\n----\n'
+
+        # Add new category
+        if savetext and savetext[-1] != u'\n':
+            savetext += ' '
+        savetext += category + u'\n' # Should end with newline!
+
+    # Preview, spellcheck or spellcheck add new words
+    if (request.form.has_key('button_preview') or
+        request.form.has_key('button_spellcheck') or
+        request.form.has_key('button_newwords')):
+        pg.sendEditor(preview=savetext, comment=comment)
+    
+    # Preview with mode switch
+    elif request.form.has_key('button_switch'):
+        pg.sendEditor(preview=savetext, comment=comment, staytop=1)
+    
+    # Save new text
+    else:
+        try:
+            savemsg = pg.saveText(savetext, rev, trivial=trivial, comment=comment)
+        except pg.EditConflict, msg:
+            # Handle conflict and send editor
+
+            # TODO: conflict messages are duplicated from PageEditor,
+            # refactor to one place only.
+            conflict_msg = _('Someone else changed this page while you were editing!')
+            pg.set_raw_body(savetext, modified=1)
+            if pg.mergeEditConflict(rev):
+                conflict_msg = _("""Someone else saved this page while you were editing!
+Please review the page and save then. Do not save this page as it is!
+Have a look at the diff of %(difflink)s to see what has been changed.""") % {
+                    'difflink': pg.link_to(pg.request,
+                                           querystr='action=diff&rev=%d' % rev)
+                    }
+                # We don't send preview when we do merge conflict
+                pg.sendEditor(msg=conflict_msg, comment=comment)
+                return
+            else:
+                savemsg = conflict_msg
+        
+        except pg.SaveError, msg:
+            # msg contain a unicode string
+            savemsg = unicode(msg)
+
+        # Send new page after save or after unsuccessful conflict merge.
+        request.reset()
+        backto = request.form.get('backto', [None])[0]
+        if backto:
+            pg = Page(request, backto)
+
+        pg.send_page(request, msg=savemsg)
+
+def do_goto(pagename, request):
+    """ redirect to another page """
+    target = request.form.get('target', [''])[0]
+    request.http_redirect(Page(request, target).url(request))
+
+def do_diff(pagename, request):
+    """ Handle "action=diff"
+        checking for either a "rev=formerrevision" parameter
+        or rev1 and rev2 parameters
+    """
+    if not request.user.may.read(pagename):
+        Page(request, pagename).send_page(request)
+        return
+
+    try:
+        date = request.form['date'][0]
+        try:
+            date = long(date) # must be long for py 2.2.x
+        except StandardError:
+            date = 0
+    except KeyError:
+        date = 0
+
+    try:
+        rev1 = int(request.form.get('rev1', [-1])[0])
+    except StandardError:
+        rev1 = 0
+    try:
+        rev2 = int(request.form.get('rev2', [0])[0])
+    except StandardError:
+        rev1 = 0
+
+    if rev1 == -1 and rev2 == 0:
+        try:
+            rev1 = int(request.form.get('rev', [-1])[0])
+        except StandardError:
+            rev1 = -1
+ 
+    # spacing flag?
+    ignorews = int(request.form.get('ignorews', [0])[0])
+
+    _ = request.getText
+    
+    # get a list of old revisions, and back out if none are available
+    currentpage = Page(request, pagename)
+    revisions = currentpage.getRevList()
+    if len(revisions) < 2:
+        currentpage.send_page(request, msg=_("No older revisions available!"))
+        return
+
+    if date: # this is how we get called from RecentChanges
+        rev1 = 0
+        log = editlog.EditLog(request, rootpagename=pagename)
+        for line in log.reverse():
+            if date >= line.ed_time_usecs and int(line.rev) != 99999999:
+                rev1 = int(line.rev)
+                break
+        else:
+            rev1 = 1
+        rev2 = 0
+
+    # Start output
+    # This action generate content in the user language
+    request.setContentLanguage(request.lang)
+
+    request.http_headers()
+    request.theme.send_title(_('Diff for "%s"') % (pagename,), pagename=pagename, allow_doubleclick=1)
+  
+    if rev1 > 0 and rev2 > 0 and rev1 > rev2 or rev1 == 0 and rev2 > 0:
+        rev1, rev2 = rev2, rev1
+          
+    oldrev1, oldcount1 = None, 0
+    oldrev2, oldcount2 = None, 0
+    
+    # get the filename of the version to compare to
+    edit_count = 0
+    for rev in revisions:
+        edit_count += 1
+        if rev <= rev1: 
+            oldrev1, oldcount1 = rev, edit_count
+        if rev2 and rev >= rev2: 
+            oldrev2, oldcount2 = rev, edit_count
+        if oldrev1 and oldrev2 or oldrev1 and not rev2:
+            break
+    
+    if rev1 == -1:
+        oldpage = Page(request, pagename, rev=revisions[1])
+        oldcount1 -= 1
+    elif rev1 == 0:
+        oldpage = currentpage
+        # oldcount1 is still on init value 0
+    else:
+        if oldrev1:
+            oldpage = Page(request, pagename, rev=oldrev1)
+        else:
+            oldpage = Page(request, "$EmptyPage$") # hack
+            oldpage.set_raw_body("")    # avoid loading from disk
+            oldrev1 = 0 # XXX
+              
+    if rev2 == 0:
+        newpage = currentpage
+        # oldcount2 is still on init value 0
+    else:
+        if oldrev2:
+            newpage = Page(request, pagename, rev=oldrev2)
+        else:
+            newpage = Page(request, "$EmptyPage$") # hack
+            newpage.set_raw_body("")    # avoid loading from disk
+            oldrev2 = 0 # XXX
+    
+    edit_count = abs(oldcount1 - oldcount2)
+
+    # this should use the formatter, but there is none?
+    request.write('<div id="content">\n') # start content div
+    request.write('<p class="diff-header">')
+    request.write(_('Differences between revisions %d and %d') % (oldpage.get_real_rev(), newpage.get_real_rev()))
+    if edit_count > 1:
+        request.write(' ' + _('(spanning %d versions)') % (edit_count,))
+    request.write('</p>')
+  
+    if request.user.show_fancy_diff:
+        from MoinMoin.util.diff import diff
+        request.write(diff(request, oldpage.get_raw_body(), newpage.get_raw_body()))
+        newpage.send_page(request, count_hit=0, content_only=1, content_id="content-below-diff")
+    else:
+        lines = wikiutil.linediff(oldpage.getlines(), newpage.getlines())
+        if not lines:
+            msg = _("No differences found!")
+            if edit_count > 1:
+                msg = msg + '<p>' + _('The page was saved %(count)d times, though!') % {
+                    'count': edit_count}
+            request.write(msg)
+        else:
+            if ignorews:
+                request.write(_('(ignoring whitespace)') + '<br>')
+            else:
+                qstr = 'action=diff&ignorews=1'
+                if rev1: qstr = '%s&rev1=%s' % (qstr, rev1)
+                if rev2: qstr = '%s&rev2=%s' % (qstr, rev2)
+                request.write(Page(request, pagename).link_to(request,
+                    text=_('Ignore changes in the amount of whitespace'),
+                    querystr=qstr) + '<p>')
+
+            request.write('<pre>')
+            for line in lines:
+                if line[0] == "@":
+                    request.write('<hr>')
+                request.write(wikiutil.escape(line)+'\n')
+            request.write('</pre>')
+
+    request.write('</div>\n') # end content div
+    request.theme.send_footer(pagename)
+    request.theme.send_closing_html()
+
+def do_info(pagename, request):
+    """ show misc. infos about a page """
+    if not request.user.may.read(pagename):
+        Page(request, pagename).send_page(request)
+        return
+
+    def general(page, pagename, request):
+        _ = request.getText
+
+        request.write('<h2>%s</h2>\n' % _('General Information'))
+        
+        # show page size
+        request.write(("<p>%s</p>" % _("Page size: %d")) % page.size())
+
+        # show SHA digest fingerprint
+        import sha
+        digest = sha.new(page.get_raw_body().encode(config.charset)).hexdigest().upper()
+        request.write('<p>%(label)s <tt>%(value)s</tt></p>' % {
+            'label': _("SHA digest of this page's content is:"),
+            'value': digest,
+            })
+
+        # show attachments (if allowed)
+        attachment_info = getHandler(request, 'AttachFile', 'info')
+        if attachment_info:
+            request.write(attachment_info(pagename, request))
+
+        # show subscribers
+        subscribers = page.getSubscribers(request,  include_self=1, return_users=1)
+        if subscribers:
+            request.write('<p>', _('The following users subscribed to this page:'))
+            for lang in subscribers.keys():
+                request.write('<br>[%s] ' % lang)
+                for user in subscribers[lang]:
+                    # do NOT disclose email addr, only WikiName
+                    userhomepage = Page(request, user.name)
+                    if userhomepage.exists():
+                        request.write(userhomepage.link_to(request) + ' ')
+                    else:
+                        request.write(user.name + ' ')
+            request.write('</p>')
+
+        # show links
+        links = page.getPageLinks(request)
+        if links:
+            request.write('<p>', _('This page links to the following pages:'), '<br>')
+            for linkedpage in links:
+                request.write("%s%s " % (Page(request, linkedpage).link_to(request), ",."[linkedpage == links[-1]]))
+            request.write("</p>")
+
+    def history(page, pagename, request):
+        # show history as default
+        _ = request.getText
+
+        # open log for this page
+        from MoinMoin.util.dataset import TupleDataset, Column
+
+        history = TupleDataset()
+        history.columns = [
+            Column('rev', label='#', align='right'),
+            Column('mtime', label=_('Date'), align='right'),
+            Column('size',  label=_('Size'), align='right'),
+            Column('diff', label='<input type="submit" value="%s">' % (_("Diff"))),
+            Column('editor', label=_('Editor'), hidden=not request.cfg.show_names),
+            Column('comment', label=_('Comment')),
+            Column('action', label=_('Action')),
+            ]
+
+        # generate history list
+        revisions = page.getRevList()
+        versions = len(revisions)
+
+        may_revert = request.user.may.revert(pagename)
+        
+        # read in the complete log of this page
+        log = editlog.EditLog(request, rootpagename=pagename)
+        count = 0
+        for line in log.reverse():
+            rev = int(line.rev)
+            actions = ""
+            if line.action in ['SAVE','SAVENEW','SAVE/REVERT',]:
+                size = page.size(rev=rev)
+                if count == 0: # latest page
+                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
+                        text=_('view'),
+                        querystr=''))
+                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
+                        text=_('raw'),
+                        querystr='action=raw'))
+                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
+                        text=_('print'),
+                        querystr='action=print'))
+                else:
+                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
+                        text=_('view'),
+                        querystr='action=recall&rev=%d' % rev))
+                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
+                        text=_('raw'),
+                        querystr='action=raw&rev=%d' % rev))
+                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
+                        text=_('print'),
+                        querystr='action=print&rev=%d' % rev))
+                    if may_revert and size: # you can only revert to nonempty revisions
+                        actions = '%s&nbsp;%s' % (actions, page.link_to(request,
+                            text=_('revert'),
+                            querystr='action=revert&rev=%d' % (rev,)))
+                if count == 0:
+                    rchecked=' checked="checked"'
+                    lchecked = ''
+                elif count == 1:
+                    lchecked=' checked="checked"'
+                    rchecked = ''
+                else:
+                    lchecked = rchecked = ''
+                diff = '<input type="radio" name="rev1" value="%d"%s><input type="radio" name="rev2" value="%d"%s>' % (rev,lchecked,rev,rchecked)
+                comment = line.comment
+                if not comment and line.action.find('/REVERT') != -1:
+                        comment = _("Revert to revision %(rev)d.") % {'rev': int(line.extra)}
+            else: # ATT*
+                rev = '-'
+                diff = '-'
+                
+                filename = wikiutil.url_unquote(line.extra)
+                comment = "%s: %s %s" % (line.action, filename, line.comment)
+                size = 0
+                if line.action != 'ATTDEL':
+                    from MoinMoin.action import AttachFile
+                    page_dir = AttachFile.getAttachDir(request, pagename)
+                    filepath = os.path.join(page_dir, filename)
+                    try:
+                        # FIXME, wrong path on non-std names
+                        size = os.path.getsize(filepath)
+                    except:
+                        pass
+                    if line.action == 'ATTNEW':
+                        actions = '%s&nbsp;%s' % (actions, page.link_to(request,
+                            text=_('view'),
+                            querystr='action=AttachFile&do=view&target=%s' % filename))
+                    elif line.action == 'ATTDRW':
+                        actions = '%s&nbsp;%s' % (actions, page.link_to(request,
+                            text=_('edit'),
+                            querystr='action=AttachFile&drawing=%s' % filename.replace(".draw","")))
+
+                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
+                        text=_('get'),
+                        querystr='action=AttachFile&do=get&target=%s' % filename))
+                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
+                        text=_('del'),
+                        querystr='action=AttachFile&do=del&target=%s' % filename))
+                    # XXX use?: wikiutil.escape(filename)
+
+            history.addRow((
+                rev,
+                request.user.getFormattedDateTime(wikiutil.version2timestamp(line.ed_time_usecs)),
+                str(size),
+                diff,
+                line.getEditor(request) or _("N/A"),
+                wikiutil.escape(comment) or '&nbsp;',
+                actions,
+            ))
+            count += 1
+            if count >= 100:
+                break
+
+        # print version history
+        from MoinMoin.widget.browser import DataBrowserWidget
+
+        request.write('<h2>%s</h2>\n' % _('Revision History'))
+
+        if not count: # there was no entry in logfile
+            request.write(_('No log entries found.'))
+            return
+
+        # TODO: this form activates revert, which should use POST, but
+        # other actions should use get. Maybe we should put the revert
+        # into the page view itself, and not in this form.
+        request.write('<form method="GET" action="">\n')
+        request.write('<div id="page-history">\n')
+        request.write('<input type="hidden" name="action" value="diff">\n')
+
+        history_table = DataBrowserWidget(request)
+        history_table.setData(history)
+        history_table.render()
+        request.write('</div>\n')
+        request.write('</form>\n')
+
+    # main function
+    _ = request.getText
+    page = Page(request, pagename)
+    qpagename = wikiutil.quoteWikinameURL(pagename)
+    title = page.split_title(request)
+
+    request.http_headers()
+
+    # This action uses page or wiki language TODO: currently
+    # page.language is broken and not available now, when we fix it,
+    # this will be automatically fixed.
+    lang = page.language or request.cfg.language_default
+    request.setContentLanguage(lang)
+    
+    request.theme.send_title(_('Info for "%s"') % (title,), pagename=pagename)
+
+    historylink =  wikiutil.link_tag(request, '%s?action=info' % qpagename,
+        _('Show "%(title)s"') % {'title': _('Revision History')})
+    generallink =  wikiutil.link_tag(request, '%s?action=info&amp;general=1' % qpagename,
+        _('Show "%(title)s"') % {'title': _('General Page Infos')})
+    hitcountlink = wikiutil.link_tag(request, '%s?action=info&amp;hitcounts=1' % qpagename,
+        _('Show chart "%(title)s"') % {'title': _('Page hits and edits')})
+    
+    request.write('<div id="content">\n') # start content div
+    request.write("<p>[%s]  [%s]  [%s]</p>" % (historylink, generallink, hitcountlink))
+
+    show_hitcounts = int(request.form.get('hitcounts', [0])[0]) != 0
+    show_general = int(request.form.get('general', [0])[0]) != 0
+    
+    if show_hitcounts:
+        from MoinMoin.stats import hitcounts
+        request.write(hitcounts.linkto(pagename, request, 'page=' + wikiutil.url_quote_plus(pagename)))
+    elif show_general:
+        general(page, pagename, request)
+    else:
+        history(page, pagename, request)
+        
+    request.write('</div>\n') # end content div
+    request.theme.send_footer(pagename)
+    request.theme.send_closing_html()
+
+def do_quicklink(pagename, request):
+    """ Add the current wiki page to the user quicklinks 
+    
+    TODO: what if add or remove quicklink fail? display an error message?
+    """
+    _ = request.getText
+    msg = None
+
+    if not request.user.valid:
+        msg = _("You must login to add a quicklink.")    
+    elif request.user.isQuickLinkedTo([pagename]):
+        if request.user.removeQuicklink(pagename):
+            msg = _('Your quicklink to this page has been removed.')            
+    else:
+        if request.user.addQuicklink(pagename):
+            msg = _('A quicklink to this page has been added for you.')
+
+    Page(request, pagename).send_page(request, msg=msg)
+
+def do_subscribe(pagename, request):
+    """ Subscribe or unsubscribe the user to pagename
+    
+    TODO: what if subscribe failed? no message is displayed.
+    """
+    _ = request.getText
+    cfg = request.cfg
+    msg = None
+
+    if not request.user.may.read(pagename):
+        msg = _("You are not allowed to subscribe to a page you can't read.")
+
+    # Check if mail is enabled
+    elif not cfg.mail_enabled:
+        msg = _("This wiki is not enabled for mail processing.")
+
+    # Suggest visitors to login
+    elif not request.user.valid:
+        msg = _("You must log in to use subscribtions.")
+
+    # Suggest users without email to add their email address
+    elif not request.user.email:
+        msg = _("Add your email address in your UserPreferences to use subscriptions.")
+
+    elif request.user.isSubscribedTo([pagename]):
+        # Try to unsubscribe
+        if request.user.unsubscribe(pagename):
+            msg = _('Your subscribtion to this page has been removed.')
+        else:
+            msg = _("Can't remove regular expression subscription!") + u' ' + \
+                  _("Edit the subscription regular expressions in your "
+                    "UserPreferences.")
+
+    else:
+        # Try to subscribe
+        if request.user.subscribe(pagename):
+            msg = _('You have been subscribed to this page.')
+
+    Page(request, pagename).send_page(request, msg=msg)
+
+def do_userform(pagename, request):
+    """ save data posted from UserPreferences """
+    from MoinMoin import userform
+    savemsg = userform.savedata(request)
+    Page(request, pagename).send_page(request, msg=savemsg)
+
+def do_bookmark(pagename, request):
+    """ set bookmarks (in time) for RecentChanges or delete them """
+    timestamp = request.form.get('time', [None])[0]
+    if timestamp is not None:
+        if timestamp == 'del':
+            tm = None
+        else:
+            try:
+                tm = int(timestamp)
+            except StandardError:
+                tm = wikiutil.timestamp2version(time.time())
+    else:
+        tm = wikiutil.timestamp2version(time.time())
+  
+    if tm is None:
+        request.user.delBookmark()
+    else:
+        request.user.setBookmark(tm)
+    Page(request, pagename).send_page(request)
+  
+
+#############################################################################
+### Special Actions
+#############################################################################
+
+def do_chart(pagename, request):
+    """ Show page charts """
+    _ = request.getText
+    if not request.user.may.read(pagename):
+        msg = _("You are not allowed to view this page.")
+        return request.page.send_page(request, msg=msg)
+    
+    if not request.cfg.chart_options:
+        msg = _("Charts are not available!")
+        return request.page.send_page(request, msg=msg)
+    
+    chart_type = request.form.get('type', [''])[0].strip()
+    if not chart_type:
+        msg = _('You need to provide a chart type!')
+        return request.page.send_page(request, msg=msg)
+    
+    try:
+        func = pysupport.importName("MoinMoin.stats." + chart_type, 'draw')
+    except (ImportError, AttributeError):
+        msg = _('Bad chart type "%s"!') % chart_type
+        return request.page.send_page(request, msg=msg)
+    
+    func(pagename, request)
+
+def do_dumpform(pagename, request):
+    """ dump the form data we received in this request for debugging """
+    data = util.dumpFormData(request.form)
+
+    request.http_headers()
+    request.write("<html><body>%s</body></html>" % data)
+
+
+#############################################################################
+### Dispatching
+#############################################################################
+
+def getPlugins(request):
+    """ return the path to the action plugin directory and a list of plugins there """
+    dir = os.path.join(request.cfg.plugin_dir, 'action')
+    plugins = []
+    if os.path.isdir(dir):
+        plugins = pysupport.getPackageModules(os.path.join(dir, 'dummy'))
+    return dir, plugins
+
+def getHandler(request, action, identifier="execute"):
+    """ return a handler function for a given action or None """
+    # check for excluded actions
+    if action in request.cfg.actions_excluded:
+        return None
+
+    try:
+        handler = wikiutil.importPlugin(request.cfg, "action", action, identifier)
+    except wikiutil.PluginMissingError:
+        handler = globals().get('do_' + action)
+        
+    return handler
+
--- a/MoinMoin/action/backup.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/backup.py	Tue May 16 09:43:18 2006 +1200
@@ -13,7 +13,6 @@
 import os, re, time, tarfile
 import cStringIO
 from MoinMoin import wikiutil
-from MoinMoin.util import MoinMoinNoFooter
 
 def addFiles(path, tar, exclude):
     """ Add files in path to tar """
@@ -40,7 +39,6 @@
     for path in request.cfg.backup_include:
         addFiles(path, tar, exclude)
     tar.close()
-    raise MoinMoinNoFooter
 
 def restoreBackup(request, pagename):
     _ = request.getText
@@ -75,7 +73,7 @@
     request.http_headers()
     request.setContentLanguage(request.lang)
     title = _('Wiki Backup / Restore')
-    wikiutil.send_title(request, title, form=request.form, pagename=pagename)
+    request.theme.send_title(title, form=request.form, pagename=pagename)
     request.write(request.formatter.startContent("content"))
     
     request.write(_("""Some hints:
@@ -112,7 +110,8 @@
 })
     
     request.write(request.formatter.endContent())
-    wikiutil.send_footer(request, pagename)
+    request.theme.send_footer(pagename)
+    request.theme.send_closing_html()
 
 def sendMsg(request, pagename, msg):
     from MoinMoin import Page
--- a/MoinMoin/action/fckdialog.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/fckdialog.py	Tue May 16 09:43:18 2006 +1200
@@ -138,8 +138,8 @@
 ''')
         
 def macro_list(request):
-    from MoinMoin import wikimacro
-    macros = wikimacro.getNames(request.cfg)
+    from MoinMoin import macro
+    macros = macro.getNames(request.cfg)
     macros.sort()
     return macros
 
--- a/MoinMoin/action/fullsearch.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/fullsearch.py	Tue May 16 09:43:18 2006 +1200
@@ -10,7 +10,6 @@
 
 from MoinMoin.Page import Page
 from MoinMoin import wikiutil
-from MoinMoin.util import MoinMoinNoFooter
 
 
 def isTitleSearch(request):
@@ -73,7 +72,7 @@
             url = page.url(request, querystr={'highlight': query.highlight_re()},
                            escape=0)
             request.http_redirect(url)
-            raise MoinMoinNoFooter
+            return
 
     # send http headers
     request.http_headers()
@@ -89,8 +88,7 @@
         title = _('Full Text Search: "%s"')
         results.sortByWeight() 
 
-    wikiutil.send_title(request, title % needle, form=request.form,
-                        pagename=pagename)
+    request.theme.send_title(title % needle, form=request.form, pagename=pagename)
     
     # Start content (important for RTL support)
     request.write(request.formatter.startContent("content"))
@@ -107,7 +105,7 @@
         output = results.pageList(request, request.formatter, info=info)        
     request.write(output)
 
-    # End content and send footer
     request.write(request.formatter.endContent())
-    wikiutil.send_footer(request, pagename)
+    request.theme.send_footer(pagename)
+    request.theme.send_closing_html()
 
--- a/MoinMoin/action/links.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/links.py	Tue May 16 09:43:18 2006 +1200
@@ -7,10 +7,7 @@
     @copyright: 2001 by Jürgen Hermann <jh@web.de>
     @license: GNU GPL, see COPYING for details.
 """
-
 from MoinMoin import config, wikiutil
-from MoinMoin.util import MoinMoinNoFooter
-
 
 def execute(pagename, request):
     _ = request.getText
@@ -22,11 +19,10 @@
     else:
         mimetype = "text/html"
 
-    request.http_headers(["Content-Type: %s; charset=%s" % (mimetype,config.charset)])
+    request.http_headers(["Content-Type: %s; charset=%s" % (mimetype, config.charset)])
 
     if mimetype == "text/html":
-        wikiutil.send_title(request,
-                            _('Full Link List for "%s"') % request.cfg.sitename)
+        request.theme.send_title(_('Full Link List for "%s"') % request.cfg.sitename)
         request.write('<pre>')
 
     # Get page dict readable by current user
@@ -52,9 +48,8 @@
 
     if mimetype == "text/html":
         request.write('</pre>')
-        wikiutil.send_footer(request, pagename)
-    else:
-        raise MoinMoinNoFooter
+        request.theme.send_footer(pagename)
+        request.theme.send_closing_html()
 
 def _emit(request, pagename):
     """ Send pagename, encode it if it contains spaces
--- a/MoinMoin/action/login.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/login.py	Tue May 16 09:43:18 2006 +1200
@@ -61,13 +61,13 @@
         
         else: # show login form
             request.http_headers()
-            wikiutil.send_title(request, _("Login"), pagename=self.pagename)
+            request.theme.send_title(_("Login"), pagename=self.pagename)
             # Start content (important for RTL support)
             request.write(request.formatter.startContent("content"))
             
             request.write(userform.getLogin(request))
             
-            # End content and send footer
             request.write(request.formatter.endContent())
-            wikiutil.send_footer(request, self.pagename)
+            request.theme.send_footer(self.pagename)
+            request.theme.send_closing_html()
 
--- a/MoinMoin/action/newpage.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/newpage.py	Tue May 16 09:43:18 2006 +1200
@@ -9,9 +9,8 @@
     @license: GNU GPL, see COPYING for details.
 """
 
-from MoinMoin.util import MoinMoinNoFooter
+import time
 from MoinMoin.Page import Page
-import time
 
 class NewPage:
     """ Open editor for a new page, using template """
@@ -93,7 +92,6 @@
 
             url = Page(self.request, pagename).url(self.request, query, 0)
             self.request.http_redirect(url)
-            raise MoinMoinNoFooter
 
         return ''
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/action/test.py	Tue May 16 09:43:18 2006 +1200
@@ -0,0 +1,120 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - test action
+
+    This action allows you to run some tests and show some data about your system.
+
+    If you don't want this action to be available due to system privacy reasons,
+    do this in your wiki/farm config:
+
+    actions_excluded = ["test"]
+    
+    @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>,
+                2006 MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+import os, sys
+
+from MoinMoin import config, version
+from MoinMoin.action import ActionBase
+from MoinMoin.logfile import editlog, eventlog
+
+
+def runTest(request):
+    request.write('Release %s\n' % version.release)
+    request.write('Revision %s\n' % version.revision)
+    request.write('Python version %s\n' % sys.version)
+    request.write('Python installed to %s\n' % sys.exec_prefix)
+
+    # Try xml
+    try:
+        import xml
+        request.write('PyXML is %sinstalled\n' % ('NOT ', '')[xml.__file__.find('_xmlplus') != -1])
+    except ImportError:
+        request.write('PyXML is missing\n')
+
+    request.write('Python Path:\n')
+    for dir in sys.path:
+        request.write('   %s\n' % dir)
+
+    # check if the request is a local one
+    import socket
+    local_request = (socket.getfqdn(request.server_name) == socket.getfqdn(request.remote_addr))
+
+    # check directories
+    request.write("Checking directories...\n")
+    dirs = [('data', request.cfg.data_dir),
+            ('user', request.cfg.user_dir),
+           ]
+    for name, path in dirs:
+        if not os.path.isdir(path):
+            request.write("*** %s directory NOT FOUND (set to '%s')\n" % (name, path))
+        elif not os.access(path, os.R_OK | os.W_OK | os.X_OK):
+            request.write("*** %s directory NOT ACCESSIBLE (set to '%s')\n" % (name, path))
+        else:
+            path = os.path.abspath(path)
+            request.write("    %s directory tests OK (set to '%s')\n" % (name, path))
+
+    # check eventlog access
+    log = eventlog.EventLog(request)
+    msg = log.sanityCheck()
+    if msg: request.write("*** %s\n" % msg)
+
+    # check editlog access
+    log = editlog.EditLog(request)
+    msg = log.sanityCheck()
+    if msg: request.write("*** %s\n" % msg)
+
+    # keep some values to ourselves
+    request.write("\nServer Environment:\n")
+    if local_request:
+        # print the environment, in case people use exotic servers with broken
+        # CGI APIs (say, M$ IIS), to help debugging those
+        keys = os.environ.keys()
+        keys.sort()
+        for key in keys:
+            request.write("    %s = %s" % (key, repr(os.environ[key])))
+    else:
+        request.write("    ONLY AVAILABLE FOR LOCAL REQUESTS ON THIS HOST!")
+
+    # run unit tests
+    request.write("\n\nUnit Tests:\n")
+
+    # The unit tests are diabled on servers using threads, beause they
+    # change request.cfg, which is now shared between threads.
+    # TODO: check if we can enable them back in a safe way
+    if config.use_threads:
+        request.write("    *** The unit tests are disabled when using multi "
+                      "threading ***")
+    else:
+        # TODO: do we need to hide the error when _tests can't be
+        # imported? It might make it hard to debug the tests package
+        # itself.
+        try:    
+            from MoinMoin import _tests
+        except ImportError:
+            request.write("    *** The unit tests are not available ***")
+        else:
+            _tests.run(request)
+
+class test(ActionBase):
+    """ test and show info action
+
+    Note: the action name is the class name
+    """
+    def do_action(self):
+        """ run tests """
+        request = self.request
+        request.http_headers(["Content-type: text/plain; charset=%s" % config.charset])
+        request.write('MoinMoin Diagnosis\n======================\n\n')
+        runTest(request)
+        return True, ""
+
+    def do_action_finish(self, success):
+        """ we don't want to do the default stuff, but just NOTHING """
+        pass
+
+def execute(pagename, request):
+    """ Glue code for actions """
+    test(pagename, request).render()
+
--- a/MoinMoin/action/titleindex.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/action/titleindex.py	Tue May 16 09:43:18 2006 +1200
@@ -38,5 +38,3 @@
         for name in pages:
             request.write(name+'\r\n')
 
-    raise util.MoinMoinNoFooter
-
--- a/MoinMoin/auth.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/auth.py	Tue May 16 09:43:18 2006 +1200
@@ -166,11 +166,11 @@
 
 def http(request, **kw):
     """ authenticate via http basic/digest/ntlm auth """
-    from MoinMoin.request import RequestTwisted, RequestCLI
+    from MoinMoin.request import TWISTED, CLI
     user_obj = kw.get('user_obj')
     u = None
     # check if we are running Twisted
-    if isinstance(request, RequestTwisted):
+    if isinstance(request, TWISTED.Request):
         username = request.twistd.getUser()
         password = request.twistd.getPassword()
         # when using Twisted http auth, we use username and password from
@@ -178,7 +178,7 @@
         u = user.User(request, auth_username=username, password=password,
                       auth_method='http', auth_attribs=())
 
-    elif not isinstance(request, RequestCLI):
+    elif not isinstance(request, CLI.Request):
         env = request.env
         auth_type = env.get('AUTH_TYPE','')
         if auth_type in ['Basic', 'Digest', 'NTLM', 'Negotiate',]:
@@ -206,12 +206,12 @@
 
 def sslclientcert(request, **kw):
     """ authenticate via SSL client certificate """
-    from MoinMoin.request import RequestTwisted
+    from MoinMoin.request import TWISTED
     user_obj = kw.get('user_obj')
     u = None
     changed = False
     # check if we are running Twisted
-    if isinstance(request, RequestTwisted):
+    if isinstance(request, TWISTED.Request):
         return user_obj, True # not supported if we run twisted
         # Addendum: this seems to need quite some twisted insight and coding.
         # A pointer i got on #twisted: divmod's vertex.sslverify
--- a/MoinMoin/formatter/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
--- a/MoinMoin/formatter/base.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/formatter/base.py	Tue May 16 09:43:18 2006 +1200
@@ -299,23 +299,17 @@
                 return args
         return None
 
-    def processor(self, processor_name, lines, is_parser=0):
-        """ processor_name MUST be valid!
+    def parser(self, parser_name, lines):
+        """ parser_name MUST be valid!
             writes out the result instead of returning it!
         """
-        if not is_parser:
-            processor = wikiutil.importPlugin(self.request.cfg, "processor",
-                                              processor_name, "process")
-            processor(self.request, self, lines)
-        else:
-            parser = wikiutil.importPlugin(self.request.cfg, "parser",
-                                           processor_name, "Parser")
-            args = self._get_bang_args(lines[0])
-            if args is not None:
-                lines = lines[1:]
-            p = parser('\n'.join(lines), self.request, format_args=args)
-            p.format(self)
-            del p
+        parser = wikiutil.importPlugin(self.request.cfg, "parser", parser_name, "Parser")
+        args = self._get_bang_args(lines[0])
+        if args is not None:
+            lines = lines[1:]
+        p = parser('\n'.join(lines), self.request, format_args=args)
+        p.format(self)
+        del p
         return ''
 
     def dynamic_content(self, parser, callback, arg_list=[], arg_dict={},
--- a/MoinMoin/formatter/dom_xml.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/formatter/dom_xml.py	Tue May 16 09:43:18 2006 +1200
@@ -206,17 +206,16 @@
         # call the macro
         return self._add_tag('macro', name=name, args=(args or ''))
 
-    def processor(self, processor_name, lines, is_parser=0):
-        """ processor_name MUST be valid!
+    def parser(self, parser_name, lines):
+        """ parser_name MUST be valid!
             writes out the result instead of returning it!
         """
-        node = self.document.createElement('processor')
-        node.setAttribute('name', processor_name)
-        node.setAttribute('isparser', is_parser)
+        node = self.document.createElement('parser')
+        node.setAttribute('name', parser_name)
         node.appendChild(self.document.createTextNode('\n'.join(lines)))
-        return (self._set_tag('processor', True, name=processor_name, isparser=is_parser) +
+        return (self._set_tag('parser', True, name=parser_name) +
                 self.text('\n'.join(lines)) +
-                self._set_tag('processor', False))
+                self._set_tag('parser', False))
 
     def dynamic_content(self, parser, callback, arg_list=[], arg_dict={}, returns_content=1):
         content = parser[callback](*arg_list, **arg_dict)
--- a/MoinMoin/formatter/text_gedit.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/formatter/text_gedit.py	Tue May 16 09:43:18 2006 +1200
@@ -100,8 +100,8 @@
             result = "[[%s]]" % name
         return '<span style="background-color:#ffff11">%s</span>' % result
 
-    def processor(self, processor_name, lines, is_parser=0):
-        """ processor_name MUST be valid!
+    def parser(self, parser_name, lines):
+        """ parser_name MUST be valid!
         """
         result = [self.preformatted(1)]
         for line in lines:
--- a/MoinMoin/formatter/text_python.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/formatter/text_python.py	Tue May 16 09:43:18 2006 +1200
@@ -180,22 +180,19 @@
                 (self.__adjust_formatter_state(),
                  self.__formatter, name, args))
             
-    def processor(self, processor_name, lines, is_parser=0):
-        """ processor_name MUST be valid!
-        prints out the result insted of returning it!
+    def parser(self, parser_name, lines):
+        """ parser_name MUST be valid!
+            prints out the result instead of returning it!
         """
-        type = ["processor", "parser"][is_parser]
         try:
-            Dependencies = wikiutil.importPlugin(self.request.cfg, type,
-                                                 processor_name,
-                                                 "Dependencies")
+            Dependencies = wikiutil.importPlugin(self.request.cfg, "parser", parser_name, "Dependencies")
         except wikiutil.PluginAttributeError:
             Dependencies = self.defaultDependencies
         if self.__is_static(Dependencies):
-            return self.formatter.processor(processor_name, lines, is_parser)
+            return self.formatter.parser(parser_name, lines)
         else:
-            return self.__insert_code('%s%s.processor(%r, %r, %r)' %
+            return self.__insert_code('%s%s.parser(%r, %r)' %
                                       (self.__adjust_formatter_state(),
                                        self.__formatter,
-                                       processor_name, lines, is_parser))
+                                       parser_name, lines))
 
--- a/MoinMoin/i18n/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
--- a/MoinMoin/i18n/Makefile	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/i18n/Makefile	Tue May 16 09:43:18 2006 +1200
@@ -79,10 +79,12 @@
 	./build_meta_py $$langs
 
 update-po:
-	$(MAKE) $(DOMAIN).pot-update
-	$(MAKE) $(UPDATEPOFILES)
-	$(MAKE) $(CATALOGS)
-	$(MAKE) metapy
+	@echo "Edit Makefile and reenable this after finally closing 1.5.x branch,"
+	@echo "no 1.5.x i18n updates will be possible once this is run in 1.6."
+	#$(MAKE) $(DOMAIN).pot-update
+	#$(MAKE) $(UPDATEPOFILES)
+	#$(MAKE) $(CATALOGS)
+	#$(MAKE) metapy
 
 stats:
 	@files="$(POFILES)"; \
--- a/MoinMoin/logfile/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
-
--- a/MoinMoin/macro/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/macro/SystemInfo.py	Tue May 16 09:43:18 2006 +1200
@@ -0,0 +1,121 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - SystemInfo Macro
+
+    This macro shows some info about your wiki, wiki software and your system.
+
+    @copyright: 2006 MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+
+Dependencies = ['pages']
+
+import operator, sys, os
+from StringIO import StringIO
+
+from MoinMoin import wikiutil, version
+from MoinMoin import action, macro, parser
+from MoinMoin.logfile import editlog, eventlog
+from MoinMoin.Page import Page
+
+def execute(Macro, args):
+    """ show SystemInfo: wiki infos, wiki sw version, space usage infos """
+    def _formatInReadableUnits(size):
+        size = float(size)
+        unit = u' Byte'
+        if size > 9999:
+            unit = u' KiB'
+            size /= 1024
+        if size > 9999:
+            unit = u' MiB'
+            size /= 1024
+        if size > 9999:
+            unit = u' GiB'
+            size /= 1024
+        return u"%.1f %s" % (size, unit)
+
+    def _getDirectorySize(path):
+        try:
+            dirsize = 0
+            for root, dirs, files in os.walk(path):
+                dirsize += sum([os.path.getsize(os.path.join(root, name)) for name in files])
+        except EnvironmentError, e:
+            dirsize = -1
+        return dirsize
+
+    _ = Macro._
+    request = Macro.request
+    
+    # check for 4XSLT
+    try:
+        import Ft
+        ftversion = Ft.__version__
+    except ImportError:
+        ftversion = None
+    except AttributeError:
+        ftversion = 'N/A'
+
+    t_count = None
+    try:
+        from threading import activeCount
+        t_count = activeCount()
+    except ImportError:
+        pass
+
+    # Get the full pagelist in the wiki
+    pagelist = request.rootpage.getPageList(user='')
+    totalsize = reduce(operator.add, [Page(request, name).size()
+                                      for name in pagelist])
+
+    buf = StringIO()
+    row = lambda label, value, buf=buf: buf.write(
+        u'<dt>%s</dt><dd>%s</dd>' % (label, value))
+
+    buf.write(u'<dl>')
+    row(_('Python Version'), sys.version)
+    row(_('MoinMoin Version'), _('Release %s [Revision %s]') % (version.release, version.revision))
+    if ftversion:
+        row(_('4Suite Version'), ftversion)
+
+    systemPages = [page for page in pagelist
+                   if wikiutil.isSystemPage(request, page)]
+    row(_('Number of pages'), str(len(pagelist)-len(systemPages)))
+    row(_('Number of system pages'), str(len(systemPages)))
+
+    row(_('Accumulated page sizes'), _formatInReadableUnits(totalsize))
+    data_dir = request.cfg.data_dir
+    row(_('Disk usage of %(data_dir)s/pages/') % {'data_dir': data_dir},
+        _formatInReadableUnits(_getDirectorySize(os.path.join(data_dir, 'pages'))))
+    row(_('Disk usage of %(data_dir)s/') % {'data_dir': data_dir},
+        _formatInReadableUnits(_getDirectorySize(data_dir)))
+
+    edlog = editlog.EditLog(request)
+    row(_('Entries in edit log'), "%s (%s)" % (edlog.lines(), _formatInReadableUnits(edlog.size())))
+
+    # This puts a heavy load on the server when the log is large
+    eventlogger = eventlog.EventLog(request)
+    nonestr = _("NONE")
+    row('Event log', _formatInReadableUnits(eventlogger.size()))
+    
+    row(_('Global extension macros'), ', '.join(macro.extension_macros) or nonestr)
+    row(_('Local extension macros'), 
+        ', '.join(wikiutil.wikiPlugins('macro', Macro.cfg)) or nonestr)
+    
+    ext_actions = [x for x in action.extension_actions
+                   if not x in request.cfg.actions_excluded]
+    row(_('Global extension actions'), ', '.join(ext_actions) or nonestr)
+    row(_('Local extension actions'), 
+        ', '.join(action.getPlugins(request)[1]) or nonestr)
+    
+    row(_('Global parsers'), ', '.join(parser.modules) or nonestr)
+    row(_('Local extension parsers'), 
+        ', '.join(wikiutil.wikiPlugins('parser', Macro.cfg)) or nonestr)
+    
+    state = (_('Disabled'), _('Enabled'))
+    row(_('Lupy search'), state[request.cfg.lupy_search])
+    
+    row(_('Active threads'), t_count or 'N/A')
+    buf.write(u'</dl>')
+
+    return Macro.formatter.rawHTML(buf.getvalue())
+
--- a/MoinMoin/macro/__init__.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/macro/__init__.py	Tue May 16 09:43:18 2006 +1200
@@ -1,19 +1,18 @@
 # -*- coding: iso-8859-1 -*-
 """
-    MoinMoin - Macro Package
+    MoinMoin - Macro Implementation
 
-    The canonical interface to macros is their execute() function,
+    These macros are used by the parser/wiki.py module
+    to implement complex and/or dynamic page content.
+
+    The canonical interface to plugin macros is their execute() function,
     which gets passed an instance of the Macro class. Such an instance
     has the four members parser, formatter, form and request.
 
-    Using "form" directly is deprecated and should be replaced
-    by "request.form".
+    Using "form" directly is deprecated and should be replaced by "request.form".
 
-    Besides the execute() function, macros can export additional
-    functions to offer services to other macros or actions. A few
-    actually do that, e.g. AttachFile.
-
-    @copyright: 2000 by Jürgen Hermann <jh@web.de>
+    @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>,
+                2006 MoinMoin:ThomasWaldmann
     @license: GNU GPL, see COPYING for details.
 """
 
@@ -21,3 +20,509 @@
 
 extension_macros = pysupport.getPackageModules(__file__)
 modules = extension_macros
+
+import re, time, os
+from MoinMoin import action, config, util
+from MoinMoin import wikiutil, i18n
+from MoinMoin.Page import Page
+from MoinMoin.util import pysupport
+
+names = ["TitleSearch", "WordIndex", "TitleIndex",
+         "GoTo", "InterWiki", "PageCount", "UserPreferences",
+         # Macros with arguments
+         "Icon", "PageList", "Date", "DateTime", "Anchor", "MailTo", "GetVal",
+         "TemplateList",
+]
+names.extend(i18n.languages.keys())
+
+#############################################################################
+### Helpers
+#############################################################################
+
+def getNames(cfg):
+    if hasattr(cfg, 'macro_names'):
+        return cfg.macro_names
+    else:
+        lnames = names[:]
+        lnames.extend(wikiutil.getPlugins('macro', cfg))
+        return lnames
+
+def _make_index_key(index_letters, additional_html=""):
+    index_letters.sort()
+    links = map(lambda ch:
+                    '<a href="#%s">%s</a>' %
+                    (wikiutil.quoteWikinameURL(ch), ch.replace('~', 'Others')),
+                index_letters)
+    return "<p>%s%s</p>" % (' | '.join(links), additional_html)
+
+
+#############################################################################
+### Macros - Handlers for [[macroname]] markup
+#############################################################################
+
+class Macro:
+    """ Macro handler 
+    
+    There are three kinds of macros: 
+     * Builtin Macros - implemented in this file and named _macro_[name]
+     * Language Pseudo Macros - any lang the wiki knows can be use as
+       macro and is implemented here by _m_lang() 
+     * External macros - implemented in either MoinMoin.macro package, or
+       in the specific wiki instance in the plugin/macro directory
+    """
+    defaultDependency = ["time"]
+
+    Dependencies = {
+        "TitleSearch" : ["namespace"],
+        "Goto"        : [],
+        "WordIndex"   : ["namespace"],
+        "TitleIndex"  : ["namespace"],
+        "InterWiki"   : ["pages"],  # if interwikimap is editable
+        "PageCount"   : ["namespace"],
+        "Icon"        : ["user"], # users have different themes and user prefs
+        "PageList"    : ["namespace"],
+        "Date"        : ["time"],
+        "DateTime"    : ["time"],
+        "UserPreferences" :["time"],
+        "Anchor"      : [],
+        "Mailto"      : ["user"],
+        "GetVal"      : ["pages"],
+        "TemplateList": ["namespace"],
+        }
+
+    # we need the lang macros to execute when html is generated,
+    # to have correct dir and lang html attributes
+    for lang in i18n.languages.keys():
+        Dependencies[lang] = []
+    
+
+    def __init__(self, parser):
+        self.parser = parser
+        self.form = self.parser.form
+        self.request = self.parser.request
+        self.formatter = self.request.formatter
+        self._ = self.request.getText
+        self.cfg = self.request.cfg
+        
+        # Initialized on execute
+        self.name = None
+
+    def execute(self, macro_name, args):
+        """ Get and execute a macro 
+        
+        Try to get a plugin macro, or a builtin macro or a language
+        macro, or just raise ImportError. 
+        """
+        self.name = macro_name
+        try:
+            execute = wikiutil.importPlugin(self.cfg, 'macro', macro_name)
+        except wikiutil.PluginMissingError:
+            try:
+                builtins = self.__class__
+                execute = getattr(builtins, '_macro_' + macro_name)
+            except AttributeError:
+                if macro_name in i18n.languages:
+                    execute = builtins._m_lang
+                else:
+                    raise ImportError("Cannot load macro %s" % macro_name)
+        return execute(self, args)
+
+    def _m_lang(self, text):
+        """ Set the current language for page content.
+        
+            Language macro are used in two ways:
+             * [lang] - set the current language until next lang macro
+             * [lang(text)] - insert text with specific lang inside page
+        """
+        if text:
+            return (self.formatter.lang(1, self.name) +
+                    self.formatter.text(text) +
+                    self.formatter.lang(0, self.name))
+        
+        self.request.current_lang = self.name
+        return ''
+  
+    def get_dependencies(self, macro_name):
+        if macro_name in self.Dependencies:
+            return self.Dependencies[macro_name]
+        try:
+            return wikiutil.importPlugin(self.request.cfg, 'macro',
+                                         macro_name, 'Dependencies')
+        except wikiutil.PluginError:
+            return self.defaultDependency
+
+    def _macro_TitleSearch(self, args):
+        return self._m_search("titlesearch")
+
+    def _m_search(self, type):
+        """ Make a search box
+
+        Make both Title Search and Full Search boxes, according to type.
+
+        @param type: search box type: 'titlesearch' or 'fullsearch'
+        @rtype: unicode
+        @return: search box html fragment
+        """
+        _ = self._
+        if self.form.has_key('value'):
+            default = wikiutil.escape(self.form["value"][0], quote=1)
+        else:
+            default = ''
+
+        # Title search settings
+        boxes = ''
+        button = _("Search Titles")
+
+        # Special code for fullsearch
+        if type == "fullsearch":
+            boxes = [
+                u'<br>',
+                u'<input type="checkbox" name="context" value="160" checked="checked">',
+                _('Display context of search results'),
+                u'<br>',
+                u'<input type="checkbox" name="case" value="1">',
+                _('Case-sensitive searching'),
+                ]
+            boxes = u'\n'.join(boxes)
+            button = _("Search Text")
+            
+        # Format
+        type = (type == "titlesearch")
+        html = [
+            u'<form method="get" action="">',
+            u'<div>',
+            u'<input type="hidden" name="action" value="fullsearch">',
+            u'<input type="hidden" name="titlesearch" value="%i">' % type,
+            u'<input type="text" name="value" size="30" value="%s">' % default,
+            u'<input type="submit" value="%s">' % button,
+            boxes,
+            u'</div>',
+            u'</form>',    
+            ]
+        html = u'\n'.join(html)
+        return self.formatter.rawHTML(html)
+    
+    def _macro_GoTo(self, args):
+        """ Make a goto box
+
+        @param args: macro arguments
+        @rtype: unicode
+        @return: goto box html fragment
+        """
+        _ = self._
+        html = [
+            u'<form method="get" action="">',
+            u'<div>',
+            u'<input type="hidden" name="action" value="goto">',
+            u'<input type="text" name="target" size="30">',
+            u'<input type="submit" value="%s">' % _("Go To Page"),
+            u'</div>',
+            u'</form>',
+            ]
+        html = u'\n'.join(html)
+        return self.formatter.rawHTML(html)
+
+    def _macro_WordIndex(self, args):
+        _ = self._
+        allpages = int(self.form.get('allpages', [0])[0]) != 0
+        # Get page list readable by current user
+        # Filter by isSystemPage if needed
+        if allpages:
+            # TODO: make this fast by caching full page list
+            pages = self.request.rootpage.getPageList()
+        else:
+            def filter(name):
+                return not wikiutil.isSystemPage(self.request, name)
+            pages = self.request.rootpage.getPageList(filter=filter)
+        map = {}
+        word_re = re.compile(u'[%s][%s]+' % (config.chars_upper, config.chars_lower), re.UNICODE)
+        for name in pages:
+            for word in word_re.findall(name):
+                try:
+                    if not map[word].count(name):
+                        map[word].append(name)
+                except KeyError:
+                    map[word] = [name]
+
+        all_words = map.keys()
+        all_words.sort()
+        index_letters = []
+        current_letter = None
+        html = []
+        for word in all_words:
+            letter = wikiutil.getUnicodeIndexGroup(word)
+            if letter != current_letter:
+                #html.append(self.formatter.anchordef()) # XXX no text param available!
+                html.append(u'<a name="%s"><h3>%s</h3></a>' % (
+                    wikiutil.quoteWikinameURL(letter), letter.replace('~', 'Others')))
+                current_letter = letter
+            if letter not in index_letters:
+                index_letters.append(letter)
+
+            html.append(self.formatter.strong(1))
+            html.append(word)
+            html.append(self.formatter.strong(0))
+            html.append(self.formatter.bullet_list(1))
+            links = map[word]
+            links.sort()
+            last_page = None
+            for name in links:
+                if name == last_page:
+                    continue
+                html.append(self.formatter.listitem(1))
+                html.append(Page(self.request, name).link_to(self.request))
+                html.append(self.formatter.listitem(0))
+            html.append(self.formatter.bullet_list(0))
+        
+        qpagename = wikiutil.quoteWikinameURL(self.formatter.page.page_name)
+        index = _make_index_key(index_letters, u"""<br>
+<a href="%s?allpages=%d">%s</a>
+""" % (qpagename, not allpages, (_('Include system pages'), _('Exclude system pages'))[allpages]) )
+        return u'%s%s' % (index, u''.join(html)) 
+
+
+    def _macro_TitleIndex(self, args):
+        _ = self._
+        html = []
+        index_letters = []
+        allpages = int(self.form.get('allpages', [0])[0]) != 0
+        # Get page list readable by current user
+        # Filter by isSystemPage if needed
+        if allpages:
+            # TODO: make this fast by caching full page list
+            pages = self.request.rootpage.getPageList()
+        else:
+            def filter(name):
+                return not wikiutil.isSystemPage(self.request, name)
+            pages = self.request.rootpage.getPageList(filter=filter)
+
+        # Sort ignoring case
+        tmp = [(name.upper(), name) for name in pages]
+        tmp.sort()
+        pages = [item[1] for item in tmp]
+                
+        current_letter = None
+        for name in pages:
+            letter = wikiutil.getUnicodeIndexGroup(name)
+            if letter not in index_letters:
+                index_letters.append(letter)
+            if letter != current_letter:
+                html.append(u'<a name="%s"><h3>%s</h3></a>' % (
+                    wikiutil.quoteWikinameURL(letter), letter.replace('~', 'Others')))
+                current_letter = letter
+            else:
+                html.append(u'<br>')
+            html.append(u'%s\n' % Page(self.request, name).link_to(self.request, attachment_indicator=1))
+
+        # add rss link
+        index = ''
+        if 0: # if wikixml.ok: # XXX currently switched off (not implemented)
+            from MoinMoin import wikixml
+            index = (index + self.formatter.url(1, 
+                wikiutil.quoteWikinameURL(self.formatter.page.page_name) + "?action=rss_ti", do_escape=0) +
+                     self.formatter.icon("rss") +
+                     self.formatter.url(0))
+
+        qpagename = wikiutil.quoteWikinameURL(self.formatter.page.page_name)
+        index = index + _make_index_key(index_letters, u"""<br>
+<a href="%s?allpages=%d">%s</a>&nbsp;|
+<a href="%s?action=titleindex">%s</a>&nbsp;|
+<a href="%s?action=titleindex&amp;mimetype=text/xml">%s</a>
+""" % (qpagename, not allpages, (_('Include system pages'), _('Exclude system pages'))[allpages],
+       qpagename, _('Plain title index'),
+       qpagename, _('XML title index')) )
+
+        return u'%s%s' % (index, u''.join(html)) 
+
+
+    def _macro_InterWiki(self, args):
+        from StringIO import StringIO
+
+        # load interwiki list
+        dummy = wikiutil.resolve_wiki(self.request, '')
+
+        buf = StringIO()
+        buf.write('<dl>')
+        list = self.cfg._interwiki_list.items() # this is where we cached it
+        list.sort()
+        for tag, url in list:
+            buf.write('<dt><tt><a href="%s">%s</a></tt></dt>' % (
+                wikiutil.join_wiki(url, 'RecentChanges'), tag))
+            if url.find('$PAGE') == -1:
+                buf.write('<dd><tt><a href="%s">%s</a></tt></dd>' % (url, url))
+            else:
+                buf.write('<dd><tt>%s</tt></dd>' % url)
+        buf.write('</dl>')
+
+        return self.formatter.rawHTML(buf.getvalue())
+
+    def _macro_PageCount(self, args):
+        """ Return number of pages readable by current user
+        
+        Return either an exact count (slow!) or fast count including
+        deleted pages.
+        """
+        # Check input
+        options = {None: 0, '': 0, 'exists': 1}
+        try:
+            exists = options[args]
+        except KeyError:
+            # Wrong argument, return inline error message
+            arg = self.formatter.text(args)
+            return (self.formatter.span(1, css_class="error") +
+                    'Wrong argument: %s' % arg +
+                    self.formatter.span(0))
+        
+        count = self.request.rootpage.getPageCount(exists=exists)
+        return self.formatter.text("%d" % count)
+
+    def _macro_Icon(self, args):
+        icon = args.lower()
+        return self.formatter.icon(icon)
+
+    def _macro_PageList(self, needle):
+        from MoinMoin import search
+        _ = self._
+        literal=0
+        case=0
+
+        # If called with empty or no argument, default to regex search for .+,
+        # the full page list.
+        if not needle:
+            needle = 'regex:.+'
+
+        # With whitespace argument, return same error message as FullSearch
+        elif needle.isspace():
+            err = _('Please use a more selective search term instead of '
+                    '{{{"%s"}}}') %  needle
+            return '<span class="error">%s</span>' % err
+            
+        # Return a title search for needle, sorted by name.
+        query = search.QueryParser(literal=literal, titlesearch=1,
+                                   case=case).parse_query(needle)
+        results = search.searchPages(self.request, query)
+        results.sortByPagename()
+        return results.pageList(self.request, self.formatter)
+        
+    def _macro_TemplateList(self, args):
+        _ = self._
+        try:
+            needle_re = re.compile(args or '', re.IGNORECASE)
+        except re.error, e:
+            return "<strong>%s: %s</strong>" % (
+                _("ERROR in regex '%s'") % (args,), e)
+
+        # Get page list readable by current user, filtered by needle
+        hits = self.request.rootpage.getPageList(filter=needle_re.search)
+        hits.sort()
+        
+        result = []
+        result.append(self.formatter.bullet_list(1))
+        for pagename in hits:
+            result.append(self.formatter.listitem(1))
+            result.append(self.formatter.pagelink(1, pagename, generated=1))
+            result.append(self.formatter.text(pagename))
+            result.append(self.formatter.pagelink(0, pagename))
+            result.append(self.formatter.listitem(0))
+        result.append(self.formatter.bullet_list(0))
+        return ''.join(result)
+
+
+    def __get_Date(self, args, format_date):
+        _ = self._
+        if not args:
+            tm = time.time() # always UTC
+        elif len(args) >= 19 and args[4] == '-' and args[7] == '-' \
+                and args[10] == 'T' and args[13] == ':' and args[16] == ':':
+            # we ignore any time zone offsets here, assume UTC,
+            # and accept (and ignore) any trailing stuff
+            try:
+                year, month, day = int(args[0:4]), int(args[5:7]), int(args[8:10]) 
+                hour, minute, second = int(args[11:13]), int(args[14:16]), int(args[17:19]) 
+                tz = args[19:] # +HHMM, -HHMM or Z or nothing (then we assume Z)
+                tzoffset = 0 # we assume UTC no matter if there is a Z
+                if tz:
+                    sign = tz[0]
+                    if sign in '+-':
+                        tzh, tzm = int(tz[1:3]), int(tz[3:])
+                        tzoffset = (tzh*60+tzm)*60
+                        if sign == '-':
+                            tzoffset = -tzoffset
+                tm = (year, month, day, hour, minute, second, 0, 0, 0)
+            except ValueError, e:
+                return "<strong>%s: %s</strong>" % (
+                    _("Bad timestamp '%s'") % (args,), e)
+            # as mktime wants a localtime argument (but we only have UTC),
+            # we adjust by our local timezone's offset
+            try:
+                tm = time.mktime(tm) - time.timezone - tzoffset
+            except (OverflowError, ValueError), err:
+                tm = 0 # incorrect, but we avoid an ugly backtrace
+        else:
+            # try raw seconds since epoch in UTC
+            try:
+                tm = float(args)
+            except ValueError, e:
+                return "<strong>%s: %s</strong>" % (
+                    _("Bad timestamp '%s'") % (args,), e)
+        return format_date(tm)
+
+    def _macro_Date(self, args):
+        return self.__get_Date(args, self.request.user.getFormattedDate)
+
+    def _macro_DateTime(self, args):
+        return self.__get_Date(args, self.request.user.getFormattedDateTime)
+
+
+    def _macro_UserPreferences(self, args):
+        from MoinMoin import userform
+
+        create_only = False
+        if isinstance(args, unicode):
+            args = args.strip(" '\"")
+            create_only = (args.lower()=="createonly")
+
+        return self.formatter.rawHTML(userform.getUserForm(
+            self.request,
+            create_only=create_only))
+
+    def _macro_Anchor(self, args):
+        return self.formatter.anchordef(args or "anchor")
+
+    def _macro_MailTo(self, args):
+        from MoinMoin.util.mail import decodeSpamSafeEmail
+
+        args = args or ''
+        if args.find(',') == -1:
+            email = args
+            text = ''
+        else:
+            email, text = args.split(',', 1)
+
+        email, text = email.strip(), text.strip()
+
+        if self.request.user.valid:
+            # decode address and generate mailto: link
+            email = decodeSpamSafeEmail(email)
+            result = (self.formatter.url(1, 'mailto:' + email, css='mailto', do_escape=0) +
+                      self.formatter.text(text or email) +
+                      self.formatter.url(0))
+        else:
+            # unknown user, maybe even a spambot, so
+            # just return text as given in macro args
+            email = self.formatter.code(1) + \
+                self.formatter.text("<%s>" % email) + \
+                self.formatter.code(0)
+            if text:
+                result = self.formatter.text(text) + " " + email
+            else:
+                result = email
+
+        return result
+
+    def _macro_GetVal(self, args):
+        page,key = args.split(',')
+        d = self.request.dicts.dict(page)
+        result = d.get(key,'')
+        return self.formatter.text(result)
+
--- a/MoinMoin/multiconfig.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/multiconfig.py	Tue May 16 09:43:18 2006 +1200
@@ -170,7 +170,7 @@
     acl_rights_after = u""
     acl_rights_valid = ['read', 'write', 'delete', 'revert', 'admin']
     
-    actions_excluded = [] # ['DeletePage', 'AttachFile', 'RenamePage']
+    actions_excluded = [] # ['DeletePage', 'AttachFile', 'RenamePage', 'test', ]
     allow_xslt = 0
     attachments = None # {'dir': path, 'url': url-prefix}
     auth = [authmodule.moin_cookie]
--- a/MoinMoin/packages.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/packages.py	Tue May 16 09:43:18 2006 +1200
@@ -427,8 +427,8 @@
         request_url = "localhost/"
 
     # Setup MoinMoin environment
-    from MoinMoin.request import RequestCLI
-    request = RequestCLI(url = 'localhost/')
+    from MoinMoin.request import CLI
+    request = CLI.Request(url = 'localhost/')
     request.form = request.args = request.setup_args()
 
     package = ZipPackage(request, packagefile)
--- a/MoinMoin/parser/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
--- a/MoinMoin/parser/CSV.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/parser/CSV.py	Tue May 16 09:43:18 2006 +1200
@@ -2,9 +2,7 @@
 """
     MoinMoin - Parser for CSV data
 
-    This is just a copy of the old CSV processor.
-    I think it is intended as an example, cause it
-    lacks to flexibility to read arbitary csv dialects.
+    This parser lacks to flexibility to read arbitary csv dialects.
 
     Perhaps this should be rewritten using another CSV lib
     because the standard module csv does not support unicode.
--- a/MoinMoin/parser/wiki.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/parser/wiki.py	Tue May 16 09:43:18 2006 +1200
@@ -7,7 +7,7 @@
 """
 
 import os, re
-from MoinMoin import config, wikimacro, wikiutil
+from MoinMoin import config, wikiutil, macro
 from MoinMoin.Page import Page
 from MoinMoin.util import web
 
@@ -62,7 +62,7 @@
 (?P<sup>\^.*?\^)
 (?P<sub>,,[^,]{1,40},,)
 (?P<tt>\{\{\{.*?\}\}\})
-(?P<processor>(\{\{\{(#!.*|\s*$)))
+(?P<parser>(\{\{\{(#!.*|\s*$)))
 (?P<pre>(\{\{\{ ?|\}\}\}))
 (?P<small>(\~- ?|-\~))
 (?P<big>(\~\+ ?|\+\~))
@@ -101,7 +101,7 @@
     # Don't start p before these 
     no_new_p_before = ("heading rule table tableZ tr td "
                        "ul ol dl dt dd li li_none indent "
-                       "macro processor pre")
+                       "macro parser pre")
     no_new_p_before = no_new_p_before.split()
     no_new_p_before = dict(zip(no_new_p_before, [1] * len(no_new_p_before)))
 
@@ -134,7 +134,7 @@
         self.list_indents = []
         self.list_types = []
         
-        self.formatting_rules = self.formatting_rules % {'macronames': u'|'.join(wikimacro.getNames(self.cfg))}
+        self.formatting_rules = self.formatting_rules % {'macronames': u'|'.join(macro.getNames(self.cfg))}
 
     def _close_item(self, result):
         #result.append("<!-- close item begin -->\n")
@@ -767,14 +767,13 @@
         return (result + self.formatter.text(title_text) +
                 self.formatter.heading(0, depth))
     
-    def _processor_repl(self, word):
-        """Handle processed code displays."""
+    def _parser_repl(self, word):
+        """Handle parsed code displays."""
         if word[:3] == '{{{':
             word = word[3:]
 
-        self.processor = None
-        self.processor_name = None
-        self.processor_is_parser = 0
+        self.parser = None
+        self.parser_name = None
         s_word = word.strip()
         if s_word == '#!':
             # empty bang paths lead to a normal code display
@@ -783,14 +782,14 @@
             self.in_pre = 3
             return self._closeP() + self.formatter.preformatted(1)
         elif s_word[:2] == '#!':
-            # First try to find a processor for this (will go away in 2.0)
-            processor_name = s_word[2:].split()[0]
-            self.setProcessor(processor_name)
+            # First try to find a parser for this (will go away in 2.0)
+            parser_name = s_word[2:].split()[0]
+            self.setParser(parser_name)
 
-        if self.processor:
-            self.processor_name = processor_name
+        if self.parser:
+            self.parser_name = parser_name
             self.in_pre = 2
-            self.colorize_lines = [word]
+            self.parser_lines = [word]
             return ''
         elif s_word:
             self.in_pre = 3
@@ -846,7 +845,7 @@
 
         # create macro instance
         if self.macro is None:
-            self.macro = wikimacro.Macro(self)
+            self.macro = macro.Macro(self)
         return self.formatter.macro(self.macro, macro_name, args)
 
     def scan(self, scan_re, line):
@@ -982,17 +981,16 @@
                 # still looking for processing instructions
                 # TODO: use strings for pre state, not numbers
                 if self.in_pre == 1:
-                    self.processor = None
-                    self.processor_is_parser = 0
-                    processor_name = ''
+                    self.parser = None
+                    parser_name = ''
                     if (line.strip()[:2] == "#!"):
-                        processor_name = line.strip()[2:].split()[0]
-                        self.setProcessor(processor_name)
+                        parser_name = line.strip()[2:].split()[0]
+                        self.setParser(parser_name)
 
-                    if self.processor:
+                    if self.parser:
                         self.in_pre = 2
-                        self.colorize_lines = [line]
-                        self.processor_name = processor_name
+                        self.parser_lines = [line]
+                        self.parser_name = parser_name
                         continue
                     else:
                         self.request.write(self._closeP() +
@@ -1002,21 +1000,19 @@
                     # processing mode
                     endpos = line.find("}}}")
                     if endpos == -1:
-                        self.colorize_lines.append(line)
+                        self.parser_lines.append(line)
                         continue
                     if line[:endpos]:
-                        self.colorize_lines.append(line[:endpos])
+                        self.parser_lines.append(line[:endpos])
                     
-                    # Close p before calling processor
+                    # Close p before calling parser
                     # TODO: do we really need this?
                     self.request.write(self._closeP())
-                    res = self.formatter.processor(self.processor_name,
-                                                   self.colorize_lines, 
-                                                   self.processor_is_parser)
+                    res = self.formatter.parser(self.parser_name, self.parser_lines)
                     self.request.write(res)
-                    del self.colorize_lines
+                    del self.parser_lines
                     self.in_pre = 0
-                    self.processor = None
+                    self.parser = None
 
                     # send rest of line through regex machinery
                     line = line[endpos+3:]
@@ -1109,22 +1105,13 @@
         if self.formatter.in_p: self.request.write(self.formatter.paragraph(0))
         if self.in_table: self.request.write(self.formatter.table(0))
 
-    # --------------------------------------------------------------------
-    # Private helpers
+    # Private helpers ------------------------------------------------------------
     
-    def setProcessor(self, name):
-        """ Set processer to either processor or parser named 'name' """
-        cfg = self.request.cfg
+    def setParser(self, name):
+        """ Set parser to parser named 'name' """
         try:
-            self.processor = wikiutil.importPlugin(cfg, "processor", name,
-                                                   "process")
-            self.processor_is_parser = 0
+            self.parser = wikiutil.importPlugin(self.request.cfg, "parser", name, "Parser")
         except wikiutil.PluginMissingError:
-            try:
-                self.processor = wikiutil.importPlugin(cfg, "parser", name,
-                                                   "Parser")
-                self.processor_is_parser = 1
-            except wikiutil.PluginMissingError:
-                self.processor = None
+            self.parser = None
 
 
--- a/MoinMoin/processor/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
--- a/MoinMoin/processor/__init__.py	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - Processor Package
-
-    DEPRECATED!
-
-    Please use Parsers instead of Processors.
-
-    @copyright: 2002 by Jürgen Hermann <jh@web.de>
-    @license: GNU GPL, see COPYING for details.
-"""
-
-from MoinMoin.util import pysupport
-
-processors = pysupport.getPackageModules(__file__)
-modules = processors
--- a/MoinMoin/request.py	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2058 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - Data associated with a single Request
-
-    @copyright: 2001-2003 by Jürgen Hermann <jh@web.de>
-    @copyright: 2003-2006 by Thomas Waldmann
-    @license: GNU GPL, see COPYING for details.
-"""
-
-import os, re, time, sys, cgi, StringIO
-import copy
-from MoinMoin import config, wikiutil, user, caching
-from MoinMoin.util import MoinMoinNoFooter, IsWin9x
-
-# Timing ---------------------------------------------------------------
-
-class Clock:
-    """ Helper class for code profiling
-        we do not use time.clock() as this does not work across threads
-    """
-
-    def __init__(self):
-        self.timings = {'total': time.time()}
-
-    def start(self, timer):
-        self.timings[timer] = time.time() - self.timings.get(timer, 0)
-
-    def stop(self, timer):
-        self.timings[timer] = time.time() - self.timings[timer]
-
-    def value(self, timer):
-        return "%.3f" % (self.timings[timer], )
-
-    def dump(self):
-        outlist = []
-        for timing in self.timings.items():
-            outlist.append("%s = %.3fs" % timing)
-        outlist.sort()
-        return outlist
-
-
-# Utilities
-
-def cgiMetaVariable(header, scheme='http'):
-    """ Return CGI meta variable for header name
-    
-    e.g 'User-Agent' -> 'HTTP_USER_AGENT'    
-    See http://www.faqs.org/rfcs/rfc3875.html section 4.1.18
-    """
-    var = '%s_%s' % (scheme, header)
-    return var.upper().replace('-', '_')
-    
-
-# Request Base ----------------------------------------------------------
-
-class RequestBase(object):
-    """ A collection for all data associated with ONE request. """
-
-    # Header set to force misbehaved proxies and browsers to keep their
-    # hands off a page
-    # Details: http://support.microsoft.com/support/kb/articles/Q234/0/67.ASP
-    nocache = [
-        "Pragma: no-cache",
-        "Cache-Control: no-cache",
-        "Expires: -1",
-    ]
-
-    # Defaults (used by sub classes)
-    http_accept_language = 'en'
-    server_name = 'localhost'
-    server_port = '80'
-
-    # Extra headers we support. Both standalone and twisted store
-    # headers as lowercase.
-    moin_location = 'x-moin-location'
-    proxy_host = 'x-forwarded-host'
-    
-    def __init__(self, properties={}):
-        # Decode values collected by sub classes
-        self.path_info = self.decodePagename(self.path_info)
-
-        self.failed = 0
-        self._available_actions = None
-        self._known_actions = None
-
-        # Pages meta data that we collect in one request
-        self.pages = {}
-              
-        self.sent_headers = 0
-        self.user_headers = []
-        self.cacheable = 0 # may this output get cached by http proxies/caches?
-        self.page = None
-        self._dicts = None
-        
-        # Fix dircaching problems on Windows 9x
-        if IsWin9x():
-            import dircache
-            dircache.reset()
-
-        # Check for dumb proxy requests
-        # TODO relying on request_uri will not work on all servers, especially
-        # not on external non-Apache servers
-        self.forbidden = False
-        if self.request_uri.startswith('http://'):
-            self.makeForbidden403()
-
-        # Init
-        else:
-            self.writestack = []
-            self.clock = Clock()
-            # order is important here!
-            self.__dict__.update(properties)
-            self._load_multi_cfg()
-            
-            self.isSpiderAgent = self.check_spider()
-        
-            # Set decode charsets.  Input from the user is always in
-            # config.charset, which is the page charsets. Except
-            # path_info, which may use utf-8, and handled by decodePagename.
-            self.decode_charsets = [config.charset]
-            
-            # hierarchical wiki - set rootpage
-            from MoinMoin.Page import Page
-            #path = self.getPathinfo()
-            #if path.startswith('/'):
-            #    pages = path[1:].split('/')
-            #    if 0: # len(path) > 1:
-            #        ## breaks MainPage/SubPage on flat storage
-            #        rootname = u'/'.join(pages[:-1])
-            #    else:
-            #        # this is the usual case, as it ever was...
-            #        rootname = u""
-            #else:
-            #    # no extra path after script name
-            #    rootname = u""
-
-            self.args = {}
-            self.form = {}
-
-            if not self.query_string.startswith('action=xmlrpc'):
-                self.args = self.form = self.setup_args()
-
-            rootname = u''
-            self.rootpage = Page(self, rootname, is_rootpage=1)
-
-            self.user = self.get_user_from_form()
-            
-            if not self.query_string.startswith('action=xmlrpc'):
-                if not self.forbidden and self.isForbidden():
-                    self.makeForbidden403()
-                if not self.forbidden and self.surge_protect():
-                    self.makeUnavailable503()
-
-            from MoinMoin import i18n
-
-            self.logger = None
-            self.pragma = {}
-            self.mode_getpagelinks = 0
-            self.no_closing_html_code = 0
-
-            self.i18n = i18n
-            self.lang = i18n.requestLanguage(self) 
-            # Language for content. Page content should use the wiki default lang,
-            # but generated content like search results should use the user language.
-            self.content_lang = self.cfg.language_default
-            self.getText = lambda text, i18n=self.i18n, request=self, lang=self.lang, **kv: i18n.getText(text, request, lang, kv.get('formatted', True))
-
-            self.opened_logs = 0
-            self.reset()
-        
-    def surge_protect(self):
-        """ check if someone requesting too much from us """
-        validuser = self.user.valid
-        current_id = validuser and self.user.name or self.remote_addr
-        if not validuser and current_id.startswith('127.'): # localnet
-            return False
-        current_action = self.form.get('action', ['show'])[0]
-        
-        limits = self.cfg.surge_action_limits
-        default_limit = self.cfg.surge_action_limits.get('default', (30, 60))
-        
-        now = int(time.time())
-        surgedict = {}
-        surge_detected = False
-        
-        try:
-            cache = caching.CacheEntry(self, 'surgeprotect', 'surge-log')
-            if cache.exists():
-                data = cache.content()
-                data = data.split("\n")
-                for line in data:
-                    try:
-                        id, t, action, surge_indicator = line.split("\t")
-                        t = int(t)
-                        maxnum, dt = limits.get(action, default_limit)
-                        if t >= now - dt:
-                            events = surgedict.setdefault(id, copy.copy({}))
-                            timestamps = events.setdefault(action, copy.copy([]))
-                            timestamps.append((t, surge_indicator))
-                    except StandardError, err:
-                        pass
-                
-            maxnum, dt = limits.get(current_action, default_limit)
-            events = surgedict.setdefault(current_id, copy.copy({}))
-            timestamps = events.setdefault(current_action, copy.copy([]))
-            surge_detected = len(timestamps) > maxnum
-
-            surge_indicator = surge_detected and "!" or ""
-            timestamps.append((now, surge_indicator))
-            if surge_detected:
-                if len(timestamps) < maxnum * 2:
-                    timestamps.append((now + self.cfg.surge_lockout_time, surge_indicator)) # continue like that and get locked out
-        
-            if current_action != 'AttachFile': # don't add AttachFile accesses to all or picture galleries will trigger SP
-                current_action = 'all' # put a total limit on user's requests
-                maxnum, dt = limits.get(current_action, default_limit)
-                events = surgedict.setdefault(current_id, copy.copy({}))
-                timestamps = events.setdefault(current_action, copy.copy([]))
-                surge_detected = surge_detected or len(timestamps) > maxnum
-            
-                surge_indicator = surge_detected and "!" or ""
-                timestamps.append((now, surge_indicator))
-                if surge_detected:
-                    if len(timestamps) < maxnum * 2:
-                        timestamps.append((now + self.cfg.surge_lockout_time, surge_indicator)) # continue like that and get locked out
-        
-            data = []
-            for id, events in surgedict.items():
-                for action, timestamps in events.items():
-                    for t, surge_indicator in timestamps:
-                        data.append("%s\t%d\t%s\t%s" % (id, t, action, surge_indicator))
-            data = "\n".join(data)
-            cache.update(data)
-        except StandardError, err:
-            pass
-
-        return surge_detected   
-        
-    def getDicts(self):
-        """ Lazy initialize the dicts on the first access """
-        if self._dicts is None:
-            from MoinMoin import wikidicts
-            dicts = wikidicts.GroupDict(self)
-            dicts.scandicts()
-            self._dicts = dicts
-        return self._dicts
-        
-    def delDicts(self):
-        """ Delete the dicts, used by some tests """
-        del self._dicts
-        self._dicts = None
-
-    dicts = property(getDicts, None, delDicts)
-  
-    def _load_multi_cfg(self):
-        # protect against calling multiple times
-        if not hasattr(self, 'cfg'):
-            from MoinMoin import multiconfig
-            self.cfg = multiconfig.getConfig(self.url)
-            
-    def setAcceptedCharsets(self, accept_charset):
-        """ Set accepted_charsets by parsing accept-charset header
-
-        Set self.accepted_charsets to an ordered list based on http_accept_charset. 
-        
-        Reference: http://www.w3.org/Protocols/rfc2616/rfc2616.txt
-
-        TODO: currently no code use this value.
-
-        @param accept_charset: accept-charset header
-        """        
-        charsets = []
-        if accept_charset:
-            accept_charset = accept_charset.lower()
-            # Add iso-8859-1 if needed
-            if (not '*' in accept_charset and
-                accept_charset.find('iso-8859-1') < 0):
-                accept_charset += ',iso-8859-1'
-
-            # Make a list, sorted by quality value, using Schwartzian Transform
-            # Create list of tuples (value, name) , sort, extract names  
-            for item in accept_charset.split(','):
-                if ';' in item:
-                    name, qval = item.split(';')
-                    qval = 1.0 - float(qval.split('=')[1])
-                else:
-                    name, qval = item, 0
-                charsets.append((qval, name))                 
-            charsets.sort()
-            # Remove *, its not clear what we should do with it later
-            charsets = [name for qval, name in charsets if name != '*']
-
-        self.accepted_charsets = charsets
-          
-    def _setup_vars_from_std_env(self, env):
-        """ Set common request variables from CGI environment
-        
-        Parse a standard CGI environment as created by common web servers.
-        Reference: http://www.faqs.org/rfcs/rfc3875.html
-
-        @param env: dict like object containing cgi meta variables
-        """
-        # Values we can just copy
-        self.env = env
-        self.http_accept_language = env.get('HTTP_ACCEPT_LANGUAGE',
-                                            self.http_accept_language)
-        self.server_name = env.get('SERVER_NAME', self.server_name)
-        self.server_port = env.get('SERVER_PORT', self.server_port)
-        self.saved_cookie = env.get('HTTP_COOKIE', '')
-        self.script_name = env.get('SCRIPT_NAME', '')
-        self.path_info = env.get('PATH_INFO', '')
-        self.query_string = env.get('QUERY_STRING', '')
-        self.request_method = env.get('REQUEST_METHOD', None)
-        self.remote_addr = env.get('REMOTE_ADDR', '')
-        self.http_user_agent = env.get('HTTP_USER_AGENT', '')
-
-        # REQUEST_URI is not part of CGI spec, but an addition of Apache.
-        self.request_uri = env.get('REQUEST_URI', '')
-        
-        # Values that need more work
-        self.setHttpReferer(env.get('HTTP_REFERER'))
-        self.setIsSSL(env)
-        self.setHost(env.get('HTTP_HOST'))
-        self.fixURI(env)
-        self.setURL(env)
-        
-        ##self.debugEnvironment(env)
-
-    def setHttpReferer(self, referer):
-        """ Set http_referer, making sure its ascii
-        
-        IE might send non-ascii value.
-        """
-        value = ''
-        if referer:
-            value = unicode(referer, 'ascii', 'replace')
-            value = value.encode('ascii', 'replace')
-        self.http_referer = value
-
-    def setIsSSL(self, env):
-        """ Set is_ssl 
-        
-        @param env: dict like object containing cgi meta variables
-        """
-        self.is_ssl = bool(env.get('SSL_PROTOCOL') or
-                           env.get('SSL_PROTOCOL_VERSION') or
-                           env.get('HTTPS') == 'on')
-
-    def setHost(self, host=None):
-        """ Set http_host 
-        
-        Create from server name and port if missing. Previous code
-        default to localhost.
-        """
-        if not host:
-            port = ''
-            standardPort = ('80', '443')[self.is_ssl]
-            if self.server_port != standardPort:
-                port = ':' + self.server_port
-            host = self.server_name + port
-        self.http_host = host
-        
-    def fixURI(self, env):
-        """ Fix problems with script_name and path_info
-        
-        Handle the strange charset semantics on Windows and other non
-        posix systems. path_info is transformed into the system code
-        page by the web server. Additionally, paths containing dots let
-        most webservers choke.
-        
-        Broken environment variables in different environments:
-                path_info script_name
-        Apache1     X          X      PI does not contain dots
-        Apache2     X          X      PI is not encoded correctly
-        IIS         X          X      path_info include script_name
-        Other       ?          -      ? := Possible and even RFC-compatible.
-                                      - := Hopefully not.
-
-        @param env: dict like object containing cgi meta variables
-        """ 
-        # Fix the script_name when using Apache on Windows.
-        server_software = env.get('SERVER_SOFTWARE', '')
-        if os.name == 'nt' and server_software.find('Apache/') != -1:
-            # Removes elements ending in '.' from the path.
-            self.script_name = '/'.join([x for x in self.script_name.split('/') 
-                                         if not x.endswith('.')])
-
-        # Fix path_info
-        if os.name != 'posix' and self.request_uri != '':
-            # Try to recreate path_info from request_uri.
-            import urlparse
-            scriptAndPath = urlparse.urlparse(self.request_uri)[2]
-            path = scriptAndPath.replace(self.script_name, '', 1)            
-            self.path_info = wikiutil.url_unquote(path, want_unicode=False)
-        elif os.name == 'nt':
-            # Recode path_info to utf-8
-            path = wikiutil.decodeWindowsPath(self.path_info)
-            self.path_info = path.encode("utf-8")
-            
-            # Fix bug in IIS/4.0 when path_info contain script_name
-            if self.path_info.startswith(self.script_name):
-                self.path_info = self.path_info[len(self.script_name):]
-
-    def setURL(self, env):
-        """ Set url, used to locate wiki config 
-        
-        This is the place to manipulate url parts as needed.
-        
-        @param env: dict like object containing cgi meta variables or http headers.
-        """
-        # If we serve on localhost:8000 and use a proxy on
-        # example.com/wiki, our urls will be example.com/wiki/pagename
-        # Same for the wiki config - they must use the proxy url.
-        self.rewriteHost(env)
-        self.rewriteURI(env)
-        
-        if not self.request_uri:
-            self.request_uri = self.makeURI()
-        self.url = self.http_host + self.request_uri
-
-    def rewriteHost(self, env):
-        """ Rewrite http_host transparently
-        
-        Get the proxy host using 'X-Forwarded-Host' header, added by
-        Apache 2 and other proxy software.
-        
-        TODO: Will not work for Apache 1 or others that don't add this header.
-        
-        TODO: If we want to add an option to disable this feature it
-        should be in the server script, because the config is not
-        loaded at this point, and must be loaded after url is set.
-        
-        @param env: dict like object containing cgi meta variables or http headers.
-        """
-        proxy_host = (env.get(self.proxy_host) or
-                      env.get(cgiMetaVariable(self.proxy_host)))
-        if proxy_host:
-            self.http_host = proxy_host
-
-    def rewriteURI(self, env):
-        """ Rewrite request_uri, script_name and path_info transparently
-        
-        Useful when running mod python or when running behind a proxy,
-        e.g run on localhost:8000/ and serve as example.com/wiki/.
-
-        Uses private 'X-Moin-Location' header to set the script name.
-        This allow setting the script name when using Apache 2
-        <location> directive::
-
-            <Location /my/wiki/>
-                RequestHeader set X-Moin-Location /my/wiki/
-            </location>
-        
-        TODO: does not work for Apache 1 and others that do not allow
-        setting custom headers per request.
-        
-        @param env: dict like object containing cgi meta variables or http headers.
-        """
-        location = (env.get(self.moin_location) or 
-                    env.get(cgiMetaVariable(self.moin_location)))
-        if location is None:
-            return
-        
-        scriptAndPath = self.script_name + self.path_info
-        location = location.rstrip('/')
-        self.script_name = location
-        
-        # This may happen when using mod_python
-        if scriptAndPath.startswith(location):
-            self.path_info = scriptAndPath[len(location):]
-
-        # Recreate the URI from the modified parts
-        if self.request_uri:
-            self.request_uri = self.makeURI()
-
-    def makeURI(self):
-        """ Return uri created from uri parts """
-        uri = self.script_name + wikiutil.url_quote(self.path_info)
-        if self.query_string:
-            uri += '?' + self.query_string
-        return uri
-
-    def splitURI(self, uri):
-        """ Return path and query splited from uri
-        
-        Just like CGI environment, the path is unquoted, the query is not.
-        """
-        if '?' in uri:
-            path, query = uri.split('?', 1)
-        else:
-            path, query = uri, ''
-        return wikiutil.url_unquote(path, want_unicode=False), query        
-
-    def get_user_from_form(self):
-        """ read the maybe present UserPreferences form and call get_user with the values """
-        name = self.form.get('name', [None])[0]
-        password = self.form.get('password', [None])[0]
-        login = self.form.has_key('login')
-        logout = self.form.has_key('logout')
-        u = self.get_user_default_unknown(name=name, password=password,
-                                          login=login, logout=logout,
-                                          user_obj=None)
-        return u
-    
-    def get_user_default_unknown(self, **kw):
-        """ call do_auth and if it doesnt return a user object, make some "Unknown User" """
-        user_obj = self.get_user_default_None(**kw)
-        if user_obj is None:
-            user_obj = user.User(self, auth_method="request:427")
-        return user_obj
-
-    def get_user_default_None(self, **kw):
-        """ loop over auth handlers, return a user obj or None """
-        name = kw.get('name')
-        password = kw.get('password')
-        login = kw.get('login')
-        logout = kw.get('logout')
-        user_obj = kw.get('user_obj')
-        for auth in self.cfg.auth:
-            user_obj, continue_flag = auth(self, name=name, password=password,
-                                           login=login, logout=logout, user_obj=user_obj)
-            if not continue_flag:
-                break
-        return user_obj
-        
-    def reset(self):
-        """ Reset request state.
-
-        Called after saving a page, before serving the updated
-        page. Solves some practical problems with request state
-        modified during saving.
-
-        """
-        # This is the content language and has nothing to do with
-        # The user interface language. The content language can change
-        # during the rendering of a page by lang macros
-        self.current_lang = self.cfg.language_default
-
-        self._all_pages = None
-        # caches unique ids
-        self._page_ids = {}
-        # keeps track of pagename/heading combinations
-        # parsers should use this dict and not a local one, so that
-        # macros like TableOfContents in combination with Include can work
-        self._page_headings = {}
-
-        if hasattr(self, "_fmt_hd_counters"):
-            del self._fmt_hd_counters
-
-    def loadTheme(self, theme_name):
-        """ Load the Theme to use for this request.
-
-        @param theme_name: the name of the theme
-        @type theme_name: str
-        @rtype: int
-        @return: success code
-                 0 on success
-                 1 if user theme could not be loaded,
-                 2 if a hard fallback to modern theme was required.
-        """
-        fallback = 0
-        if theme_name == "<default>":
-            theme_name = self.cfg.theme_default
-        
-        try:
-            Theme = wikiutil.importPlugin(self.cfg, 'theme', theme_name, 'Theme')
-        except wikiutil.PluginMissingError:
-            fallback = 1
-            try:
-                Theme = wikiutil.importPlugin(self.cfg, 'theme', self.cfg.theme_default, 'Theme')
-            except wikiutil.PluginMissingError:
-                fallback = 2
-                from MoinMoin.theme.modern import Theme
-        
-        self.theme = Theme(self)
-        return fallback
-
-    def setContentLanguage(self, lang):
-        """ Set the content language, used for the content div
-
-        Actions that generate content in the user language, like search,
-        should set the content direction to the user language before they
-        call send_title!
-        """
-        self.content_lang = lang
-        self.current_lang = lang
-
-    def getPragma(self, key, defval=None):
-        """ Query a pragma value (#pragma processing instruction)
-
-            Keys are not case-sensitive.
-        """
-        return self.pragma.get(key.lower(), defval)
-
-    def setPragma(self, key, value):
-        """ Set a pragma value (#pragma processing instruction)
-
-            Keys are not case-sensitive.
-        """
-        self.pragma[key.lower()] = value
-
-    def getPathinfo(self):
-        """ Return the remaining part of the URL. """
-        return self.path_info
-
-    def getScriptname(self):
-        """ Return the scriptname part of the URL ('/path/to/my.cgi'). """
-        if self.script_name == '/':
-            return ''
-        return self.script_name
-
-    def getPageNameFromQueryString(self):
-        """ Try to get pagename from the query string
-        
-        Support urls like http://netloc/script/?page_name. Allow
-        solving path_info encoding problems by calling with the page
-        name as a query.
-        """
-        pagename = wikiutil.url_unquote(self.query_string, want_unicode=False)
-        pagename = self.decodePagename(pagename)
-        pagename = self.normalizePagename(pagename)
-        return pagename
-    
-    def getKnownActions(self):
-        """ Create a dict of avaiable actions
-
-        Return cached version if avaiable.
-       
-        @rtype: dict
-        @return: dict of all known actions
-        """
-        try:
-            self.cfg._known_actions # check
-        except AttributeError:
-            from MoinMoin import wikiaction
-            # Add built in  actions from wikiaction
-            actions = [name[3:] for name in wikiaction.__dict__ if name.startswith('do_')]
-
-            # Add plugins           
-            dummy, plugins = wikiaction.getPlugins(self)
-            actions.extend(plugins)
-
-            # Add extensions
-            from MoinMoin.action import extension_actions
-            actions.extend(extension_actions)           
-           
-            # TODO: Use set when we require Python 2.3
-            actions = dict(zip(actions, [''] * len(actions)))            
-            self.cfg._known_actions = actions
-
-        # Return a copy, so clients will not change the dict.
-        return self.cfg._known_actions.copy()        
-
-    def getAvailableActions(self, page):
-        """ Get list of avaiable actions for this request
-
-        The dict does not contain actions that starts with lower case.
-        Themes use this dict to display the actions to the user.
-
-        @param page: current page, Page object
-        @rtype: dict
-        @return: dict of avaiable actions
-        """
-        if self._available_actions is None:
-            # Add actions for existing pages only, including deleted pages.
-            # Fix *OnNonExistingPage bugs.
-            if not (page.exists(includeDeleted=1) and self.user.may.read(page.page_name)):
-                return []
-
-            # Filter non ui actions (starts with lower case letter)
-            actions = self.getKnownActions()
-            for key in actions.keys():
-                if key[0].islower():
-                    del actions[key]
-
-            # Filter wiki excluded actions
-            for key in self.cfg.actions_excluded:
-                if key in actions:
-                    del actions[key]                
-
-            # Filter actions by page type, acl and user state
-            excluded = []
-            if ((page.isUnderlayPage() and not page.isStandardPage()) or
-                not self.user.may.write(page.page_name) or
-                not self.user.may.delete(page.page_name)):
-                # Prevent modification of underlay only pages, or pages
-                # the user can't write and can't delete
-                excluded = [u'RenamePage', u'DeletePage', ] # AttachFile must NOT be here!
-            for key in excluded:
-                if key in actions:
-                    del actions[key]                
-
-            self._available_actions = actions
-
-        # Return a copy, so clients will not change the dict.
-        return self._available_actions.copy()
-
-    def redirectedOutput(self, function, *args, **kw):
-        """ Redirect output during function, return redirected output """
-        buffer = StringIO.StringIO()
-        self.redirect(buffer)
-        try:
-            function(*args, **kw)
-        finally:
-            self.redirect()
-        text = buffer.getvalue()
-        buffer.close()        
-        return text
-
-    def redirect(self, file=None):
-        """ Redirect output to file, or restore saved output """
-        if file:
-            self.writestack.append(self.write)
-            self.write = file.write
-        else:
-            self.write = self.writestack.pop()
-
-    def reset_output(self):
-        """ restore default output method
-            destroy output stack
-            (useful for error messages)
-        """
-        if self.writestack:
-            self.write = self.writestack[0]
-            self.writestack = []
-
-    def log(self, msg):
-        """ Log to stderr, which may be error.log """
-        msg = msg.strip()
-        # Encode unicode msg
-        if isinstance(msg, unicode):
-            msg = msg.encode(config.charset)
-        # Add time stamp
-        msg = '[%s] %s\n' % (time.asctime(), msg)
-        sys.stderr.write(msg)
-    
-    def write(self, *data):
-        """ Write to output stream.
-        """
-        raise NotImplementedError
-
-    def encode(self, data):
-        """ encode data (can be both unicode strings and strings),
-            preparing for a single write()
-        """
-        wd = []
-        for d in data:
-            try:
-                if isinstance(d, unicode):
-                    # if we are REALLY sure, we can use "strict"
-                    d = d.encode(config.charset, 'replace') 
-                wd.append(d)
-            except UnicodeError:
-                print >>sys.stderr, "Unicode error on: %s" % repr(d)
-        return ''.join(wd)
-    
-    def decodePagename(self, name):
-        """ Decode path, possibly using non ascii characters
-
-        Does not change the name, only decode to Unicode.
-
-        First split the path to pages, then decode each one. This enables
-        us to decode one page using config.charset and another using
-        utf-8. This situation happens when you try to add to a name of
-        an existing page.
-
-        See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.1
-        
-        @param name: page name, string
-        @rtype: unicode
-        @return decoded page name
-        """
-        # Split to pages and decode each one
-        pages = name.split('/')
-        decoded = []
-        for page in pages:
-            # Recode from utf-8 into config charset. If the path
-            # contains user typed parts, they are encoded using 'utf-8'.
-            if config.charset != 'utf-8':
-                try:
-                    page = unicode(page, 'utf-8', 'strict')
-                    # Fit data into config.charset, replacing what won't
-                    # fit. Better have few "?" in the name than crash.
-                    page = page.encode(config.charset, 'replace')
-                except UnicodeError:
-                    pass
-                
-            # Decode from config.charset, replacing what can't be decoded.
-            page = unicode(page, config.charset, 'replace')
-            decoded.append(page)
-
-        # Assemble decoded parts
-        name = u'/'.join(decoded)
-        return name
-
-    def normalizePagename(self, name):
-        """ Normalize page name 
-
-        Convert '_' to spaces - allows using nice URLs with spaces, with no
-        need to quote.
-
-        Prevent creating page names with invisible characters or funny
-        whitespace that might confuse the users or abuse the wiki, or
-        just does not make sense.
-
-        Restrict even more group pages, so they can be used inside acl lines.
-        
-        @param name: page name, unicode
-        @rtype: unicode
-        @return: decoded and sanitized page name
-        """
-        # Replace underscores with spaces
-        name = name.replace(u'_', u' ')
-
-        # Strip invalid characters
-        name = config.page_invalid_chars_regex.sub(u'', name)
-
-        # Split to pages and normalize each one
-        pages = name.split(u'/')
-        normalized = []
-        for page in pages:            
-            # Ignore empty or whitespace only pages
-            if not page or page.isspace():
-                continue
-
-            # Cleanup group pages.
-            # Strip non alpha numeric characters, keep white space
-            if wikiutil.isGroupPage(self, page):
-                page = u''.join([c for c in page
-                                 if c.isalnum() or c.isspace()])
-
-            # Normalize white space. Each name can contain multiple 
-            # words separated with only one space. Split handle all
-            # 30 unicode spaces (isspace() == True)
-            page = u' '.join(page.split())
-            
-            normalized.append(page)            
-        
-        # Assemble components into full pagename
-        name = u'/'.join(normalized)
-        return name
-        
-    def read(self, n):
-        """ Read n bytes from input stream.
-        """
-        raise NotImplementedError
-
-    def flush(self):
-        """ Flush output stream.
-        """
-        raise NotImplementedError
-
-    def check_spider(self):
-        """ check if the user agent for current request is a spider/bot """
-        isSpider = False
-        spiders = self.cfg.ua_spiders
-        if spiders:
-            ua = self.getUserAgent()
-            if ua:
-                isSpider = re.search(spiders, ua, re.I) is not None
-        return isSpider
-
-    def isForbidden(self):
-        """ check for web spiders and refuse anything except viewing """
-        forbidden = 0
-        # we do not have a parsed query string here, so we can just do simple matching
-        qs = self.query_string
-        if ((qs != '' or self.request_method != 'GET') and
-            not 'action=rss_rc' in qs and
-            # allow spiders to get attachments and do 'show'
-            not ('action=AttachFile' in qs and 'do=get' in qs) and
-            not 'action=show' in qs
-            ):
-            forbidden = self.isSpiderAgent
-
-        if not forbidden and self.cfg.hosts_deny:
-            ip = self.remote_addr
-            for host in self.cfg.hosts_deny:
-                if host[-1] == '.' and ip.startswith(host):
-                    forbidden = 1
-                    #self.log("hosts_deny (net): %s" % str(forbidden))
-                    break
-                if ip == host:
-                    forbidden = 1
-                    #self.log("hosts_deny (ip): %s" % str(forbidden))
-                    break
-        return forbidden
-
-    def setup_args(self, form=None):
-        """ Return args dict 
-        
-        In POST request, invoke _setup_args_from_cgi_form to handle possible
-        file uploads. For other request simply parse the query string.
-        
-        Warning: calling with a form might fail, depending on the type of the
-        request! Only the request know which kind of form it can handle.
-        
-        TODO: The form argument should be removed in 1.5.
-        """
-        if form is not None or self.request_method == 'POST':
-            return self._setup_args_from_cgi_form(form)
-        args = cgi.parse_qs(self.query_string, keep_blank_values=1)
-        return self.decodeArgs(args)
-
-    def _setup_args_from_cgi_form(self, form=None):
-        """ Return args dict from a FieldStorage
-        
-        Create the args from a standard cgi.FieldStorage or from given form.
-        Each key contain a list of values.
-
-        @param form: a cgi.FieldStorage
-        @rtype: dict
-        @return: dict with form keys, each contains a list of values
-        """
-        if form is None:
-            form = cgi.FieldStorage()
-
-        args = {}
-        for key in form:
-            values = form[key]
-            if not isinstance(values, list):
-                values = [values]
-            fixedResult = []
-            for item in values:
-                fixedResult.append(item.value)
-                if isinstance(item, cgi.FieldStorage) and item.filename:
-                    # Save upload file name in a separate key
-                    args[key + '__filename__'] = item.filename            
-            args[key] = fixedResult
-            
-        return self.decodeArgs(args)
-
-    def decodeArgs(self, args):
-        """ Decode args dict 
-        
-        Decoding is done in a separate path because it is reused by
-        other methods and sub classes.
-        """
-        decode = wikiutil.decodeUserInput
-        result = {}
-        for key in args:
-            if key + '__filename__' in args:
-                # Copy file data as is
-                result[key] = args[key]
-            elif key.endswith('__filename__'):
-                result[key] = decode(args[key], self.decode_charsets)
-            else:
-                result[key] = [decode(value, self.decode_charsets) for value in args[key]]
-        return result
-
-    def getBaseURL(self):
-        """ Return a fully qualified URL to this script. """
-        return self.getQualifiedURL(self.getScriptname())
-
-    def getQualifiedURL(self, uri=''):
-        """ Return an absolute URL starting with schema and host.
-
-        Already qualified urls are returned unchanged.
-
-        @param uri: server rooted uri e.g /scriptname/pagename.
-                    It must start with a slash. Must be ascii and url encoded.
-        """
-        import urlparse
-        scheme = urlparse.urlparse(uri)[0]
-        if scheme:
-            return uri
-
-        scheme = ('http', 'https')[self.is_ssl]
-        result = "%s://%s%s" % (scheme, self.http_host, uri)
-
-        # This might break qualified urls in redirects!
-        # e.g. mapping 'http://netloc' -> '/'
-        return wikiutil.mapURL(self, result)
-
-    def getUserAgent(self):
-        """ Get the user agent. """
-        return self.http_user_agent
-
-    def makeForbidden(self, resultcode, msg):
-        statusmsg = {
-            403: 'FORBIDDEN',
-            503: 'Service unavailable',
-        }
-        self.http_headers([
-            'Status: %d %s' % (resultcode, statusmsg[resultcode]),
-            'Content-Type: text/plain'
-        ])
-        self.write(msg)
-        self.setResponseCode(resultcode)
-        self.forbidden = True
-
-    def makeForbidden403(self):
-        self.makeForbidden(403, 'You are not allowed to access this!\r\n')
-
-    def makeUnavailable503(self):
-        self.makeForbidden(503, "Warning:\r\n"
-                   "You triggered the wiki's surge protection by doing too many requests in a short time.\r\n"
-                   "Please make a short break reading the stuff you already got.\r\n"
-                   "When you restart doing requests AFTER that, slow down or you might get locked out for a longer time!\r\n")
-
-    def initTheme(self):
-        """ Set theme - forced theme, user theme or wiki default """
-        if self.cfg.theme_force:
-            theme_name = self.cfg.theme_default
-        else:
-            theme_name = self.user.theme_name
-        self.loadTheme(theme_name)
-        
-    def run(self):
-        # Exit now if __init__ failed or request is forbidden
-        if self.failed or self.forbidden:
-            # Don't sleep() here, it binds too much of our resources!
-            return self.finish()
-
-        self.open_logs()
-        _ = self.getText
-        self.clock.start('run')
-
-        from MoinMoin.Page import Page
-
-        if self.query_string == 'action=xmlrpc':
-            from MoinMoin.wikirpc import xmlrpc
-            xmlrpc(self)
-            return self.finish()
-        
-        if self.query_string == 'action=xmlrpc2':
-            from MoinMoin.wikirpc import xmlrpc2
-            xmlrpc2(self)
-            return self.finish()
-
-        # parse request data
-        try:
-            self.initTheme()
-            
-            action = self.form.get('action', [None])[0]
-
-            # The last component in path_info is the page name, if any
-            path = self.getPathinfo()
-            if path.startswith('/'):
-                pagename = self.normalizePagename(path)
-            else:
-                pagename = None
-
-            # Handle request. We have these options:
-            
-            # 1. If user has a bad user name, delete its bad cookie and
-            # send him to UserPreferences to make a new account.
-            if not user.isValidName(self, self.user.name):
-                msg = _("""Invalid user name {{{'%s'}}}.
-Name may contain any Unicode alpha numeric character, with optional one
-space between words. Group page name is not allowed.""") % self.user.name
-                self.user = self.get_user_default_unknown(name=self.user.name, logout=True)
-                page = wikiutil.getSysPage(self, 'UserPreferences')
-                page.send_page(self, msg=msg)
-
-            # 2. Or jump to page where user left off
-            elif not pagename and not action and self.user.remember_last_visit:
-                pagetrail = self.user.getTrail()
-                if pagetrail:
-                    # Redirect to last page visited
-                    if ":" in pagetrail[-1]:
-                        wikitag, wikiurl, wikitail, error = wikiutil.resolve_wiki(self, pagetrail[-1]) 
-                        url = wikiurl + wikiutil.quoteWikinameURL(wikitail)
-                    else:
-                        url = Page(self, pagetrail[-1]).url(self)
-                else:
-                    # Or to localized FrontPage
-                    url = wikiutil.getFrontPage(self).url(self)
-                self.http_redirect(url)
-                return self.finish()
-            
-            # 3. Or save drawing
-            elif self.form.has_key('filepath') and self.form.has_key('noredirect'):
-                # looks like user wants to save a drawing
-                from MoinMoin.action.AttachFile import execute
-                # TODO: what if pagename is None?
-                execute(pagename, self)
-                raise MoinMoinNoFooter           
-
-            # 4. Or handle action
-            else:
-                if action is None:
-                    action = 'show'
-                if not pagename and self.query_string:
-                    pagename = self.getPageNameFromQueryString()
-                # pagename could be empty after normalization e.g. '///' -> ''
-                # Use localized FrontPage if pagename is empty
-                if not pagename:
-                    self.page = wikiutil.getFrontPage(self)
-                else:
-                    self.page = Page(self, pagename)
-
-                # Complain about unknown actions
-                if not action in self.getKnownActions():
-                    self.http_headers()
-                    self.write(u'<html><body><h1>Unknown action %s</h1></body>' % wikiutil.escape(action))
-
-                # Disallow non available actions
-                elif action[0].isupper() and not action in self.getAvailableActions(self.page):
-                    # Send page with error
-                    msg = _("You are not allowed to do %s on this page.") % wikiutil.escape(action)
-                    if not self.user.valid:
-                        # Suggest non valid user to login
-                        msg += " " + _("Login and try again.", formatted=0)
-                    self.page.send_page(self, msg=msg)
-
-                # Try action
-                else:
-                    from MoinMoin.wikiaction import getHandler
-                    handler = getHandler(self, action)
-                    handler(self.page.page_name, self)
-
-            # generate page footer (actions that do not want this footer use
-            # raise util.MoinMoinNoFooter to break out of the default execution
-            # path, see the "except MoinMoinNoFooter" below)
-
-            self.clock.stop('run')
-            self.clock.stop('total')
-
-            # Close html code
-            if not self.no_closing_html_code:
-                if (self.cfg.show_timings and
-                    self.form.get('action', [None])[0] != 'print'):
-                    self.write('<ul id="timings">\n')
-                    for t in self.clock.dump():
-                        self.write('<li>%s</li>\n' % t)
-                    self.write('</ul>\n')
-                #self.write('<!-- auth_method == %s -->' % repr(self.user.auth_method))
-                self.write('</body>\n</html>\n\n')
-            
-        except MoinMoinNoFooter:
-            pass
-        except Exception, err:
-            self.fail(err)
-
-        return self.finish()
-
-    def http_redirect(self, url):
-        """ Redirect to a fully qualified, or server-rooted URL
-        
-        @param url: relative or absolute url, ascii using url encoding.
-        """
-        url = self.getQualifiedURL(url)
-        self.http_headers(["Status: 302 Found", "Location: %s" % url])
-
-    def setHttpHeader(self, header):
-        """ Save header for later send. """
-        self.user_headers.append(header)
-
-    def setResponseCode(self, code, message=None):
-        pass
-
-    def fail(self, err):
-        """ Fail when we can't continue
-
-        Send 500 status code with the error name. Reference: 
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1
-
-        Log the error, then let failure module handle it. 
-
-        @param err: Exception instance or subclass.
-        """
-        self.failed = 1 # save state for self.run()            
-        self.http_headers(['Status: 500 MoinMoin Internal Error'])
-        self.setResponseCode(500)
-        self.log('%s: %s' % (err.__class__.__name__, str(err)))        
-        from MoinMoin import failure
-        failure.handle(self)             
-
-    def open_logs(self):
-        pass
-
-    def makeUniqueID(self, base):
-        """
-        Generates a unique ID using a given base name. Appends a running count to the base.
-
-        @param base: the base of the id
-        @type base: unicode
-
-        @returns: an unique id
-        @rtype: unicode
-        """
-        if not isinstance(base, unicode):
-            base = unicode(str(base), 'ascii', 'ignore')
-        count = self._page_ids.get(base, -1) + 1
-        self._page_ids[base] = count
-        if count == 0:
-            return base
-        return u'%s_%04d' % (base, count)
-
-    def httpDate(self, when=None, rfc='1123'):
-        """ Returns http date string, according to rfc2068
-
-        See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-3.3
-
-        A http 1.1 server should use only rfc1123 date, but cookie's
-        "expires" field should use the older obsolete rfc850 date.
-
-        Note: we can not use strftime() because that honors the locale
-        and rfc2822 requires english day and month names.
-
-        We can not use email.Utils.formatdate because it formats the
-        zone as '-0000' instead of 'GMT', and creates only rfc1123
-        dates. This is a modified version of email.Utils.formatdate
-        from Python 2.4.
-
-        @param when: seconds from epoch, as returned by time.time()
-        @param rfc: conform to rfc ('1123' or '850')
-        @rtype: string
-        @return: http date conforming to rfc1123 or rfc850
-        """
-        if when is None:
-            when = time.time()
-        now = time.gmtime(when)
-        month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
-                 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now.tm_mon - 1]
-        if rfc == '1123':
-            day = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][now.tm_wday]
-            date = '%02d %s %04d' % (now.tm_mday, month, now.tm_year)
-        elif rfc == '850':
-            day = ["Monday", "Tuesday", "Wednesday", "Thursday",
-                    "Friday", "Saturday", "Sunday"][now.tm_wday]
-            date = '%02d-%s-%s' % (now.tm_mday, month, str(now.tm_year)[-2:])
-        else:
-            raise ValueError("Invalid rfc value: %s" % rfc)
-        
-        return '%s, %s %02d:%02d:%02d GMT' % (day, date, now.tm_hour,
-                                              now.tm_min, now.tm_sec)
-    
-    def disableHttpCaching(self):
-        """ Prevent caching of pages that should not be cached
-
-        This is important to prevent caches break acl by providing one
-        user pages meant to be seen only by another user, when both users
-        share the same caching proxy.
-        """
-        # Run only once
-        if hasattr(self, 'http_caching_disabled'):
-            return
-        self.http_caching_disabled = 1
-
-        # Set Cache control header for http 1.1 caches
-        # See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2109.html#sec-4.2.3
-        # and http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-14.9
-        self.setHttpHeader('Cache-Control: no-cache="set-cookie"')
-        self.setHttpHeader('Cache-Control: private')
-        self.setHttpHeader('Cache-Control: max-age=0')       
-
-        # Set Expires for http 1.0 caches (does not support Cache-Control)
-        yearago = time.time() - (3600 * 24 * 365)
-        self.setHttpHeader('Expires: %s' % self.httpDate(when=yearago))
-
-        # Set Pragma for http 1.0 caches
-        # See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-14.32
-        self.setHttpHeader('Pragma: no-cache')
-
-    def finish(self):
-        """ General cleanup on end of request
-        
-        Delete circular references - all object that we create using self.name = class(self).
-        This helps Python to collect these objects and keep our memory footprint lower.
-        """
-        try:
-            del self.user
-            del self.theme
-            del self.dicts
-        except:
-            pass
-
-    # Debug ------------------------------------------------------------
-
-    def debugEnvironment(self, env):
-        """ Environment debugging aid """
-        # Keep this one name per line so its easy to comment stuff
-        names = [
-#             'http_accept_language',
-#             'http_host',
-#             'http_referer',
-#             'http_user_agent',
-#             'is_ssl',
-            'path_info',
-            'query_string',
-#             'remote_addr',
-            'request_method',
-#             'request_uri',
-#             'saved_cookie',
-            'script_name',
-#             'server_name',
-#             'server_port',
-            ]
-        names.sort()
-        attributes = []
-        for name in names:
-            attributes.append('  %s = %r\n' % (name, getattr(self, name, None)))
-        attributes = ''.join(attributes)
-        
-        environment = []
-        names = env.keys()
-        names.sort()
-        for key in names:
-            environment.append('  %s = %r\n' % (key, env[key]))
-        environment = ''.join(environment)
-        
-        data = '\nRequest Attributes\n%s\nEnviroment\n%s' % (attributes, environment)        
-        f = open('/tmp/env.log', 'a')
-        try:
-            f.write(data)
-        finally:
-            f.close()
-  
-
-# CGI ---------------------------------------------------------------
-
-class RequestCGI(RequestBase):
-    """ specialized on CGI requests """
-
-    def __init__(self, properties={}):
-        try:
-            # force input/output to binary
-            if sys.platform == "win32":
-                import msvcrt
-                msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
-                msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
-
-            self._setup_vars_from_std_env(os.environ)
-            RequestBase.__init__(self, properties)
-
-        except Exception, err:
-            self.fail(err)
-            
-    def open_logs(self):
-        # create log file for catching stderr output
-        if not self.opened_logs:
-            sys.stderr = open(os.path.join(self.cfg.data_dir, 'error.log'), 'at')
-            self.opened_logs = 1
-
-    def read(self, n=None):
-        """ Read from input stream. """
-        if n is None:
-            return sys.stdin.read()
-        else:
-            return sys.stdin.read(n)
-
-    def write(self, *data):
-        """ Write to output stream. """
-        sys.stdout.write(self.encode(data))
-
-    def flush(self):
-        sys.stdout.flush()
-        
-    def finish(self):
-        RequestBase.finish(self)
-        # flush the output, ignore errors caused by the user closing the socket
-        try:
-            sys.stdout.flush()
-        except IOError, ex:
-            import errno
-            if ex.errno != errno.EPIPE: raise
-
-    # Headers ----------------------------------------------------------
-    
-    def http_headers(self, more_headers=[]):
-        # Send only once
-        if getattr(self, 'sent_headers', None):
-            return
-        
-        self.sent_headers = 1
-        have_ct = 0
-
-        # send http headers
-        for header in more_headers + getattr(self, 'user_headers', []):
-            if header.lower().startswith("content-type:"):
-                # don't send content-type multiple times!
-                if have_ct: continue
-                have_ct = 1
-            if type(header) is unicode:
-                header = header.encode('ascii')
-            self.write("%s\r\n" % header)
-
-        if not have_ct:
-            self.write("Content-type: text/html;charset=%s\r\n" % config.charset)
-
-        self.write('\r\n')
-
-        #from pprint import pformat
-        #sys.stderr.write(pformat(more_headers))
-        #sys.stderr.write(pformat(self.user_headers))
-
-
-# Twisted -----------------------------------------------------------
-
-class RequestTwisted(RequestBase):
-    """ specialized on Twisted requests """
-
-    def __init__(self, twistedRequest, pagename, reactor, properties={}):
-        try:
-            self.twistd = twistedRequest
-            self.reactor = reactor
-            
-            # Copy headers
-            self.http_accept_language = self.twistd.getHeader('Accept-Language')
-            self.saved_cookie = self.twistd.getHeader('Cookie')
-            self.http_user_agent = self.twistd.getHeader('User-Agent')
-            
-            # Copy values from twisted request
-            self.server_protocol = self.twistd.clientproto
-            self.server_name = self.twistd.getRequestHostname().split(':')[0]
-            self.server_port = str(self.twistd.getHost()[2])
-            self.is_ssl = self.twistd.isSecure()
-            self.path_info = '/' + '/'.join([pagename] + self.twistd.postpath)
-            self.request_method = self.twistd.method
-            self.remote_host = self.twistd.getClient()
-            self.remote_addr = self.twistd.getClientIP()
-            self.request_uri = self.twistd.uri
-            self.script_name = "/" + '/'.join(self.twistd.prepath[:-1])
-
-            # Values that need more work
-            self.query_string = self.splitURI(self.twistd.uri)[1]
-            self.setHttpReferer(self.twistd.getHeader('Referer'))
-            self.setHost()
-            self.setURL(self.twistd.getAllHeaders())
-
-            ##self.debugEnvironment(twistedRequest.getAllHeaders())
-            
-            RequestBase.__init__(self, properties)
-
-        except MoinMoinNoFooter: # might be triggered by http_redirect
-            self.http_headers() # send headers (important for sending MOIN_ID cookie)
-            self.finish()
-
-        except Exception, err:
-            # Wrap err inside an internal error if needed
-            from MoinMoin import error
-            if isinstance(err, error.FatalError):
-                self.delayedError = err
-            else:
-                self.delayedError = error.InternalError(str(err))
-
-    def run(self):
-        """ Handle delayed errors then invoke base class run """
-        if hasattr(self, 'delayedError'):
-            self.fail(self.delayedError)
-            return self.finish()
-        RequestBase.run(self)
-            
-    def setup_args(self, form=None):
-        """ Return args dict 
-        
-        Twisted already parsed args, including __filename__ hacking,
-        but did not decoded the values.
-        """
-        return self.decodeArgs(self.twistd.args)
-        
-    def read(self, n=None):
-        """ Read from input stream. """
-        # XXX why is that wrong?:
-        #rd = self.reactor.callFromThread(self.twistd.read)
-        
-        # XXX do we need self.reactor.callFromThread with that?
-        # XXX if yes, why doesn't it work?
-        self.twistd.content.seek(0, 0)
-        if n is None:
-            rd = self.twistd.content.read()
-        else:
-            rd = self.twistd.content.read(n)
-        #print "request.RequestTwisted.read: data=\n" + str(rd)
-        return rd
-    
-    def write(self, *data):
-        """ Write to output stream. """
-        #print "request.RequestTwisted.write: data=\n" + wd
-        self.reactor.callFromThread(self.twistd.write, self.encode(data))
-
-    def flush(self):
-        pass # XXX is there a flush in twisted?
-
-    def finish(self):
-        RequestBase.finish(self)
-        self.reactor.callFromThread(self.twistd.finish)
-
-    def open_logs(self):
-        return
-        # create log file for catching stderr output
-        if not self.opened_logs:
-            sys.stderr = open(os.path.join(self.cfg.data_dir, 'error.log'), 'at')
-            self.opened_logs = 1
-
-    # Headers ----------------------------------------------------------
-
-    def __setHttpHeader(self, header):
-        if type(header) is unicode:
-            header = header.encode('ascii')
-        key, value = header.split(':', 1)
-        value = value.lstrip()
-        if key.lower() == 'set-cookie':
-            key, value = value.split('=', 1)
-            self.twistd.addCookie(key, value)
-        else:
-            self.twistd.setHeader(key, value)
-        #print "request.RequestTwisted.setHttpHeader: %s" % header
-
-    def http_headers(self, more_headers=[]):
-        if getattr(self, 'sent_headers', None):
-            return
-        self.sent_headers = 1
-        have_ct = 0
-
-        # set http headers
-        for header in more_headers + getattr(self, 'user_headers', []):
-            if header.lower().startswith("content-type:"):
-                # don't send content-type multiple times!
-                if have_ct: continue
-                have_ct = 1
-            self.__setHttpHeader(header)
-
-        if not have_ct:
-            self.__setHttpHeader("Content-type: text/html;charset=%s" % config.charset)
-
-    def http_redirect(self, url):
-        """ Redirect to a fully qualified, or server-rooted URL 
-        
-        @param url: relative or absolute url, ascii using url encoding.
-        """
-        url = self.getQualifiedURL(url)
-        self.twistd.redirect(url)
-        # calling finish here will send the rest of the data to the next
-        # request. leave the finish call to run()
-        #self.twistd.finish()
-        raise MoinMoinNoFooter
-
-    def setResponseCode(self, code, message=None):
-        self.twistd.setResponseCode(code, message)
-        
-# CLI ------------------------------------------------------------------
-
-class RequestCLI(RequestBase):
-    """ specialized on command line interface and script requests """
-
-    def __init__(self, url='CLI', pagename='', properties={}):
-        self.saved_cookie = ''
-        self.path_info = '/' + pagename
-        self.query_string = ''
-        self.remote_addr = '127.0.0.1'
-        self.is_ssl = 0
-        self.http_user_agent = 'CLI/Script'
-        self.url = url
-        self.request_method = 'GET'
-        self.request_uri = '/' + pagename # TODO check
-        self.http_host = 'localhost'
-        self.http_referer = ''
-        self.script_name = '.'
-        RequestBase.__init__(self, properties)
-        self.cfg.caching_formats = [] # don't spoil the cache
-        self.initTheme() # usually request.run() does this, but we don't use it
-  
-    def read(self, n=None):
-        """ Read from input stream. """
-        if n is None:
-            return sys.stdin.read()
-        else:
-            return sys.stdin.read(n)
-
-    def write(self, *data):
-        """ Write to output stream. """
-        sys.stdout.write(self.encode(data))
-
-    def flush(self):
-        sys.stdout.flush()
-        
-    def finish(self):
-        RequestBase.finish(self)
-        # flush the output, ignore errors caused by the user closing the socket
-        try:
-            sys.stdout.flush()
-        except IOError, ex:
-            import errno
-            if ex.errno != errno.EPIPE: raise
-
-    def isForbidden(self):
-        """ Nothing is forbidden """
-        return 0
-
-    # Accessors --------------------------------------------------------
-
-    def getQualifiedURL(self, uri=None):
-        """ Return a full URL starting with schema and host
-        
-        TODO: does this create correct pages when you render wiki pages
-              within a cli request?!
-        """
-        return uri
-
-    # Headers ----------------------------------------------------------
-
-    def setHttpHeader(self, header):
-        pass
-
-    def http_headers(self, more_headers=[]):
-        pass
-
-    def http_redirect(self, url):
-        """ Redirect to a fully qualified, or server-rooted URL 
-        
-        TODO: Does this work for rendering redirect pages?
-        """
-        raise Exception("Redirect not supported for command line tools!")
-
-
-# StandAlone Server ----------------------------------------------------
-
-class RequestStandAlone(RequestBase):
-    """ specialized on StandAlone Server (MoinMoin.server.standalone) requests """
-    script_name = ''
-    
-    def __init__(self, sa, properties={}):
-        """
-        @param sa: stand alone server object
-        @param properties: ...
-        """
-        try:
-            self.sareq = sa
-            self.wfile = sa.wfile
-            self.rfile = sa.rfile
-            self.headers = sa.headers
-            self.is_ssl = 0
-            
-            # Copy headers
-            self.http_accept_language = (sa.headers.getheader('accept-language') 
-                                         or self.http_accept_language)
-            self.http_user_agent = sa.headers.getheader('user-agent', '')            
-            co = filter(None, sa.headers.getheaders('cookie'))
-            self.saved_cookie = ', '.join(co) or ''
-            
-            # Copy rest from standalone request   
-            self.server_name = sa.server.server_name
-            self.server_port = str(sa.server.server_port)
-            self.request_method = sa.command
-            self.request_uri = sa.path
-            self.remote_addr = sa.client_address[0]
-
-            # Values that need more work                        
-            self.path_info, self.query_string = self.splitURI(sa.path)
-            self.setHttpReferer(sa.headers.getheader('referer'))
-            self.setHost(sa.headers.getheader('host'))
-            self.setURL(sa.headers)
-
-            ##self.debugEnvironment(sa.headers)
-            
-            RequestBase.__init__(self, properties)
-
-        except Exception, err:
-            self.fail(err)
-
-    def _setup_args_from_cgi_form(self, form=None):
-        """ Override to create standlone form """
-        form = cgi.FieldStorage(self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST'})
-        return RequestBase._setup_args_from_cgi_form(self, form)
-        
-    def read(self, n=None):
-        """ Read from input stream
-        
-        Since self.rfile.read() will block, content-length will be used instead.
-        
-        TODO: test with n > content length, or when calling several times
-        with smaller n but total over content length.
-        """
-        if n is None:
-            try:
-                n = int(self.headers.get('content-length'))
-            except (TypeError, ValueError):
-                import warnings
-                warnings.warn("calling request.read() when content-length is "
-                              "not available will block")
-                return self.rfile.read()
-        return self.rfile.read(n)
-
-    def write(self, *data):
-        """ Write to output stream. """
-        self.wfile.write(self.encode(data))
-
-    def flush(self):
-        self.wfile.flush()
-        
-    def finish(self):
-        RequestBase.finish(self)
-        self.wfile.flush()
-
-    # Headers ----------------------------------------------------------
-
-    def http_headers(self, more_headers=[]):
-        if getattr(self, 'sent_headers', None):
-            return
-        
-        self.sent_headers = 1
-        user_headers = getattr(self, 'user_headers', [])
-        
-        # check for status header and send it
-        our_status = 200
-        for header in more_headers + user_headers:
-            if header.lower().startswith("status:"):
-                try:
-                    our_status = int(header.split(':', 1)[1].strip().split(" ", 1)[0]) 
-                except:
-                    pass
-                # there should be only one!
-                break
-        # send response
-        self.sareq.send_response(our_status)
-
-        # send http headers
-        have_ct = 0
-        for header in more_headers + user_headers:
-            if type(header) is unicode:
-                header = header.encode('ascii')
-            if header.lower().startswith("content-type:"):
-                # don't send content-type multiple times!
-                if have_ct: continue
-                have_ct = 1
-
-            self.write("%s\r\n" % header)
-
-        if not have_ct:
-            self.write("Content-type: text/html;charset=%s\r\n" % config.charset)
-
-        self.write('\r\n')
-
-        #from pprint import pformat
-        #sys.stderr.write(pformat(more_headers))
-        #sys.stderr.write(pformat(self.user_headers))
-
-# mod_python/Apache ----------------------------------------------------
-
-class RequestModPy(RequestBase):
-    """ specialized on mod_python requests """
-
-    def __init__(self, req):
-        """ Saves mod_pythons request and sets basic variables using
-            the req.subprocess_env, cause this provides a standard
-            way to access the values we need here.
-
-            @param req: the mod_python request instance
-        """
-        try:
-            # flags if headers sent out contained content-type or status
-            self._have_ct = 0
-            self._have_status = 0
-
-            req.add_common_vars()
-            self.mpyreq = req
-            # some mod_python 2.7.X has no get method for table objects,
-            # so we make a real dict out of it first.
-            if not hasattr(req.subprocess_env, 'get'):
-                env=dict(req.subprocess_env)
-            else:
-                env=req.subprocess_env
-            self._setup_vars_from_std_env(env)
-            RequestBase.__init__(self)
-
-        except Exception, err:
-            self.fail(err)
-            
-    def fixURI(self, env):
-        """ Fix problems with script_name and path_info using
-        PythonOption directive to rewrite URI.
-        
-        This is needed when using Apache 1 or other server which does
-        not support adding custom headers per request. With mod_python we
-        can use the PythonOption directive:
-        
-            <Location /url/to/mywiki/>
-                PythonOption X-Moin-Location /url/to/mywiki/
-            </location>
-
-        Note that *neither* script_name *nor* path_info can be trusted
-        when Moin is invoked as a mod_python handler with apache1, so we
-        must build both using request_uri and the provided PythonOption.
-        """
-        # Be compatible with release 1.3.5 "Location" option 
-        # TODO: Remove in later release, we should have one option only.
-        old_location = 'Location'
-        options_table = self.mpyreq.get_options()
-        if not hasattr(options_table, 'get'):
-            options = dict(options_table)
-        else:
-            options = options_table
-        location = options.get(self.moin_location) or options.get(old_location)
-        if location:
-            env[self.moin_location] = location
-            # Try to recreate script_name and path_info from request_uri.
-            import urlparse
-            scriptAndPath = urlparse.urlparse(self.request_uri)[2]
-            self.script_name = location.rstrip('/')
-            path = scriptAndPath.replace(self.script_name, '', 1)            
-            self.path_info = wikiutil.url_unquote(path, want_unicode=False)
-
-        RequestBase.fixURI(self, env)
-
-    def _setup_args_from_cgi_form(self, form=None):
-        """ Override to use mod_python.util.FieldStorage 
-        
-        Its little different from cgi.FieldStorage, so we need to
-        duplicate the conversion code.
-        """
-        from mod_python import util
-        if form is None:
-            form = util.FieldStorage(self.mpyreq)
-
-        args = {}
-        for key in form.keys():
-            values = form[key]
-            if not isinstance(values, list):
-                values = [values]
-            fixedResult = []
-
-            for item in values:
-                # Remember filenames with a name hack
-                if hasattr(item, 'filename') and item.filename:
-                    args[key + '__filename__'] = item.filename
-                # mod_python 2.7 might return strings instead of Field
-                # objects.
-                if hasattr(item, 'value'):
-                    item = item.value
-                fixedResult.append(item)                
-            args[key] = fixedResult
-            
-        return self.decodeArgs(args)
-
-    def run(self, req):
-        """ mod_python calls this with its request object. We don't
-            need it cause its already passed to __init__. So ignore
-            it and just return RequestBase.run.
-
-            @param req: the mod_python request instance
-        """
-        return RequestBase.run(self)
-
-    def read(self, n=None):
-        """ Read from input stream. """
-        if n is None:
-            return self.mpyreq.read()
-        else:
-            return self.mpyreq.read(n)
-
-    def write(self, *data):
-        """ Write to output stream. """
-        self.mpyreq.write(self.encode(data))
-
-    def flush(self):
-        """ We can't flush it, so do nothing. """
-        pass
-        
-    def finish(self):
-        """ Just return apache.OK. Status is set in req.status. """
-        RequestBase.finish(self)
-        # is it possible that we need to return something else here?
-        from mod_python import apache
-        return apache.OK
-
-    # Headers ----------------------------------------------------------
-
-    def setHttpHeader(self, header):
-        """ Filters out content-type and status to set them directly
-            in the mod_python request. Rest is put into the headers_out
-            member of the mod_python request.
-
-            @param header: string, containing valid HTTP header.
-        """
-        if type(header) is unicode:
-            header = header.encode('ascii')
-        key, value = header.split(':', 1)
-        value = value.lstrip()
-        if key.lower() == 'content-type':
-            # save content-type for http_headers
-            if not self._have_ct:
-                # we only use the first content-type!
-                self.mpyreq.content_type = value
-                self._have_ct = 1
-        elif key.lower() == 'status':
-            # save status for finish
-            try:
-                self.mpyreq.status = int(value.split(' ', 1)[0])
-            except:
-                pass
-            else:
-                self._have_status = 1
-        else:
-            # this is a header we sent out
-            self.mpyreq.headers_out[key]=value
-
-    def http_headers(self, more_headers=[]):
-        """ Sends out headers and possibly sets default content-type
-            and status.
-
-            @param more_headers: list of strings, defaults to []
-        """
-        for header in more_headers + getattr(self, 'user_headers', []):
-            self.setHttpHeader(header)
-        # if we don't had an content-type header, set text/html
-        if self._have_ct == 0:
-            self.mpyreq.content_type = "text/html;charset=%s" % config.charset
-        # if we don't had a status header, set 200
-        if self._have_status == 0:
-            self.mpyreq.status = 200
-        # this is for mod_python 2.7.X, for 3.X it's a NOP
-        self.mpyreq.send_http_header()
-
-# FastCGI -----------------------------------------------------------
-
-class RequestFastCGI(RequestBase):
-    """ specialized on FastCGI requests """
-
-    def __init__(self, fcgRequest, env, form, properties={}):
-        """ Initializes variables from FastCGI environment and saves
-            FastCGI request and form for further use.
-
-            @param fcgRequest: the FastCGI request instance.
-            @param env: environment passed by FastCGI.
-            @param form: FieldStorage passed by FastCGI.
-        """
-        try:
-            self.fcgreq = fcgRequest
-            self.fcgenv = env
-            self.fcgform = form
-            self._setup_vars_from_std_env(env)
-            RequestBase.__init__(self, properties)
-
-        except Exception, err:
-            self.fail(err)
-
-    def _setup_args_from_cgi_form(self, form=None):
-        """ Override to use FastCGI form """
-        if form is None:
-            form = self.fcgform
-        return RequestBase._setup_args_from_cgi_form(self, form)
-
-    def read(self, n=None):
-        """ Read from input stream. """
-        if n is None:
-            return self.fcgreq.stdin.read()
-        else:
-            return self.fcgreq.stdin.read(n)
-
-    def write(self, *data):
-        """ Write to output stream. """
-        self.fcgreq.out.write(self.encode(data))
-
-    def flush(self):
-        """ Flush output stream. """
-        self.fcgreq.flush_out()
-
-    def finish(self):
-        """ Call finish method of FastCGI request to finish handling of this request. """
-        RequestBase.finish(self)
-        self.fcgreq.finish()
-
-    # Headers ----------------------------------------------------------
-
-    def http_headers(self, more_headers=[]):
-        """ Send out HTTP headers. Possibly set a default content-type. """
-        if getattr(self, 'sent_headers', None):
-            return
-        self.sent_headers = 1
-        have_ct = 0
-
-        # send http headers
-        for header in more_headers + getattr(self, 'user_headers', []):
-            if type(header) is unicode:
-                header = header.encode('ascii')
-            if header.lower().startswith("content-type:"):
-                # don't send content-type multiple times!
-                if have_ct: continue
-                have_ct = 1
-            self.write("%s\r\n" % header)
-
-        if not have_ct:
-            self.write("Content-type: text/html;charset=%s\r\n" % config.charset)
-
-        self.write('\r\n')
-
-        #from pprint import pformat
-        #sys.stderr.write(pformat(more_headers))
-        #sys.stderr.write(pformat(self.user_headers))
-
-# WSGI --------------------------------------------------------------
-
-class RequestWSGI(RequestBase):
-    def __init__(self, env):
-        try:
-            self.env = env
-            self.hasContentType = False
-            
-            self.stdin = env['wsgi.input']
-            self.stdout = StringIO.StringIO()
-            
-            self.status = '200 OK'
-            self.headers = []
-            
-            self._setup_vars_from_std_env(env)
-            RequestBase.__init__(self, {})
-        
-        except Exception, err:
-            self.fail(err)
-    
-    def setup_args(self, form=None):
-        if form is None:
-            form = cgi.FieldStorage(fp=self.stdin, environ=self.env, keep_blank_values=1)
-        return self._setup_args_from_cgi_form(form)
-    
-    def read(self, n=None):
-        if n is None:
-            return self.stdin.read()
-        else:
-            return self.stdin.read(n)
-    
-    def write(self, *data):
-        self.stdout.write(self.encode(data))
-    
-    def reset_output(self):
-        self.stdout = StringIO.StringIO()
-    
-    def setHttpHeader(self, header):
-        if type(header) is unicode:
-            header = header.encode('ascii')
-        
-        key, value = header.split(':', 1)
-        value = value.lstrip()
-        if key.lower() == 'content-type':
-            # save content-type for http_headers
-            if self.hasContentType:
-                # we only use the first content-type!
-                return
-            else:
-                self.hasContentType = True
-        
-        elif key.lower() == 'status':
-            # save status for finish
-            self.status = value
-            return
-            
-        self.headers.append((key, value))
-    
-    def http_headers(self, more_headers=[]):
-        for header in more_headers:
-            self.setHttpHeader(header)
-        
-        if not self.hasContentType:
-            self.headers.insert(0, ('Content-Type', 'text/html;charset=%s' % config.charset))
-    
-    def flush(self):
-        pass
-    
-    def finish(self):
-        pass
-    
-    def output(self):
-        return self.stdout.getvalue()
-
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/request/CGI.py	Tue May 16 09:43:18 2006 +1200
@@ -0,0 +1,89 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - CGI Request Implementation for std. CGI web servers
+    like Apache or IIS or others.
+
+    @copyright: 2001-2003 by Jürgen Hermann <jh@web.de>,
+                2003-2006 MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+import sys, os
+
+from MoinMoin.request import RequestBase
+
+class Request(RequestBase):
+    """ specialized on CGI requests """
+
+    def __init__(self, properties={}):
+        try:
+            # force input/output to binary
+            if sys.platform == "win32":
+                import msvcrt
+                msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
+                msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
+
+            self._setup_vars_from_std_env(os.environ)
+            RequestBase.__init__(self, properties)
+
+        except Exception, err:
+            self.fail(err)
+            
+    def open_logs(self):
+        # create log file for catching stderr output
+        if not self.opened_logs:
+            sys.stderr = open(os.path.join(self.cfg.data_dir, 'error.log'), 'at')
+            self.opened_logs = 1
+
+    def read(self, n=None):
+        """ Read from input stream. """
+        if n is None:
+            return sys.stdin.read()
+        else:
+            return sys.stdin.read(n)
+
+    def write(self, *data):
+        """ Write to output stream. """
+        sys.stdout.write(self.encode(data))
+
+    def flush(self):
+        sys.stdout.flush()
+        
+    def finish(self):
+        RequestBase.finish(self)
+        # flush the output, ignore errors caused by the user closing the socket
+        try:
+            sys.stdout.flush()
+        except IOError, ex:
+            import errno
+            if ex.errno != errno.EPIPE: raise
+
+    # Headers ----------------------------------------------------------
+    
+    def http_headers(self, more_headers=[]):
+        # Send only once
+        if getattr(self, 'sent_headers', None):
+            return
+        
+        self.sent_headers = 1
+        have_ct = 0
+
+        # send http headers
+        for header in more_headers + getattr(self, 'user_headers', []):
+            if header.lower().startswith("content-type:"):
+                # don't send content-type multiple times!
+                if have_ct: continue
+                have_ct = 1
+            if type(header) is unicode:
+                header = header.encode('ascii')
+            self.write("%s\r\n" % header)
+
+        if not have_ct:
+            self.write("Content-type: text/html;charset=%s\r\n" % config.charset)
+
+        self.write('\r\n')
+
+        #from pprint import pformat
+        #sys.stderr.write(pformat(more_headers))
+        #sys.stderr.write(pformat(self.user_headers))
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/request/CLI.py	Tue May 16 09:43:18 2006 +1200
@@ -0,0 +1,85 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - CLI Request Implementation for commandline usage.
+
+    @copyright: 2001-2003 by Jürgen Hermann <jh@web.de>,
+                2003-2006 MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+import sys, os
+
+from MoinMoin.request import RequestBase
+
+class Request(RequestBase):
+    """ specialized on command line interface and script requests """
+
+    def __init__(self, url='CLI', pagename='', properties={}):
+        self.saved_cookie = ''
+        self.path_info = '/' + pagename
+        self.query_string = ''
+        self.remote_addr = '127.0.0.1'
+        self.is_ssl = 0
+        self.http_user_agent = 'CLI/Script'
+        self.url = url
+        self.request_method = 'GET'
+        self.request_uri = '/' + pagename # TODO check
+        self.http_host = 'localhost'
+        self.http_referer = ''
+        self.script_name = '.'
+        RequestBase.__init__(self, properties)
+        self.cfg.caching_formats = [] # don't spoil the cache
+        self.initTheme() # usually request.run() does this, but we don't use it
+  
+    def read(self, n=None):
+        """ Read from input stream. """
+        if n is None:
+            return sys.stdin.read()
+        else:
+            return sys.stdin.read(n)
+
+    def write(self, *data):
+        """ Write to output stream. """
+        sys.stdout.write(self.encode(data))
+
+    def flush(self):
+        sys.stdout.flush()
+        
+    def finish(self):
+        RequestBase.finish(self)
+        # flush the output, ignore errors caused by the user closing the socket
+        try:
+            sys.stdout.flush()
+        except IOError, ex:
+            import errno
+            if ex.errno != errno.EPIPE: raise
+
+    def isForbidden(self):
+        """ Nothing is forbidden """
+        return 0
+
+    # Accessors --------------------------------------------------------
+
+    def getQualifiedURL(self, uri=None):
+        """ Return a full URL starting with schema and host
+        
+        TODO: does this create correct pages when you render wiki pages
+              within a cli request?!
+        """
+        return uri
+
+    # Headers ----------------------------------------------------------
+
+    def setHttpHeader(self, header):
+        pass
+
+    def http_headers(self, more_headers=[]):
+        pass
+
+    def http_redirect(self, url):
+        """ Redirect to a fully qualified, or server-rooted URL 
+        
+        TODO: Does this work for rendering redirect pages?
+        """
+        raise Exception("Redirect not supported for command line tools!")
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/request/FCGI.py	Tue May 16 09:43:18 2006 +1200
@@ -0,0 +1,88 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - FastCGI Request Implementation for fastcgi and Apache
+    (and maybe others).
+
+    @copyright: 2001-2003 by Jürgen Hermann <jh@web.de>,
+                2003-2006 MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+import sys, os
+
+from MoinMoin.request import RequestBase
+
+class Request(RequestBase):
+    """ specialized on FastCGI requests """
+
+    def __init__(self, fcgRequest, env, form, properties={}):
+        """ Initializes variables from FastCGI environment and saves
+            FastCGI request and form for further use.
+
+            @param fcgRequest: the FastCGI request instance.
+            @param env: environment passed by FastCGI.
+            @param form: FieldStorage passed by FastCGI.
+        """
+        try:
+            self.fcgreq = fcgRequest
+            self.fcgenv = env
+            self.fcgform = form
+            self._setup_vars_from_std_env(env)
+            RequestBase.__init__(self, properties)
+
+        except Exception, err:
+            self.fail(err)
+
+    def _setup_args_from_cgi_form(self, form=None):
+        """ Override to use FastCGI form """
+        if form is None:
+            form = self.fcgform
+        return RequestBase._setup_args_from_cgi_form(self, form)
+
+    def read(self, n=None):
+        """ Read from input stream. """
+        if n is None:
+            return self.fcgreq.stdin.read()
+        else:
+            return self.fcgreq.stdin.read(n)
+
+    def write(self, *data):
+        """ Write to output stream. """
+        self.fcgreq.out.write(self.encode(data))
+
+    def flush(self):
+        """ Flush output stream. """
+        self.fcgreq.flush_out()
+
+    def finish(self):
+        """ Call finish method of FastCGI request to finish handling of this request. """
+        RequestBase.finish(self)
+        self.fcgreq.finish()
+
+    # Headers ----------------------------------------------------------
+
+    def http_headers(self, more_headers=[]):
+        """ Send out HTTP headers. Possibly set a default content-type. """
+        if getattr(self, 'sent_headers', None):
+            return
+        self.sent_headers = 1
+        have_ct = 0
+
+        # send http headers
+        for header in more_headers + getattr(self, 'user_headers', []):
+            if type(header) is unicode:
+                header = header.encode('ascii')
+            if header.lower().startswith("content-type:"):
+                # don't send content-type multiple times!
+                if have_ct: continue
+                have_ct = 1
+            self.write("%s\r\n" % header)
+
+        if not have_ct:
+            self.write("Content-type: text/html;charset=%s\r\n" % config.charset)
+
+        self.write('\r\n')
+
+        #from pprint import pformat
+        #sys.stderr.write(pformat(more_headers))
+        #sys.stderr.write(pformat(self.user_headers))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/request/MODPYTHON.py	Tue May 16 09:43:18 2006 +1200
@@ -0,0 +1,186 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - mod_python Request Implementation for Apache and mod_python.
+
+    @copyright: 2001-2003 by Jürgen Hermann <jh@web.de>,
+                2003-2006 MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+import sys, os
+
+from MoinMoin.request import RequestBase
+
+class Request(RequestBase):
+    """ specialized on mod_python requests """
+
+    def __init__(self, req):
+        """ Saves mod_pythons request and sets basic variables using
+            the req.subprocess_env, cause this provides a standard
+            way to access the values we need here.
+
+            @param req: the mod_python request instance
+        """
+        try:
+            # flags if headers sent out contained content-type or status
+            self._have_ct = 0
+            self._have_status = 0
+
+            req.add_common_vars()
+            self.mpyreq = req
+            # some mod_python 2.7.X has no get method for table objects,
+            # so we make a real dict out of it first.
+            if not hasattr(req.subprocess_env, 'get'):
+                env=dict(req.subprocess_env)
+            else:
+                env=req.subprocess_env
+            self._setup_vars_from_std_env(env)
+            RequestBase.__init__(self)
+
+        except Exception, err:
+            self.fail(err)
+            
+    def fixURI(self, env):
+        """ Fix problems with script_name and path_info using
+        PythonOption directive to rewrite URI.
+        
+        This is needed when using Apache 1 or other server which does
+        not support adding custom headers per request. With mod_python we
+        can use the PythonOption directive:
+        
+            <Location /url/to/mywiki/>
+                PythonOption X-Moin-Location /url/to/mywiki/
+            </location>
+
+        Note that *neither* script_name *nor* path_info can be trusted
+        when Moin is invoked as a mod_python handler with apache1, so we
+        must build both using request_uri and the provided PythonOption.
+        """
+        # Be compatible with release 1.3.5 "Location" option 
+        # TODO: Remove in later release, we should have one option only.
+        old_location = 'Location'
+        options_table = self.mpyreq.get_options()
+        if not hasattr(options_table, 'get'):
+            options = dict(options_table)
+        else:
+            options = options_table
+        location = options.get(self.moin_location) or options.get(old_location)
+        if location:
+            env[self.moin_location] = location
+            # Try to recreate script_name and path_info from request_uri.
+            import urlparse
+            scriptAndPath = urlparse.urlparse(self.request_uri)[2]
+            self.script_name = location.rstrip('/')
+            path = scriptAndPath.replace(self.script_name, '', 1)            
+            self.path_info = wikiutil.url_unquote(path, want_unicode=False)
+
+        RequestBase.fixURI(self, env)
+
+    def _setup_args_from_cgi_form(self, form=None):
+        """ Override to use mod_python.util.FieldStorage 
+        
+        Its little different from cgi.FieldStorage, so we need to
+        duplicate the conversion code.
+        """
+        from mod_python import util
+        if form is None:
+            form = util.FieldStorage(self.mpyreq)
+
+        args = {}
+        for key in form.keys():
+            values = form[key]
+            if not isinstance(values, list):
+                values = [values]
+            fixedResult = []
+
+            for item in values:
+                # Remember filenames with a name hack
+                if hasattr(item, 'filename') and item.filename:
+                    args[key + '__filename__'] = item.filename
+                # mod_python 2.7 might return strings instead of Field
+                # objects.
+                if hasattr(item, 'value'):
+                    item = item.value
+                fixedResult.append(item)                
+            args[key] = fixedResult
+            
+        return self.decodeArgs(args)
+
+    def run(self, req):
+        """ mod_python calls this with its request object. We don't
+            need it cause its already passed to __init__. So ignore
+            it and just return RequestBase.run.
+
+            @param req: the mod_python request instance
+        """
+        return RequestBase.run(self)
+
+    def read(self, n=None):
+        """ Read from input stream. """
+        if n is None:
+            return self.mpyreq.read()
+        else:
+            return self.mpyreq.read(n)
+
+    def write(self, *data):
+        """ Write to output stream. """
+        self.mpyreq.write(self.encode(data))
+
+    def flush(self):
+        """ We can't flush it, so do nothing. """
+        pass
+        
+    def finish(self):
+        """ Just return apache.OK. Status is set in req.status. """
+        RequestBase.finish(self)
+        # is it possible that we need to return something else here?
+        from mod_python import apache
+        return apache.OK
+
+    # Headers ----------------------------------------------------------
+
+    def setHttpHeader(self, header):
+        """ Filters out content-type and status to set them directly
+            in the mod_python request. Rest is put into the headers_out
+            member of the mod_python request.
+
+            @param header: string, containing valid HTTP header.
+        """
+        if type(header) is unicode:
+            header = header.encode('ascii')
+        key, value = header.split(':', 1)
+        value = value.lstrip()
+        if key.lower() == 'content-type':
+            # save content-type for http_headers
+            if not self._have_ct:
+                # we only use the first content-type!
+                self.mpyreq.content_type = value
+                self._have_ct = 1
+        elif key.lower() == 'status':
+            # save status for finish
+            try:
+                self.mpyreq.status = int(value.split(' ', 1)[0])
+            except:
+                pass
+            else:
+                self._have_status = 1
+        else:
+            # this is a header we sent out
+            self.mpyreq.headers_out[key]=value
+
+    def http_headers(self, more_headers=[]):
+        """ Sends out headers and possibly sets default content-type
+            and status.
+
+            @param more_headers: list of strings, defaults to []
+        """
+        for header in more_headers + getattr(self, 'user_headers', []):
+            self.setHttpHeader(header)
+        # if we don't had an content-type header, set text/html
+        if self._have_ct == 0:
+            self.mpyreq.content_type = "text/html;charset=%s" % config.charset
+        # if we don't had a status header, set 200
+        if self._have_status == 0:
+            self.mpyreq.status = 200
+        # this is for mod_python 2.7.X, for 3.X it's a NOP
+        self.mpyreq.send_http_header()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/request/STANDALONE.py	Tue May 16 09:43:18 2006 +1200
@@ -0,0 +1,132 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - Standalone Moin Server Request Implementation
+
+    @copyright: 2001-2003 by Jürgen Hermann <jh@web.de>,
+                2003-2006 MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+import sys, os
+
+from MoinMoin.request import RequestBase
+
+class Request(RequestBase):
+    """ specialized on StandAlone Server (MoinMoin.server.standalone) requests """
+    script_name = ''
+    
+    def __init__(self, sa, properties={}):
+        """
+        @param sa: stand alone server object
+        @param properties: ...
+        """
+        try:
+            self.sareq = sa
+            self.wfile = sa.wfile
+            self.rfile = sa.rfile
+            self.headers = sa.headers
+            self.is_ssl = 0
+            
+            # Copy headers
+            self.http_accept_language = (sa.headers.getheader('accept-language') 
+                                         or self.http_accept_language)
+            self.http_user_agent = sa.headers.getheader('user-agent', '')            
+            co = filter(None, sa.headers.getheaders('cookie'))
+            self.saved_cookie = ', '.join(co) or ''
+            
+            # Copy rest from standalone request   
+            self.server_name = sa.server.server_name
+            self.server_port = str(sa.server.server_port)
+            self.request_method = sa.command
+            self.request_uri = sa.path
+            self.remote_addr = sa.client_address[0]
+
+            # Values that need more work                        
+            self.path_info, self.query_string = self.splitURI(sa.path)
+            self.setHttpReferer(sa.headers.getheader('referer'))
+            self.setHost(sa.headers.getheader('host'))
+            self.setURL(sa.headers)
+
+            ##self.debugEnvironment(sa.headers)
+            
+            RequestBase.__init__(self, properties)
+
+        except Exception, err:
+            self.fail(err)
+
+    def _setup_args_from_cgi_form(self, form=None):
+        """ Override to create standalone form """
+        form = cgi.FieldStorage(self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST'})
+        return RequestBase._setup_args_from_cgi_form(self, form)
+        
+    def read(self, n=None):
+        """ Read from input stream
+        
+        Since self.rfile.read() will block, content-length will be used instead.
+        
+        TODO: test with n > content length, or when calling several times
+        with smaller n but total over content length.
+        """
+        if n is None:
+            try:
+                n = int(self.headers.get('content-length'))
+            except (TypeError, ValueError):
+                import warnings
+                warnings.warn("calling request.read() when content-length is "
+                              "not available will block")
+                return self.rfile.read()
+        return self.rfile.read(n)
+
+    def write(self, *data):
+        """ Write to output stream. """
+        self.wfile.write(self.encode(data))
+
+    def flush(self):
+        self.wfile.flush()
+        
+    def finish(self):
+        RequestBase.finish(self)
+        self.wfile.flush()
+
+    # Headers ----------------------------------------------------------
+
+    def http_headers(self, more_headers=[]):
+        if getattr(self, 'sent_headers', None):
+            return
+        
+        self.sent_headers = 1
+        user_headers = getattr(self, 'user_headers', [])
+        
+        # check for status header and send it
+        our_status = 200
+        for header in more_headers + user_headers:
+            if header.lower().startswith("status:"):
+                try:
+                    our_status = int(header.split(':', 1)[1].strip().split(" ", 1)[0]) 
+                except:
+                    pass
+                # there should be only one!
+                break
+        # send response
+        self.sareq.send_response(our_status)
+
+        # send http headers
+        have_ct = 0
+        for header in more_headers + user_headers:
+            if type(header) is unicode:
+                header = header.encode('ascii')
+            if header.lower().startswith("content-type:"):
+                # don't send content-type multiple times!
+                if have_ct: continue
+                have_ct = 1
+
+            self.write("%s\r\n" % header)
+
+        if not have_ct:
+            self.write("Content-type: text/html;charset=%s\r\n" % config.charset)
+
+        self.write('\r\n')
+
+        #from pprint import pformat
+        #sys.stderr.write(pformat(more_headers))
+        #sys.stderr.write(pformat(self.user_headers))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/request/TWISTED.py	Tue May 16 09:43:18 2006 +1200
@@ -0,0 +1,156 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - Twisted Request Implementation
+
+    @copyright: 2001-2003 by Jürgen Hermann <jh@web.de>,
+                2003-2006 MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+import sys, os
+
+from MoinMoin.request import RequestBase
+
+class Request(RequestBase):
+    """ specialized on Twisted requests """
+
+    def __init__(self, twistedRequest, pagename, reactor, properties={}):
+        try:
+            self.twistd = twistedRequest
+            self.reactor = reactor
+            
+            # Copy headers
+            self.http_accept_language = self.twistd.getHeader('Accept-Language')
+            self.saved_cookie = self.twistd.getHeader('Cookie')
+            self.http_user_agent = self.twistd.getHeader('User-Agent')
+            
+            # Copy values from twisted request
+            self.server_protocol = self.twistd.clientproto
+            self.server_name = self.twistd.getRequestHostname().split(':')[0]
+            self.server_port = str(self.twistd.getHost()[2])
+            self.is_ssl = self.twistd.isSecure()
+            self.path_info = '/' + '/'.join([pagename] + self.twistd.postpath)
+            self.request_method = self.twistd.method
+            self.remote_host = self.twistd.getClient()
+            self.remote_addr = self.twistd.getClientIP()
+            self.request_uri = self.twistd.uri
+            self.script_name = "/" + '/'.join(self.twistd.prepath[:-1])
+
+            # Values that need more work
+            self.query_string = self.splitURI(self.twistd.uri)[1]
+            self.setHttpReferer(self.twistd.getHeader('Referer'))
+            self.setHost()
+            self.setURL(self.twistd.getAllHeaders())
+
+            ##self.debugEnvironment(twistedRequest.getAllHeaders())
+            
+            RequestBase.__init__(self, properties)
+
+        except MoinMoinFinish: # might be triggered by http_redirect
+            self.http_headers() # send headers (important for sending MOIN_ID cookie)
+            self.finish()
+
+        except Exception, err:
+            # Wrap err inside an internal error if needed
+            from MoinMoin import error
+            if isinstance(err, error.FatalError):
+                self.delayedError = err
+            else:
+                self.delayedError = error.InternalError(str(err))
+
+    def run(self):
+        """ Handle delayed errors then invoke base class run """
+        if hasattr(self, 'delayedError'):
+            self.fail(self.delayedError)
+            return self.finish()
+        RequestBase.run(self)
+            
+    def setup_args(self, form=None):
+        """ Return args dict 
+        
+        Twisted already parsed args, including __filename__ hacking,
+        but did not decode the values.
+        """
+        # TODO: check if for a POST this included query_string args (needed for
+        # TwikiDraw's action=AttachFile&do=savedrawing)
+        return self.decodeArgs(self.twistd.args)
+        
+    def read(self, n=None):
+        """ Read from input stream. """
+        # XXX why is that wrong?:
+        #rd = self.reactor.callFromThread(self.twistd.read)
+        
+        # XXX do we need self.reactor.callFromThread with that?
+        # XXX if yes, why doesn't it work?
+        self.twistd.content.seek(0, 0)
+        if n is None:
+            rd = self.twistd.content.read()
+        else:
+            rd = self.twistd.content.read(n)
+        #print "request.RequestTwisted.read: data=\n" + str(rd)
+        return rd
+    
+    def write(self, *data):
+        """ Write to output stream. """
+        #print "request.RequestTwisted.write: data=\n" + wd
+        self.reactor.callFromThread(self.twistd.write, self.encode(data))
+
+    def flush(self):
+        pass # XXX is there a flush in twisted?
+
+    def finish(self):
+        RequestBase.finish(self)
+        self.reactor.callFromThread(self.twistd.finish)
+
+    def open_logs(self):
+        return
+        # create log file for catching stderr output
+        if not self.opened_logs:
+            sys.stderr = open(os.path.join(self.cfg.data_dir, 'error.log'), 'at')
+            self.opened_logs = 1
+
+    # Headers ----------------------------------------------------------
+
+    def __setHttpHeader(self, header):
+        if type(header) is unicode:
+            header = header.encode('ascii')
+        key, value = header.split(':', 1)
+        value = value.lstrip()
+        if key.lower() == 'set-cookie':
+            key, value = value.split('=', 1)
+            self.twistd.addCookie(key, value)
+        else:
+            self.twistd.setHeader(key, value)
+        #print "request.RequestTwisted.setHttpHeader: %s" % header
+
+    def http_headers(self, more_headers=[]):
+        if getattr(self, 'sent_headers', None):
+            return
+        self.sent_headers = 1
+        have_ct = 0
+
+        # set http headers
+        for header in more_headers + getattr(self, 'user_headers', []):
+            if header.lower().startswith("content-type:"):
+                # don't send content-type multiple times!
+                if have_ct: continue
+                have_ct = 1
+            self.__setHttpHeader(header)
+
+        if not have_ct:
+            self.__setHttpHeader("Content-type: text/html;charset=%s" % config.charset)
+
+    def http_redirect(self, url):
+        """ Redirect to a fully qualified, or server-rooted URL 
+        
+        @param url: relative or absolute url, ascii using url encoding.
+        """
+        url = self.getQualifiedURL(url)
+        self.twistd.redirect(url)
+        # calling finish here will send the rest of the data to the next
+        # request. leave the finish call to run()
+        #self.twistd.finish()
+        raise MoinMoinFinish
+
+    def setResponseCode(self, code, message=None):
+        self.twistd.setResponseCode(code, message)
+        
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/request/WSGI.py	Tue May 16 09:43:18 2006 +1200
@@ -0,0 +1,88 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - WSGI Request Implementation for std. WSGI web servers.
+
+    @copyright: 2001-2003 by Jürgen Hermann <jh@web.de>,
+                2003-2006 MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+import sys, os
+
+from MoinMoin.request import RequestBase
+
+class Request(RequestBase):
+    """ specialized on WSGI requests """
+    def __init__(self, env):
+        try:
+            self.env = env
+            self.hasContentType = False
+            
+            self.stdin = env['wsgi.input']
+            self.stdout = StringIO.StringIO()
+            
+            self.status = '200 OK'
+            self.headers = []
+            
+            self._setup_vars_from_std_env(env)
+            RequestBase.__init__(self, {})
+
+        except Exception, err:
+            self.fail(err)
+    
+    def setup_args(self, form=None):
+        # TODO: does this include query_string args for POST requests?
+        # see also how CGI works now
+        if form is None:
+            form = cgi.FieldStorage(fp=self.stdin, environ=self.env, keep_blank_values=1)
+        return self._setup_args_from_cgi_form(form)
+    
+    def read(self, n=None):
+        if n is None:
+            return self.stdin.read()
+        else:
+            return self.stdin.read(n)
+    
+    def write(self, *data):
+        self.stdout.write(self.encode(data))
+    
+    def reset_output(self):
+        self.stdout = StringIO.StringIO()
+    
+    def setHttpHeader(self, header):
+        if type(header) is unicode:
+            header = header.encode('ascii')
+        
+        key, value = header.split(':', 1)
+        value = value.lstrip()
+        if key.lower() == 'content-type':
+            # save content-type for http_headers
+            if self.hasContentType:
+                # we only use the first content-type!
+                return
+            else:
+                self.hasContentType = True
+        
+        elif key.lower() == 'status':
+            # save status for finish
+            self.status = value
+            return
+            
+        self.headers.append((key, value))
+    
+    def http_headers(self, more_headers=[]):
+        for header in more_headers:
+            self.setHttpHeader(header)
+        
+        if not self.hasContentType:
+            self.headers.insert(0, ('Content-Type', 'text/html;charset=%s' % config.charset))
+    
+    def flush(self):
+        pass
+    
+    def finish(self):
+        pass
+    
+    def output(self):
+        return self.stdout.getvalue()
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/request/__init__.py	Tue May 16 09:43:18 2006 +1200
@@ -0,0 +1,1298 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - RequestBase Implementation
+
+    @copyright: 2001-2003 by Jürgen Hermann <jh@web.de>,
+                2003-2006 MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import os, re, time, sys, cgi, StringIO
+import copy
+from MoinMoin import config, wikiutil, user, caching
+from MoinMoin.util import IsWin9x
+
+
+# Exceptions -----------------------------------------------------------
+
+class MoinMoinFinish(Exception):
+    """ Raised to jump directly to end of run() function, where finish is called """
+    pass
+
+# Timing ---------------------------------------------------------------
+
+class Clock:
+    """ Helper class for code profiling
+        we do not use time.clock() as this does not work across threads
+    """
+
+    def __init__(self):
+        self.timings = {'total': time.time()}
+
+    def start(self, timer):
+        self.timings[timer] = time.time() - self.timings.get(timer, 0)
+
+    def stop(self, timer):
+        self.timings[timer] = time.time() - self.timings[timer]
+
+    def value(self, timer):
+        return "%.3f" % (self.timings[timer], )
+
+    def dump(self):
+        outlist = []
+        for timing in self.timings.items():
+            outlist.append("%s = %.3fs" % timing)
+        outlist.sort()
+        return outlist
+
+
+# Utilities
+
+def cgiMetaVariable(header, scheme='http'):
+    """ Return CGI meta variable for header name
+    
+    e.g 'User-Agent' -> 'HTTP_USER_AGENT'    
+    See http://www.faqs.org/rfcs/rfc3875.html section 4.1.18
+    """
+    var = '%s_%s' % (scheme, header)
+    return var.upper().replace('-', '_')
+    
+
+# Request Base ----------------------------------------------------------
+
+class RequestBase(object):
+    """ A collection for all data associated with ONE request. """
+
+    # Header set to force misbehaved proxies and browsers to keep their
+    # hands off a page
+    # Details: http://support.microsoft.com/support/kb/articles/Q234/0/67.ASP
+    nocache = [
+        "Pragma: no-cache",
+        "Cache-Control: no-cache",
+        "Expires: -1",
+    ]
+
+    # Defaults (used by sub classes)
+    http_accept_language = 'en'
+    server_name = 'localhost'
+    server_port = '80'
+
+    # Extra headers we support. Both standalone and twisted store
+    # headers as lowercase.
+    moin_location = 'x-moin-location'
+    proxy_host = 'x-forwarded-host'
+    
+    def __init__(self, properties={}):
+        # Decode values collected by sub classes
+        self.path_info = self.decodePagename(self.path_info)
+
+        self.failed = 0
+        self._available_actions = None
+        self._known_actions = None
+
+        # Pages meta data that we collect in one request
+        self.pages = {}
+              
+        self.sent_headers = 0
+        self.user_headers = []
+        self.cacheable = 0 # may this output get cached by http proxies/caches?
+        self.page = None
+        self._dicts = None
+        
+        # Fix dircaching problems on Windows 9x
+        if IsWin9x():
+            import dircache
+            dircache.reset()
+
+        # Check for dumb proxy requests
+        # TODO relying on request_uri will not work on all servers, especially
+        # not on external non-Apache servers
+        self.forbidden = False
+        if self.request_uri.startswith('http://'):
+            self.makeForbidden403()
+
+        # Init
+        else:
+            self.writestack = []
+            self.clock = Clock()
+            # order is important here!
+            self.__dict__.update(properties)
+            self._load_multi_cfg()
+            
+            self.isSpiderAgent = self.check_spider()
+        
+            # Set decode charsets.  Input from the user is always in
+            # config.charset, which is the page charsets. Except
+            # path_info, which may use utf-8, and handled by decodePagename.
+            self.decode_charsets = [config.charset]
+            
+            # hierarchical wiki - set rootpage
+            from MoinMoin.Page import Page
+            #path = self.getPathinfo()
+            #if path.startswith('/'):
+            #    pages = path[1:].split('/')
+            #    if 0: # len(path) > 1:
+            #        ## breaks MainPage/SubPage on flat storage
+            #        rootname = u'/'.join(pages[:-1])
+            #    else:
+            #        # this is the usual case, as it ever was...
+            #        rootname = u""
+            #else:
+            #    # no extra path after script name
+            #    rootname = u""
+
+            self.args = {}
+            self.form = {}
+
+            if not self.query_string.startswith('action=xmlrpc'):
+                self.args = self.form = self.setup_args()
+
+            rootname = u''
+            self.rootpage = Page(self, rootname, is_rootpage=1)
+
+            self.user = self.get_user_from_form()
+            
+            if not self.query_string.startswith('action=xmlrpc'):
+                if not self.forbidden and self.isForbidden():
+                    self.makeForbidden403()
+                if not self.forbidden and self.surge_protect():
+                    self.makeUnavailable503()
+
+            from MoinMoin import i18n
+
+            self.logger = None
+            self.pragma = {}
+            self.mode_getpagelinks = 0
+            self.no_closing_html_code = 0
+
+            self.i18n = i18n
+            self.lang = i18n.requestLanguage(self) 
+            # Language for content. Page content should use the wiki default lang,
+            # but generated content like search results should use the user language.
+            self.content_lang = self.cfg.language_default
+            self.getText = lambda text, i18n=self.i18n, request=self, lang=self.lang, **kv: i18n.getText(text, request, lang, kv.get('formatted', True))
+
+            self.opened_logs = 0
+            self.reset()
+        
+    def surge_protect(self):
+        """ check if someone requesting too much from us """
+        validuser = self.user.valid
+        current_id = validuser and self.user.name or self.remote_addr
+        if not validuser and current_id.startswith('127.'): # localnet
+            return False
+        current_action = self.form.get('action', ['show'])[0]
+        
+        limits = self.cfg.surge_action_limits
+        default_limit = self.cfg.surge_action_limits.get('default', (30, 60))
+        
+        now = int(time.time())
+        surgedict = {}
+        surge_detected = False
+        
+        try:
+            cache = caching.CacheEntry(self, 'surgeprotect', 'surge-log')
+            if cache.exists():
+                data = cache.content()
+                data = data.split("\n")
+                for line in data:
+                    try:
+                        id, t, action, surge_indicator = line.split("\t")
+                        t = int(t)
+                        maxnum, dt = limits.get(action, default_limit)
+                        if t >= now - dt:
+                            events = surgedict.setdefault(id, copy.copy({}))
+                            timestamps = events.setdefault(action, copy.copy([]))
+                            timestamps.append((t, surge_indicator))
+                    except StandardError, err:
+                        pass
+                
+            maxnum, dt = limits.get(current_action, default_limit)
+            events = surgedict.setdefault(current_id, copy.copy({}))
+            timestamps = events.setdefault(current_action, copy.copy([]))
+            surge_detected = len(timestamps) > maxnum
+
+            surge_indicator = surge_detected and "!" or ""
+            timestamps.append((now, surge_indicator))
+            if surge_detected:
+                if len(timestamps) < maxnum * 2:
+                    timestamps.append((now + self.cfg.surge_lockout_time, surge_indicator)) # continue like that and get locked out
+        
+            if current_action != 'AttachFile': # don't add AttachFile accesses to all or picture galleries will trigger SP
+                current_action = 'all' # put a total limit on user's requests
+                maxnum, dt = limits.get(current_action, default_limit)
+                events = surgedict.setdefault(current_id, copy.copy({}))
+                timestamps = events.setdefault(current_action, copy.copy([]))
+                surge_detected = surge_detected or len(timestamps) > maxnum
+            
+                surge_indicator = surge_detected and "!" or ""
+                timestamps.append((now, surge_indicator))
+                if surge_detected:
+                    if len(timestamps) < maxnum * 2:
+                        timestamps.append((now + self.cfg.surge_lockout_time, surge_indicator)) # continue like that and get locked out
+        
+            data = []
+            for id, events in surgedict.items():
+                for action, timestamps in events.items():
+                    for t, surge_indicator in timestamps:
+                        data.append("%s\t%d\t%s\t%s" % (id, t, action, surge_indicator))
+            data = "\n".join(data)
+            cache.update(data)
+        except StandardError, err:
+            pass
+
+        return surge_detected   
+        
+    def getDicts(self):
+        """ Lazy initialize the dicts on the first access """
+        if self._dicts is None:
+            from MoinMoin import wikidicts
+            dicts = wikidicts.GroupDict(self)
+            dicts.scandicts()
+            self._dicts = dicts
+        return self._dicts
+        
+    def delDicts(self):
+        """ Delete the dicts, used by some tests """
+        del self._dicts
+        self._dicts = None
+
+    dicts = property(getDicts, None, delDicts)
+  
+    def _load_multi_cfg(self):
+        # protect against calling multiple times
+        if not hasattr(self, 'cfg'):
+            from MoinMoin import multiconfig
+            self.cfg = multiconfig.getConfig(self.url)
+            
+    def setAcceptedCharsets(self, accept_charset):
+        """ Set accepted_charsets by parsing accept-charset header
+
+        Set self.accepted_charsets to an ordered list based on http_accept_charset. 
+        
+        Reference: http://www.w3.org/Protocols/rfc2616/rfc2616.txt
+
+        TODO: currently no code use this value.
+
+        @param accept_charset: accept-charset header
+        """        
+        charsets = []
+        if accept_charset:
+            accept_charset = accept_charset.lower()
+            # Add iso-8859-1 if needed
+            if (not '*' in accept_charset and
+                accept_charset.find('iso-8859-1') < 0):
+                accept_charset += ',iso-8859-1'
+
+            # Make a list, sorted by quality value, using Schwartzian Transform
+            # Create list of tuples (value, name) , sort, extract names  
+            for item in accept_charset.split(','):
+                if ';' in item:
+                    name, qval = item.split(';')
+                    qval = 1.0 - float(qval.split('=')[1])
+                else:
+                    name, qval = item, 0
+                charsets.append((qval, name))                 
+            charsets.sort()
+            # Remove *, its not clear what we should do with it later
+            charsets = [name for qval, name in charsets if name != '*']
+
+        self.accepted_charsets = charsets
+          
+    def _setup_vars_from_std_env(self, env):
+        """ Set common request variables from CGI environment
+        
+        Parse a standard CGI environment as created by common web servers.
+        Reference: http://www.faqs.org/rfcs/rfc3875.html
+
+        @param env: dict like object containing cgi meta variables
+        """
+        # Values we can just copy
+        self.env = env
+        self.http_accept_language = env.get('HTTP_ACCEPT_LANGUAGE',
+                                            self.http_accept_language)
+        self.server_name = env.get('SERVER_NAME', self.server_name)
+        self.server_port = env.get('SERVER_PORT', self.server_port)
+        self.saved_cookie = env.get('HTTP_COOKIE', '')
+        self.script_name = env.get('SCRIPT_NAME', '')
+        self.path_info = env.get('PATH_INFO', '')
+        self.query_string = env.get('QUERY_STRING', '')
+        self.request_method = env.get('REQUEST_METHOD', None)
+        self.remote_addr = env.get('REMOTE_ADDR', '')
+        self.http_user_agent = env.get('HTTP_USER_AGENT', '')
+
+        # REQUEST_URI is not part of CGI spec, but an addition of Apache.
+        self.request_uri = env.get('REQUEST_URI', '')
+        
+        # Values that need more work
+        self.setHttpReferer(env.get('HTTP_REFERER'))
+        self.setIsSSL(env)
+        self.setHost(env.get('HTTP_HOST'))
+        self.fixURI(env)
+        self.setURL(env)
+        
+        ##self.debugEnvironment(env)
+
+    def setHttpReferer(self, referer):
+        """ Set http_referer, making sure its ascii
+        
+        IE might send non-ascii value.
+        """
+        value = ''
+        if referer:
+            value = unicode(referer, 'ascii', 'replace')
+            value = value.encode('ascii', 'replace')
+        self.http_referer = value
+
+    def setIsSSL(self, env):
+        """ Set is_ssl 
+        
+        @param env: dict like object containing cgi meta variables
+        """
+        self.is_ssl = bool(env.get('SSL_PROTOCOL') or
+                           env.get('SSL_PROTOCOL_VERSION') or
+                           env.get('HTTPS') == 'on')
+
+    def setHost(self, host=None):
+        """ Set http_host 
+        
+        Create from server name and port if missing. Previous code
+        default to localhost.
+        """
+        if not host:
+            port = ''
+            standardPort = ('80', '443')[self.is_ssl]
+            if self.server_port != standardPort:
+                port = ':' + self.server_port
+            host = self.server_name + port
+        self.http_host = host
+        
+    def fixURI(self, env):
+        """ Fix problems with script_name and path_info
+        
+        Handle the strange charset semantics on Windows and other non
+        posix systems. path_info is transformed into the system code
+        page by the web server. Additionally, paths containing dots let
+        most webservers choke.
+        
+        Broken environment variables in different environments:
+                path_info script_name
+        Apache1     X          X      PI does not contain dots
+        Apache2     X          X      PI is not encoded correctly
+        IIS         X          X      path_info include script_name
+        Other       ?          -      ? := Possible and even RFC-compatible.
+                                      - := Hopefully not.
+
+        @param env: dict like object containing cgi meta variables
+        """ 
+        # Fix the script_name when using Apache on Windows.
+        server_software = env.get('SERVER_SOFTWARE', '')
+        if os.name == 'nt' and server_software.find('Apache/') != -1:
+            # Removes elements ending in '.' from the path.
+            self.script_name = '/'.join([x for x in self.script_name.split('/') 
+                                         if not x.endswith('.')])
+
+        # Fix path_info
+        if os.name != 'posix' and self.request_uri != '':
+            # Try to recreate path_info from request_uri.
+            import urlparse
+            scriptAndPath = urlparse.urlparse(self.request_uri)[2]
+            path = scriptAndPath.replace(self.script_name, '', 1)            
+            self.path_info = wikiutil.url_unquote(path, want_unicode=False)
+        elif os.name == 'nt':
+            # Recode path_info to utf-8
+            path = wikiutil.decodeWindowsPath(self.path_info)
+            self.path_info = path.encode("utf-8")
+            
+            # Fix bug in IIS/4.0 when path_info contain script_name
+            if self.path_info.startswith(self.script_name):
+                self.path_info = self.path_info[len(self.script_name):]
+
+    def setURL(self, env):
+        """ Set url, used to locate wiki config 
+        
+        This is the place to manipulate url parts as needed.
+        
+        @param env: dict like object containing cgi meta variables or http headers.
+        """
+        # If we serve on localhost:8000 and use a proxy on
+        # example.com/wiki, our urls will be example.com/wiki/pagename
+        # Same for the wiki config - they must use the proxy url.
+        self.rewriteHost(env)
+        self.rewriteURI(env)
+        
+        if not self.request_uri:
+            self.request_uri = self.makeURI()
+        self.url = self.http_host + self.request_uri
+
+    def rewriteHost(self, env):
+        """ Rewrite http_host transparently
+        
+        Get the proxy host using 'X-Forwarded-Host' header, added by
+        Apache 2 and other proxy software.
+        
+        TODO: Will not work for Apache 1 or others that don't add this header.
+        
+        TODO: If we want to add an option to disable this feature it
+        should be in the server script, because the config is not
+        loaded at this point, and must be loaded after url is set.
+        
+        @param env: dict like object containing cgi meta variables or http headers.
+        """
+        proxy_host = (env.get(self.proxy_host) or
+                      env.get(cgiMetaVariable(self.proxy_host)))
+        if proxy_host:
+            self.http_host = proxy_host
+
+    def rewriteURI(self, env):
+        """ Rewrite request_uri, script_name and path_info transparently
+        
+        Useful when running mod python or when running behind a proxy,
+        e.g run on localhost:8000/ and serve as example.com/wiki/.
+
+        Uses private 'X-Moin-Location' header to set the script name.
+        This allow setting the script name when using Apache 2
+        <location> directive::
+
+            <Location /my/wiki/>
+                RequestHeader set X-Moin-Location /my/wiki/
+            </location>
+        
+        TODO: does not work for Apache 1 and others that do not allow
+        setting custom headers per request.
+        
+        @param env: dict like object containing cgi meta variables or http headers.
+        """
+        location = (env.get(self.moin_location) or 
+                    env.get(cgiMetaVariable(self.moin_location)))
+        if location is None:
+            return
+        
+        scriptAndPath = self.script_name + self.path_info
+        location = location.rstrip('/')
+        self.script_name = location
+        
+        # This may happen when using mod_python
+        if scriptAndPath.startswith(location):
+            self.path_info = scriptAndPath[len(location):]
+
+        # Recreate the URI from the modified parts
+        if self.request_uri:
+            self.request_uri = self.makeURI()
+
+    def makeURI(self):
+        """ Return uri created from uri parts """
+        uri = self.script_name + wikiutil.url_quote(self.path_info)
+        if self.query_string:
+            uri += '?' + self.query_string
+        return uri
+
+    def splitURI(self, uri):
+        """ Return path and query splited from uri
+        
+        Just like CGI environment, the path is unquoted, the query is not.
+        """
+        if '?' in uri:
+            path, query = uri.split('?', 1)
+        else:
+            path, query = uri, ''
+        return wikiutil.url_unquote(path, want_unicode=False), query        
+
+    def get_user_from_form(self):
+        """ read the maybe present UserPreferences form and call get_user with the values """
+        name = self.form.get('name', [None])[0]
+        password = self.form.get('password', [None])[0]
+        login = self.form.has_key('login')
+        logout = self.form.has_key('logout')
+        u = self.get_user_default_unknown(name=name, password=password,
+                                          login=login, logout=logout,
+                                          user_obj=None)
+        return u
+    
+    def get_user_default_unknown(self, **kw):
+        """ call do_auth and if it doesnt return a user object, make some "Unknown User" """
+        user_obj = self.get_user_default_None(**kw)
+        if user_obj is None:
+            user_obj = user.User(self, auth_method="request:427")
+        return user_obj
+
+    def get_user_default_None(self, **kw):
+        """ loop over auth handlers, return a user obj or None """
+        name = kw.get('name')
+        password = kw.get('password')
+        login = kw.get('login')
+        logout = kw.get('logout')
+        user_obj = kw.get('user_obj')
+        for auth in self.cfg.auth:
+            user_obj, continue_flag = auth(self, name=name, password=password,
+                                           login=login, logout=logout, user_obj=user_obj)
+            if not continue_flag:
+                break
+        return user_obj
+        
+    def reset(self):
+        """ Reset request state.
+
+        Called after saving a page, before serving the updated
+        page. Solves some practical problems with request state
+        modified during saving.
+
+        """
+        # This is the content language and has nothing to do with
+        # The user interface language. The content language can change
+        # during the rendering of a page by lang macros
+        self.current_lang = self.cfg.language_default
+
+        self._all_pages = None
+        # caches unique ids
+        self._page_ids = {}
+        # keeps track of pagename/heading combinations
+        # parsers should use this dict and not a local one, so that
+        # macros like TableOfContents in combination with Include can work
+        self._page_headings = {}
+
+        if hasattr(self, "_fmt_hd_counters"):
+            del self._fmt_hd_counters
+
+    def loadTheme(self, theme_name):
+        """ Load the Theme to use for this request.
+
+        @param theme_name: the name of the theme
+        @type theme_name: str
+        @rtype: int
+        @return: success code
+                 0 on success
+                 1 if user theme could not be loaded,
+                 2 if a hard fallback to modern theme was required.
+        """
+        fallback = 0
+        if theme_name == "<default>":
+            theme_name = self.cfg.theme_default
+        
+        try:
+            Theme = wikiutil.importPlugin(self.cfg, 'theme', theme_name, 'Theme')
+        except wikiutil.PluginMissingError:
+            fallback = 1
+            try:
+                Theme = wikiutil.importPlugin(self.cfg, 'theme', self.cfg.theme_default, 'Theme')
+            except wikiutil.PluginMissingError:
+                fallback = 2
+                from MoinMoin.theme.modern import Theme
+        
+        self.theme = Theme(self)
+        return fallback
+
+    def setContentLanguage(self, lang):
+        """ Set the content language, used for the content div
+
+        Actions that generate content in the user language, like search,
+        should set the content direction to the user language before they
+        call send_title!
+        """
+        self.content_lang = lang
+        self.current_lang = lang
+
+    def getPragma(self, key, defval=None):
+        """ Query a pragma value (#pragma processing instruction)
+
+            Keys are not case-sensitive.
+        """
+        return self.pragma.get(key.lower(), defval)
+
+    def setPragma(self, key, value):
+        """ Set a pragma value (#pragma processing instruction)
+
+            Keys are not case-sensitive.
+        """
+        self.pragma[key.lower()] = value
+
+    def getPathinfo(self):
+        """ Return the remaining part of the URL. """
+        return self.path_info
+
+    def getScriptname(self):
+        """ Return the scriptname part of the URL ('/path/to/my.cgi'). """
+        if self.script_name == '/':
+            return ''
+        return self.script_name
+
+    def getPageNameFromQueryString(self):
+        """ Try to get pagename from the query string
+        
+        Support urls like http://netloc/script/?page_name. Allow
+        solving path_info encoding problems by calling with the page
+        name as a query.
+        """
+        pagename = wikiutil.url_unquote(self.query_string, want_unicode=False)
+        pagename = self.decodePagename(pagename)
+        pagename = self.normalizePagename(pagename)
+        return pagename
+    
+    def getKnownActions(self):
+        """ Create a dict of avaiable actions
+
+        Return cached version if avaiable.
+       
+        @rtype: dict
+        @return: dict of all known actions
+        """
+        try:
+            self.cfg._known_actions # check
+        except AttributeError:
+            from MoinMoin import action
+            # Add built in actions
+            actions = [name[3:] for name in action.__dict__ if name.startswith('do_')]
+
+            # Add plugins           
+            dummy, plugins = action.getPlugins(self)
+            actions.extend(plugins)
+
+            # Add extensions
+            actions.extend(action.extension_actions)           
+           
+            # TODO: Use set when we require Python 2.3
+            actions = dict(zip(actions, [''] * len(actions)))            
+            self.cfg._known_actions = actions
+
+        # Return a copy, so clients will not change the dict.
+        return self.cfg._known_actions.copy()        
+
+    def getAvailableActions(self, page):
+        """ Get list of avaiable actions for this request
+
+        The dict does not contain actions that starts with lower case.
+        Themes use this dict to display the actions to the user.
+
+        @param page: current page, Page object
+        @rtype: dict
+        @return: dict of avaiable actions
+        """
+        if self._available_actions is None:
+            # Add actions for existing pages only, including deleted pages.
+            # Fix *OnNonExistingPage bugs.
+            if not (page.exists(includeDeleted=1) and self.user.may.read(page.page_name)):
+                return []
+
+            # Filter non ui actions (starts with lower case letter)
+            actions = self.getKnownActions()
+            for key in actions.keys():
+                if key[0].islower():
+                    del actions[key]
+
+            # Filter wiki excluded actions
+            for key in self.cfg.actions_excluded:
+                if key in actions:
+                    del actions[key]                
+
+            # Filter actions by page type, acl and user state
+            excluded = []
+            if ((page.isUnderlayPage() and not page.isStandardPage()) or
+                not self.user.may.write(page.page_name) or
+                not self.user.may.delete(page.page_name)):
+                # Prevent modification of underlay only pages, or pages
+                # the user can't write and can't delete
+                excluded = [u'RenamePage', u'DeletePage', ] # AttachFile must NOT be here!
+            for key in excluded:
+                if key in actions:
+                    del actions[key]                
+
+            self._available_actions = actions
+
+        # Return a copy, so clients will not change the dict.
+        return self._available_actions.copy()
+
+    def redirectedOutput(self, function, *args, **kw):
+        """ Redirect output during function, return redirected output """
+        buffer = StringIO.StringIO()
+        self.redirect(buffer)
+        try:
+            function(*args, **kw)
+        finally:
+            self.redirect()
+        text = buffer.getvalue()
+        buffer.close()        
+        return text
+
+    def redirect(self, file=None):
+        """ Redirect output to file, or restore saved output """
+        if file:
+            self.writestack.append(self.write)
+            self.write = file.write
+        else:
+            self.write = self.writestack.pop()
+
+    def reset_output(self):
+        """ restore default output method
+            destroy output stack
+            (useful for error messages)
+        """
+        if self.writestack:
+            self.write = self.writestack[0]
+            self.writestack = []
+
+    def log(self, msg):
+        """ Log to stderr, which may be error.log """
+        msg = msg.strip()
+        # Encode unicode msg
+        if isinstance(msg, unicode):
+            msg = msg.encode(config.charset)
+        # Add time stamp
+        msg = '[%s] %s\n' % (time.asctime(), msg)
+        sys.stderr.write(msg)
+    
+    def write(self, *data):
+        """ Write to output stream. """
+        raise NotImplementedError
+
+    def encode(self, data):
+        """ encode data (can be both unicode strings and strings),
+            preparing for a single write()
+        """
+        wd = []
+        for d in data:
+            try:
+                if isinstance(d, unicode):
+                    # if we are REALLY sure, we can use "strict"
+                    d = d.encode(config.charset, 'replace') 
+                wd.append(d)
+            except UnicodeError:
+                print >>sys.stderr, "Unicode error on: %s" % repr(d)
+        return ''.join(wd)
+    
+    def decodePagename(self, name):
+        """ Decode path, possibly using non ascii characters
+
+        Does not change the name, only decode to Unicode.
+
+        First split the path to pages, then decode each one. This enables
+        us to decode one page using config.charset and another using
+        utf-8. This situation happens when you try to add to a name of
+        an existing page.
+
+        See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.1
+        
+        @param name: page name, string
+        @rtype: unicode
+        @return decoded page name
+        """
+        # Split to pages and decode each one
+        pages = name.split('/')
+        decoded = []
+        for page in pages:
+            # Recode from utf-8 into config charset. If the path
+            # contains user typed parts, they are encoded using 'utf-8'.
+            if config.charset != 'utf-8':
+                try:
+                    page = unicode(page, 'utf-8', 'strict')
+                    # Fit data into config.charset, replacing what won't
+                    # fit. Better have few "?" in the name than crash.
+                    page = page.encode(config.charset, 'replace')
+                except UnicodeError:
+                    pass
+                
+            # Decode from config.charset, replacing what can't be decoded.
+            page = unicode(page, config.charset, 'replace')
+            decoded.append(page)
+
+        # Assemble decoded parts
+        name = u'/'.join(decoded)
+        return name
+
+    def normalizePagename(self, name):
+        """ Normalize page name 
+
+        Convert '_' to spaces - allows using nice URLs with spaces, with no
+        need to quote.
+
+        Prevent creating page names with invisible characters or funny
+        whitespace that might confuse the users or abuse the wiki, or
+        just does not make sense.
+
+        Restrict even more group pages, so they can be used inside acl lines.
+        
+        @param name: page name, unicode
+        @rtype: unicode
+        @return: decoded and sanitized page name
+        """
+        # Replace underscores with spaces
+        name = name.replace(u'_', u' ')
+
+        # Strip invalid characters
+        name = config.page_invalid_chars_regex.sub(u'', name)
+
+        # Split to pages and normalize each one
+        pages = name.split(u'/')
+        normalized = []
+        for page in pages:            
+            # Ignore empty or whitespace only pages
+            if not page or page.isspace():
+                continue
+
+            # Cleanup group pages.
+            # Strip non alpha numeric characters, keep white space
+            if wikiutil.isGroupPage(self, page):
+                page = u''.join([c for c in page
+                                 if c.isalnum() or c.isspace()])
+
+            # Normalize white space. Each name can contain multiple 
+            # words separated with only one space. Split handle all
+            # 30 unicode spaces (isspace() == True)
+            page = u' '.join(page.split())
+            
+            normalized.append(page)            
+        
+        # Assemble components into full pagename
+        name = u'/'.join(normalized)
+        return name
+        
+    def read(self, n):
+        """ Read n bytes from input stream. """
+        raise NotImplementedError
+
+    def flush(self):
+        """ Flush output stream. """
+        raise NotImplementedError
+
+    def check_spider(self):
+        """ check if the user agent for current request is a spider/bot """
+        isSpider = False
+        spiders = self.cfg.ua_spiders
+        if spiders:
+            ua = self.getUserAgent()
+            if ua:
+                isSpider = re.search(spiders, ua, re.I) is not None
+        return isSpider
+
+    def isForbidden(self):
+        """ check for web spiders and refuse anything except viewing """
+        forbidden = 0
+        # we do not have a parsed query string here, so we can just do simple matching
+        qs = self.query_string
+        if ((qs != '' or self.request_method != 'GET') and
+            not 'action=rss_rc' in qs and
+            # allow spiders to get attachments and do 'show'
+            not ('action=AttachFile' in qs and 'do=get' in qs) and
+            not 'action=show' in qs
+            ):
+            forbidden = self.isSpiderAgent
+
+        if not forbidden and self.cfg.hosts_deny:
+            ip = self.remote_addr
+            for host in self.cfg.hosts_deny:
+                if host[-1] == '.' and ip.startswith(host):
+                    forbidden = 1
+                    #self.log("hosts_deny (net): %s" % str(forbidden))
+                    break
+                if ip == host:
+                    forbidden = 1
+                    #self.log("hosts_deny (ip): %s" % str(forbidden))
+                    break
+        return forbidden
+
+    def setup_args(self, form=None):
+        """ Return args dict 
+        First, we parse the query string (usually this is used in GET methods,
+        but TwikiDraw uses ?action=AttachFile&do=savedrawing plus posted stuff).
+        Second, we update what we got in first step by the stuff we get from
+        the form (or by a POST). We invoke _setup_args_from_cgi_form to handle
+        possible file uploads.
+        
+        Warning: calling with a form might fail, depending on the type of the
+        request! Only the request knows which kind of form it can handle.
+        
+        TODO: The form argument should be removed in 1.5.
+        """
+        args = cgi.parse_qs(self.query_string, keep_blank_values=1)
+        args = self.decodeArgs(args)
+        # if we have form data (e.g. in a POST), those override the stuff we already have:
+        if form is not None or self.request_method == 'POST':
+            postargs = self._setup_args_from_cgi_form(form)
+            args.update(postargs)
+        return args
+
+    def _setup_args_from_cgi_form(self, form=None):
+        """ Return args dict from a FieldStorage
+        
+        Create the args from a standard cgi.FieldStorage or from given form.
+        Each key contain a list of values.
+
+        @param form: a cgi.FieldStorage
+        @rtype: dict
+        @return: dict with form keys, each contains a list of values
+        """
+        if form is None:
+            form = cgi.FieldStorage()
+
+        args = {}
+        for key in form:
+            values = form[key]
+            if not isinstance(values, list):
+                values = [values]
+            fixedResult = []
+            for item in values:
+                fixedResult.append(item.value)
+                if isinstance(item, cgi.FieldStorage) and item.filename:
+                    # Save upload file name in a separate key
+                    args[key + '__filename__'] = item.filename            
+            args[key] = fixedResult
+            
+        return self.decodeArgs(args)
+
+    def decodeArgs(self, args):
+        """ Decode args dict 
+        
+        Decoding is done in a separate path because it is reused by
+        other methods and sub classes.
+        """
+        decode = wikiutil.decodeUserInput
+        result = {}
+        for key in args:
+            if key + '__filename__' in args:
+                # Copy file data as is
+                result[key] = args[key]
+            elif key.endswith('__filename__'):
+                result[key] = decode(args[key], self.decode_charsets)
+            else:
+                result[key] = [decode(value, self.decode_charsets) for value in args[key]]
+        return result
+
+    def getBaseURL(self):
+        """ Return a fully qualified URL to this script. """
+        return self.getQualifiedURL(self.getScriptname())
+
+    def getQualifiedURL(self, uri=''):
+        """ Return an absolute URL starting with schema and host.
+
+        Already qualified urls are returned unchanged.
+
+        @param uri: server rooted uri e.g /scriptname/pagename.
+                    It must start with a slash. Must be ascii and url encoded.
+        """
+        import urlparse
+        scheme = urlparse.urlparse(uri)[0]
+        if scheme:
+            return uri
+
+        scheme = ('http', 'https')[self.is_ssl]
+        result = "%s://%s%s" % (scheme, self.http_host, uri)
+
+        # This might break qualified urls in redirects!
+        # e.g. mapping 'http://netloc' -> '/'
+        return wikiutil.mapURL(self, result)
+
+    def getUserAgent(self):
+        """ Get the user agent. """
+        return self.http_user_agent
+
+    def makeForbidden(self, resultcode, msg):
+        statusmsg = {
+            403: 'FORBIDDEN',
+            503: 'Service unavailable',
+        }
+        self.http_headers([
+            'Status: %d %s' % (resultcode, statusmsg[resultcode]),
+            'Content-Type: text/plain'
+        ])
+        self.write(msg)
+        self.setResponseCode(resultcode)
+        self.forbidden = True
+
+    def makeForbidden403(self):
+        self.makeForbidden(403, 'You are not allowed to access this!\r\n')
+
+    def makeUnavailable503(self):
+        self.makeForbidden(503, "Warning:\r\n"
+                   "You triggered the wiki's surge protection by doing too many requests in a short time.\r\n"
+                   "Please make a short break reading the stuff you already got.\r\n"
+                   "When you restart doing requests AFTER that, slow down or you might get locked out for a longer time!\r\n")
+
+    def initTheme(self):
+        """ Set theme - forced theme, user theme or wiki default """
+        if self.cfg.theme_force:
+            theme_name = self.cfg.theme_default
+        else:
+            theme_name = self.user.theme_name
+        self.loadTheme(theme_name)
+        
+    def run(self):
+        # Exit now if __init__ failed or request is forbidden
+        if self.failed or self.forbidden:
+            # Don't sleep() here, it binds too much of our resources!
+            return self.finish()
+
+        self.open_logs()
+        _ = self.getText
+        self.clock.start('run')
+
+        from MoinMoin.Page import Page
+        from MoinMoin.formatter.text_html import Formatter
+        self.html_formatter = Formatter(self)
+        self.formatter = self.html_formatter
+
+        if self.query_string == 'action=xmlrpc':
+            from MoinMoin import xmlrpc
+            xmlrpc.xmlrpc(self)
+            return self.finish()
+        
+        if self.query_string == 'action=xmlrpc2':
+            from MoinMoin import xmlrpc
+            xmlrpc.xmlrpc2(self)
+            return self.finish()
+
+        # parse request data
+        try:
+            self.initTheme()
+            
+            action_name = self.form.get('action', [None])[0]
+
+            # The last component in path_info is the page name, if any
+            path = self.getPathinfo()
+            if path.startswith('/'):
+                pagename = self.normalizePagename(path)
+            else:
+                pagename = None
+
+            # Handle request. We have these options:
+            
+            # 1. If user has a bad user name, delete its bad cookie and
+            # send him to UserPreferences to make a new account.
+            if not user.isValidName(self, self.user.name):
+                msg = _("""Invalid user name {{{'%s'}}}.
+Name may contain any Unicode alpha numeric character, with optional one
+space between words. Group page name is not allowed.""") % self.user.name
+                self.user = self.get_user_default_unknown(name=self.user.name, logout=True)
+                page = wikiutil.getSysPage(self, 'UserPreferences')
+                page.send_page(self, msg=msg)
+
+            # 2. Or jump to page where user left off
+            elif not pagename and not action_name and self.user.remember_last_visit:
+                pagetrail = self.user.getTrail()
+                if pagetrail:
+                    # Redirect to last page visited
+                    if ":" in pagetrail[-1]:
+                        wikitag, wikiurl, wikitail, error = wikiutil.resolve_wiki(self, pagetrail[-1]) 
+                        url = wikiurl + wikiutil.quoteWikinameURL(wikitail)
+                    else:
+                        url = Page(self, pagetrail[-1]).url(self)
+                else:
+                    # Or to localized FrontPage
+                    url = wikiutil.getFrontPage(self).url(self)
+                self.http_redirect(url)
+                return self.finish()
+            
+            # 3. Or handle action
+            else:
+                if action_name is None:
+                    action_name = 'show'
+                if not pagename and self.query_string:
+                    pagename = self.getPageNameFromQueryString()
+                # pagename could be empty after normalization e.g. '///' -> ''
+                # Use localized FrontPage if pagename is empty
+                if not pagename:
+                    self.page = wikiutil.getFrontPage(self)
+                else:
+                    self.page = Page(self, pagename)
+
+                # Complain about unknown actions
+                if not action_name in self.getKnownActions():
+                    self.http_headers()
+                    self.write(u'<html><body><h1>Unknown action %s</h1></body>' % wikiutil.escape(action_name))
+
+                # Disallow non available actions
+                elif action_name[0].isupper() and not action_name in self.getAvailableActions(self.page):
+                    # Send page with error
+                    msg = _("You are not allowed to do %s on this page.") % wikiutil.escape(action_name)
+                    if not self.user.valid:
+                        # Suggest non valid user to login
+                        msg += " " + _("Login and try again.", formatted=0)
+                    self.page.send_page(self, msg=msg)
+
+                # Try action
+                else:
+                    from MoinMoin import action
+                    handler = action.getHandler(self, action_name)
+                    handler(self.page.page_name, self)
+
+            # every action that didn't use to raise MoinMoinNoFooter must call this now:
+            # self.theme.send_closing_html()
+
+        except MoinMoinFinish:
+            pass
+        except Exception, err:
+            self.fail(err)
+
+        return self.finish()
+
+    def http_redirect(self, url):
+        """ Redirect to a fully qualified, or server-rooted URL
+        
+        @param url: relative or absolute url, ascii using url encoding.
+        """
+        url = self.getQualifiedURL(url)
+        self.http_headers(["Status: 302 Found", "Location: %s" % url])
+
+    def setHttpHeader(self, header):
+        """ Save header for later send. """
+        self.user_headers.append(header)
+
+    def setResponseCode(self, code, message=None):
+        pass
+
+    def fail(self, err):
+        """ Fail when we can't continue
+
+        Send 500 status code with the error name. Reference: 
+        http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1
+
+        Log the error, then let failure module handle it. 
+
+        @param err: Exception instance or subclass.
+        """
+        self.failed = 1 # save state for self.run()            
+        self.http_headers(['Status: 500 MoinMoin Internal Error'])
+        self.setResponseCode(500)
+        self.log('%s: %s' % (err.__class__.__name__, str(err)))        
+        from MoinMoin import failure
+        failure.handle(self)             
+
+    def open_logs(self):
+        pass
+
+    def makeUniqueID(self, base):
+        """
+        Generates a unique ID using a given base name. Appends a running count to the base.
+
+        @param base: the base of the id
+        @type base: unicode
+
+        @returns: an unique id
+        @rtype: unicode
+        """
+        if not isinstance(base, unicode):
+            base = unicode(str(base), 'ascii', 'ignore')
+        count = self._page_ids.get(base, -1) + 1
+        self._page_ids[base] = count
+        if count == 0:
+            return base
+        return u'%s_%04d' % (base, count)
+
+    def httpDate(self, when=None, rfc='1123'):
+        """ Returns http date string, according to rfc2068
+
+        See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-3.3
+
+        A http 1.1 server should use only rfc1123 date, but cookie's
+        "expires" field should use the older obsolete rfc850 date.
+
+        Note: we can not use strftime() because that honors the locale
+        and rfc2822 requires english day and month names.
+
+        We can not use email.Utils.formatdate because it formats the
+        zone as '-0000' instead of 'GMT', and creates only rfc1123
+        dates. This is a modified version of email.Utils.formatdate
+        from Python 2.4.
+
+        @param when: seconds from epoch, as returned by time.time()
+        @param rfc: conform to rfc ('1123' or '850')
+        @rtype: string
+        @return: http date conforming to rfc1123 or rfc850
+        """
+        if when is None:
+            when = time.time()
+        now = time.gmtime(when)
+        month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
+                 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now.tm_mon - 1]
+        if rfc == '1123':
+            day = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][now.tm_wday]
+            date = '%02d %s %04d' % (now.tm_mday, month, now.tm_year)
+        elif rfc == '850':
+            day = ["Monday", "Tuesday", "Wednesday", "Thursday",
+                    "Friday", "Saturday", "Sunday"][now.tm_wday]
+            date = '%02d-%s-%s' % (now.tm_mday, month, str(now.tm_year)[-2:])
+        else:
+            raise ValueError("Invalid rfc value: %s" % rfc)
+        
+        return '%s, %s %02d:%02d:%02d GMT' % (day, date, now.tm_hour,
+                                              now.tm_min, now.tm_sec)
+    
+    def disableHttpCaching(self):
+        """ Prevent caching of pages that should not be cached
+
+        This is important to prevent caches break acl by providing one
+        user pages meant to be seen only by another user, when both users
+        share the same caching proxy.
+        """
+        # Run only once
+        if hasattr(self, 'http_caching_disabled'):
+            return
+        self.http_caching_disabled = 1
+
+        # Set Cache control header for http 1.1 caches
+        # See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2109.html#sec-4.2.3
+        # and http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-14.9
+        self.setHttpHeader('Cache-Control: no-cache="set-cookie"')
+        self.setHttpHeader('Cache-Control: private')
+        self.setHttpHeader('Cache-Control: max-age=0')       
+
+        # Set Expires for http 1.0 caches (does not support Cache-Control)
+        yearago = time.time() - (3600 * 24 * 365)
+        self.setHttpHeader('Expires: %s' % self.httpDate(when=yearago))
+
+        # Set Pragma for http 1.0 caches
+        # See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-14.32
+        self.setHttpHeader('Pragma: no-cache')
+
+    def finish(self):
+        """ General cleanup on end of request
+        
+        Delete circular references - all object that we create using self.name = class(self).
+        This helps Python to collect these objects and keep our memory footprint lower.
+        """
+        try:
+            del self.user
+            del self.theme
+            del self.dicts
+        except:
+            pass
+
+    # Debug ------------------------------------------------------------
+
+    def debugEnvironment(self, env):
+        """ Environment debugging aid """
+        # Keep this one name per line so its easy to comment stuff
+        names = [
+#             'http_accept_language',
+#             'http_host',
+#             'http_referer',
+#             'http_user_agent',
+#             'is_ssl',
+            'path_info',
+            'query_string',
+#             'remote_addr',
+            'request_method',
+#             'request_uri',
+#             'saved_cookie',
+            'script_name',
+#             'server_name',
+#             'server_port',
+            ]
+        names.sort()
+        attributes = []
+        for name in names:
+            attributes.append('  %s = %r\n' % (name, getattr(self, name, None)))
+        attributes = ''.join(attributes)
+        
+        environment = []
+        names = env.keys()
+        names.sort()
+        for key in names:
+            environment.append('  %s = %r\n' % (key, env[key]))
+        environment = ''.join(environment)
+        
+        data = '\nRequest Attributes\n%s\nEnviroment\n%s' % (attributes, environment)        
+        f = open('/tmp/env.log', 'a')
+        try:
+            f.write(data)
+        finally:
+            f.close()
+  
--- a/MoinMoin/script/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
--- a/MoinMoin/script/_util.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/script/_util.py	Tue May 16 09:43:18 2006 +1200
@@ -117,11 +117,11 @@
     
     def init_request(self):
         """ create request """
-        from MoinMoin.request import RequestCLI
+        from MoinMoin.request import CLI
         if self.options.wiki_url:
-            self.request = RequestCLI(self.options.wiki_url, self.options.page)
+            self.request = CLI.Request(self.options.wiki_url, self.options.page)
         else:
-            self.request = RequestCLI(pagename=self.options.page)
+            self.request = CLI.Request(pagename=self.options.page)
         
     def mainloop(self):
         # Insert config dir or the current directory to the start of the path.
--- a/MoinMoin/script/cli/show.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/script/cli/show.py	Tue May 16 09:43:18 2006 +1200
@@ -3,14 +3,11 @@
     MoinMoin - cli show script
 
     Just run a CLI request and show the output.
-    Currently, we require --page option for the pagename, this is ugly, but
-    matches the RequestCLI interface...
-               
+
     @copyright: 2006 by Thomas Waldmann
     @license: GNU GPL, see COPYING for details.
 """
 
-from MoinMoin.request import RequestCLI
 from MoinMoin.script._util import MoinScript
 
 class PluginScript(MoinScript):
--- a/MoinMoin/script/maint/globaledit.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/script/maint/globaledit.py	Tue May 16 09:43:18 2006 +1200
@@ -99,7 +99,7 @@
             pagelist = request.rootpage.getPageList(user='')
 
             for pagename in pagelist:
-                #request = RequestCLI(url=url, pagename=pagename.encode('utf-8'))
+                #request = CLI.Request(url=url, pagename=pagename.encode('utf-8'))
                 p = PageEditor.PageEditor(request, pagename, do_editor_backup=0)
                 origtext = p.get_raw_body()
                 changedtext = self.do_edit(pagename, origtext)
--- a/MoinMoin/script/old/xmlrpc-tools/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-{arch}
-.arch-ids
-
--- a/MoinMoin/script/old/xmlrpc-tools/putPageTest.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/script/old/xmlrpc-tools/putPageTest.py	Tue May 16 09:43:18 2006 +1200
@@ -4,8 +4,8 @@
 This script is just an example how to put data into a wiki using xmlrpc.
 We use wiki rpc v2 here.
 
-This script only works if you edited MoinMoin/wikirpc.py (see the comment
-in the putPage handler) to not require http auth (trusted user) and to
+This script only works if you edited MoinMoin/xmlrpc/__init__.py (see the
+comment in the putPage handler) to not require http auth (trusted user) and to
 really use the pagename we give.
 
 This can be done for migrating data into an offline moin wiki running on
--- a/MoinMoin/server/standalone.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/server/standalone.py	Tue May 16 09:43:18 2006 +1200
@@ -39,7 +39,7 @@
 
 from MoinMoin import version, wikiutil
 from MoinMoin.server import Config, switchUID
-from MoinMoin.request import RequestStandAlone
+from MoinMoin.request import STANDALONE
 from MoinMoin.util import timefuncs
 
 # Server globals
@@ -329,7 +329,7 @@
         self.expires = 0
 
         try:
-            req = RequestStandAlone(self, properties=config.properties)
+            req = STANDALONE.Request(self, properties=config.properties)
             req.run()
         except socket.error, err:
             # Ignore certain errors
--- a/MoinMoin/server/twistedmoin.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/server/twistedmoin.py	Tue May 16 09:43:18 2006 +1200
@@ -36,7 +36,7 @@
 threadable.init(1)
 
 # MoinMoin imports
-from MoinMoin.request import RequestTwisted
+from MoinMoin.request import TWISTED
 from MoinMoin.server import Config
 
 # Set threads flag, so other code can use proper locking
@@ -76,8 +76,7 @@
         else:
             if config.memoryProfile:
                 config.memoryProfile.addRequest()
-            req = RequestTwisted(request, name, reactor,
-                                 properties=config.properties)
+            req = TWISTED.Request(request, name, reactor, properties=config.properties)
             if config.hotshotProfile:
                 threads.deferToThread(config.hotshotProfile.runcall, req.run)
             else:
--- a/MoinMoin/server/wsgi.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/server/wsgi.py	Tue May 16 09:43:18 2006 +1200
@@ -5,10 +5,10 @@
     @license: GNU GPL, see COPYING for details.
 """
 
-from MoinMoin.request import RequestWSGI
+from MoinMoin.request import WSGI
 
 def moinmoinApp(environ, start_response):
-    request = RequestWSGI(environ)
+    request = WSGI.Request(environ)
     request.run()
     start_response(request.status, request.headers)
     return [request.output()]
--- a/MoinMoin/stats/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
--- a/MoinMoin/stats/hitcounts.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/stats/hitcounts.py	Tue May 16 09:43:18 2006 +1200
@@ -16,8 +16,6 @@
 
 from MoinMoin import caching, config, wikiutil
 from MoinMoin.Page import Page
-from MoinMoin.util import MoinMoinNoFooter
-from MoinMoin.formatter.text_html import Formatter
 from MoinMoin.logfile import eventlog, logfile
 
 
@@ -25,7 +23,6 @@
     _ = request.getText
 
     if not request.cfg.chart_options:
-        request.formatter = Formatter(request)
         return text(pagename, request, params)
 
     if _debug:
@@ -251,5 +248,4 @@
     # copy the image
     image.reset()
     shutil.copyfileobj(image, request, 8192)
-    raise MoinMoinNoFooter
 
--- a/MoinMoin/stats/pagesize.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/stats/pagesize.py	Tue May 16 09:43:18 2006 +1200
@@ -12,7 +12,6 @@
 
 from MoinMoin import wikiutil
 from MoinMoin.Page import Page
-from MoinMoin.util import MoinMoinNoFooter
 
 
 def linkto(pagename, request, params=''):
@@ -124,5 +123,4 @@
     # copy the image
     image.reset()
     shutil.copyfileobj(image, request, 8192)
-    raise MoinMoinNoFooter
 
--- a/MoinMoin/stats/useragents.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/stats/useragents.py	Tue May 16 09:43:18 2006 +1200
@@ -15,7 +15,6 @@
 
 from MoinMoin import wikiutil, caching 
 from MoinMoin.Page import Page
-from MoinMoin.util import MoinMoinNoFooter
 from MoinMoin.logfile import eventlog, logfile
 
 
@@ -183,5 +182,4 @@
     # copy the image
     image.reset()
     shutil.copyfileobj(image, request, 8192)
-    raise MoinMoinNoFooter
 
--- a/MoinMoin/support/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
--- a/MoinMoin/theme/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-*.pyc
-*.pyo
-{arch}
-.arch-ids
-
--- a/MoinMoin/theme/__init__.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/theme/__init__.py	Tue May 16 09:43:18 2006 +1200
@@ -1010,7 +1010,7 @@
         @rtype: unicode
         @return: iconbar html
         """
-        page = d['page']       
+        page = d['page']
         if not self.shouldShowEditbar(page):
             return ''
 
@@ -1379,3 +1379,288 @@
         lang = self.request.content_lang
         return ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
 
+    # stuff from wikiutil.py
+    def send_title(self, text, **keywords):
+        """
+        Output the page header (and title).
+
+        TODO: check all code that call us and add page keyword for the
+        current page being rendered.
+        
+        @param text: the title text
+        @keyword link: URL for the title
+        @keyword msg: additional message (after saving)
+        @keyword pagename: 'PageName'
+        @keyword page: the page instance that called us.
+        @keyword print_mode: 1 (or 0)
+        @keyword editor_mode: 1 (or 0)
+        @keyword media: css media type, defaults to 'screen'
+        @keyword allow_doubleclick: 1 (or 0)
+        @keyword html_head: additional <head> code
+        @keyword body_attr: additional <body> attributes
+        @keyword body_onload: additional "onload" JavaScript code
+        """
+        request = self.request
+        _ = request.getText
+        
+        if keywords.has_key('page'):
+            page = keywords['page']
+            pagename = page.page_name
+        else:
+            pagename = keywords.get('pagename', '')
+            page = Page(request, pagename)
+        
+        scriptname = request.getScriptname()
+        pagename_quoted = wikiutil.quoteWikinameURL(pagename)
+
+        # get name of system pages
+        page_front_page = wikiutil.getFrontPage(request).page_name
+        page_help_contents = wikiutil.getSysPage(request, 'HelpContents').page_name
+        page_title_index = wikiutil.getSysPage(request, 'TitleIndex').page_name
+        page_site_navigation = wikiutil.getSysPage(request, 'SiteNavigation').page_name
+        page_word_index = wikiutil.getSysPage(request, 'WordIndex').page_name
+        page_user_prefs = wikiutil.getSysPage(request, 'UserPreferences').page_name
+        page_help_formatting = wikiutil.getSysPage(request, 'HelpOnFormatting').page_name
+        page_find_page = wikiutil.getSysPage(request, 'FindPage').page_name
+        home_page = wikiutil.getInterwikiHomePage(request) # XXX sorry theme API change!!! Either None or tuple (wikiname,pagename) now.
+        page_parent_page = getattr(page.getParentPage(), 'page_name', None)
+        
+        # Prepare the HTML <head> element
+        user_head = [request.cfg.html_head]
+
+        # include charset information - needed for moin_dump or any other case
+        # when reading the html without a web server
+        user_head.append('''<meta http-equiv="Content-Type" content="%s;charset=%s">\n''' % (page.output_mimetype, page.output_charset))
+
+        meta_keywords = request.getPragma('keywords')
+        meta_desc = request.getPragma('description')
+        if meta_keywords:
+            user_head.append('<meta name="keywords" content="%s">\n' % escape(meta_keywords, 1))
+        if meta_desc:
+            user_head.append('<meta name="description" content="%s">\n' % escape(meta_desc, 1))
+
+        # search engine precautions / optimization:
+        # if it is an action or edit/search, send query headers (noindex,nofollow):
+        if request.query_string:
+            user_head.append(request.cfg.html_head_queries)
+        elif request.request_method == 'POST':
+            user_head.append(request.cfg.html_head_posts)
+        # if it is a special page, index it and follow the links - we do it
+        # for the original, English pages as well as for (the possibly
+        # modified) frontpage:
+        elif pagename in [page_front_page, request.cfg.page_front_page,
+                          page_title_index, 'TitleIndex',
+                          page_find_page, 'FindPage',
+                          page_site_navigation, 'SiteNavigation',
+                          'RecentChanges',]:
+            user_head.append(request.cfg.html_head_index)
+        # if it is a normal page, index it, but do not follow the links, because
+        # there are a lot of illegal links (like actions) or duplicates:
+        else:
+            user_head.append(request.cfg.html_head_normal)
+
+        if keywords.has_key('pi_refresh') and keywords['pi_refresh']:
+            user_head.append('<meta http-equiv="refresh" content="%(delay)d;URL=%(url)s">' % keywords['pi_refresh'])
+        
+        # output buffering increases latency but increases throughput as well
+        output = []
+        # later: <html xmlns=\"http://www.w3.org/1999/xhtml\">
+        output.append("""\
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+%s
+%s
+%s
+""" % (
+            ''.join(user_head),
+            self.html_head({
+                'page': page,
+                'title': wikiutil.escape(text),
+                'sitename': wikiutil.escape(request.cfg.html_pagetitle or request.cfg.sitename),
+                'print_mode': keywords.get('print_mode', False),
+                'media': keywords.get('media', 'screen'),
+            }),
+            keywords.get('html_head', ''),
+        ))
+
+        # Links
+        output.append('<link rel="Start" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_front_page)))
+        if pagename:
+            output.append('<link rel="Alternate" title="%s" href="%s/%s?action=raw">\n' % (
+                _('Wiki Markup'), scriptname, pagename_quoted,))
+            output.append('<link rel="Alternate" media="print" title="%s" href="%s/%s?action=print">\n' % (
+                _('Print View'), scriptname, pagename_quoted,))
+
+            # !!! currently disabled due to Mozilla link prefetching, see
+            # http://www.mozilla.org/projects/netlib/Link_Prefetching_FAQ.html
+            #~ all_pages = request.getPageList()
+            #~ if all_pages:
+            #~     try:
+            #~         pos = all_pages.index(pagename)
+            #~     except ValueError:
+            #~         # this shopuld never happend in theory, but let's be sure
+            #~         pass
+            #~     else:
+            #~         request.write('<link rel="First" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[0]))
+            #~         if pos > 0:
+            #~             request.write('<link rel="Previous" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[pos-1])))
+            #~         if pos+1 < len(all_pages):
+            #~             request.write('<link rel="Next" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[pos+1])))
+            #~         request.write('<link rel="Last" href="%s/%s">\n' % (request.getScriptname(), quoteWikinameURL(all_pages[-1])))
+
+            if page_parent_page:
+                output.append('<link rel="Up" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_parent_page)))
+
+        # write buffer because we call AttachFile
+        request.write(''.join(output))
+        output = []
+
+        if pagename:
+            from MoinMoin.action import AttachFile
+            AttachFile.send_link_rel(request, pagename)
+
+        output.extend([
+            '<link rel="Search" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_find_page)),
+            '<link rel="Index" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_title_index)),
+            '<link rel="Glossary" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_word_index)),
+            '<link rel="Help" href="%s/%s">\n' % (scriptname, wikiutil.quoteWikinameURL(page_help_formatting)),
+                      ])
+        
+        output.append("</head>\n")
+        request.write(''.join(output))
+        output = []
+        request.flush()
+
+        # start the <body>
+        bodyattr = []
+        if keywords.has_key('body_attr'):
+            bodyattr.append(' ')
+            bodyattr.append(keywords['body_attr'])
+
+        # Add doubleclick edit action
+        if (pagename and keywords.get('allow_doubleclick', 0) and
+            not keywords.get('print_mode', 0) and
+            request.user.edit_on_doubleclick):
+            if request.user.may.write(pagename): # separating this gains speed
+                querystr = wikiutil.escape(makeQueryString({'action': 'edit'}))
+                # TODO: remove escape=0 in 2.0
+                url = page.url(request, querystr, escape=0)
+                bodyattr.append(''' ondblclick="location.href='%s'" ''' % url)
+
+        # Set body to the user interface language and direction
+        bodyattr.append(' %s' % self.ui_lang_attr())
+        
+        body_onload = keywords.get('body_onload', '')
+        if body_onload:
+            bodyattr.append(''' onload="%s"''' % body_onload)
+        output.append('\n<body%s>\n' % ''.join(bodyattr))
+
+        # Output -----------------------------------------------------------
+
+        # If in print mode, start page div and emit the title
+        if keywords.get('print_mode', 0):
+            d = {'title_text': text, 'title_link': None, 'page': page,}
+            request.themedict = d
+            output.append(self.startPage())
+            output.append(self.interwiki(d))      
+            output.append(self.title(d))      
+
+        # In standard mode, emit theme.header
+        else:
+            # prepare dict for theme code:
+            d = {
+                'theme': self.name,
+                'script_name': scriptname,
+                'title_text': text,
+                'title_link': keywords.get('link', ''),
+                'logo_string': request.cfg.logo_string,
+                'site_name': request.cfg.sitename,
+                'page': page,
+                'pagesize': pagename and page.size() or 0,
+                'last_edit_info': pagename and page.lastEditInfo() or '',
+                'page_name': pagename or '',
+                'page_find_page': page_find_page,
+                'page_front_page': page_front_page,
+                'home_page': home_page,
+                'page_help_contents': page_help_contents,
+                'page_help_formatting': page_help_formatting,
+                'page_parent_page': page_parent_page,
+                'page_title_index': page_title_index,
+                'page_word_index': page_word_index,
+                'page_user_prefs': page_user_prefs,
+                'user_name': request.user.name,
+                'user_valid': request.user.valid,
+                'user_prefs': (page_user_prefs, request.user.name)[request.user.valid],
+                'msg': keywords.get('msg', ''),
+                'trail': keywords.get('trail', None),
+                # Discontinued keys, keep for a while for 3rd party theme developers
+                'titlesearch': 'use self.searchform(d)',
+                'textsearch': 'use self.searchform(d)',
+                'navibar': ['use self.navibar(d)'],
+                'available_actions': ['use self.request.availableActions(page)'],
+            }
+
+            # add quoted versions of pagenames
+            newdict = {}
+            for key in d:
+                if key.startswith('page_'):
+                    if not d[key] is None:
+                        newdict['q_'+key] = wikiutil.quoteWikinameURL(d[key])
+                    else:
+                        newdict['q_'+key] = None
+            d.update(newdict)
+            request.themedict = d
+
+            # now call the theming code to do the rendering
+            if keywords.get('editor_mode', 0):
+                output.append(self.editorheader(d))
+            else:
+                output.append(self.header(d))
+        
+        # emit it
+        request.write(''.join(output))
+        output = []
+        request.flush()
+
+    def send_footer(self, pagename, **keywords):
+        """
+        Output the page footer.
+
+        @param pagename: WikiName of the page
+        @keyword print_mode: true, when page is displayed in Print mode
+        """
+        request = self.request
+        d = request.themedict
+
+        # Emit end of page in print mode, or complete footer in standard mode
+        if keywords.get('print_mode', 0):
+            request.write(self.pageinfo(d['page']))
+            request.write(self.endPage())
+        else:
+            request.write(self.footer(d, **keywords))
+
+    # stuff moved from request.py
+    def send_closing_html(self):
+        """ generate timing info html and closing html tag,
+            everyone calling send_title must call this at the end to close
+            the body and html tags.
+        """
+        request = self.request
+
+        # as this is the last chance to emit some html, we stop the clocks:
+        request.clock.stop('run')
+        request.clock.stop('total')
+
+        # Close html code
+        if not request.no_closing_html_code:
+            if (request.cfg.show_timings and
+                request.form.get('action', [None])[0] != 'print'):
+                request.write('<ul id="timings">\n')
+                for t in request.clock.dump():
+                    request.write('<li>%s</li>\n' % t)
+                request.write('</ul>\n')
+            #request.write('<!-- auth_method == %s -->' % repr(request.user.auth_method))
+            request.write('</body>\n</html>\n\n')
+
+
--- a/MoinMoin/util/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
--- a/MoinMoin/util/__init__.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/util/__init__.py	Tue May 16 09:43:18 2006 +1200
@@ -46,14 +46,6 @@
 
 
 #############################################################################
-### Exceptions
-#############################################################################
-
-class MoinMoinNoFooter(Exception):
-    """Raised by actions to prevent output of a page footer (with timings)."""
-    pass
-
-#############################################################################
 ### Misc
 #############################################################################
 
@@ -97,7 +89,7 @@
     if hasattr(sys, 'getwindowsversion'):
         if sys.getwindowsversion()[3] == 1:
             return True
-    elif os.environ.get('comspec', '').find('command'):
+    elif 'command' in os.environ.get('comspec', ''):
         return True
     return False
 
--- a/MoinMoin/util/wikiext.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/util/wikiext.py	Tue May 16 09:43:18 2006 +1200
@@ -4,7 +4,7 @@
 
     The stuff in this module is especially geared towards
     writing extensions / plugins, i.e. additional actions,
-    macros, processors, parsers and formatters.
+    macros, parsers and formatters.
 
     See MoinMoin.wikiutil for more.
 
--- a/MoinMoin/version.py	Mon May 15 10:58:04 2006 +1200
+++ b/MoinMoin/version.py	Tue May 16 09:43:18 2006 +1200
@@ -14,7 +14,7 @@
     patchlevel = 'release'
 
 project = "MoinMoin"
-release  = '1.5.3'
+release  = '1.6.0alpha'
 revision = patchlevel
 
 def update():
--- a/MoinMoin/webapi/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
--- a/MoinMoin/widget/.cvsignore	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-*.pyo
-*.pyc
-{arch}
-.arch-ids
--- a/MoinMoin/wikiaction.py	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,904 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - Action Handlers
-
-    Actions are triggered by the user clicking on special links on the page
-    (like the icons in the title, or the "EditText" link at the bottom). The
-    name of the action is passed in the "action" CGI parameter.
-
-    The sub-package "MoinMoin.action" contains external actions, you can
-    place your own extensions there (similar to extension macros). User
-    actions that start with a capital letter will be displayed in a list
-    at the bottom of each page.
-
-    User actions starting with a lowercase letter can be used to work
-    together with a user macro; those actions a likely to work only if
-    invoked BY that macro, and are thus hidden from the user interface.
-
-    @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>
-    @license: GNU GPL, see COPYING for details.
-"""
-
-import os, re, time
-from MoinMoin import config, util, wikiutil
-from MoinMoin.Page import Page
-from MoinMoin.util import MoinMoinNoFooter, pysupport
-from MoinMoin.logfile import editlog
-
-#############################################################################
-### Misc Actions
-#############################################################################
-
-def do_goto(pagename, request):
-    """ redirect to another page """
-    target = request.form.get('target', [''])[0]
-    request.http_redirect(Page(request, target).url(request))
-    request.finish()
-    raise MoinMoinNoFooter
-
-def do_diff(pagename, request):
-    """ Handle "action=diff"
-        checking for either a "rev=formerrevision" parameter
-        or rev1 and rev2 parameters
-    """
-    if not request.user.may.read(pagename):
-        Page(request, pagename).send_page(request)
-        return
-
-    try:
-        date = request.form['date'][0]
-        try:
-            date = long(date) # must be long for py 2.2.x
-        except StandardError:
-            date = 0
-    except KeyError:
-        date = 0
-
-    try:
-        rev1 = request.form['rev1'][0]
-        try:
-            rev1 = int(rev1)
-        except StandardError:
-            rev1 = 0
-    except KeyError:
-        rev1 = -1
-
-    try:
-        rev2 = request.form['rev2'][0]
-        try:
-            rev2 = int(rev2)
-        except StandardError:
-            rev2 = 0
-    except KeyError:
-        rev2 = 0
-
-    if rev1 == -1 and rev2 == 0:
-        try:
-            rev1 = request.form['rev'][0]
-            try:
-                rev1 = int(rev1)
-            except StandardError:
-                rev1 = -1
-        except KeyError:
-            rev1 = -1
- 
-    # spacing flag?
-    try:
-        ignorews = int(request.form['ignorews'][0])
-    except (KeyError, ValueError, TypeError):
-        ignorews = 0
-
-    _ = request.getText
-    
-    # get a list of old revisions, and back out if none are available
-    currentpage = Page(request, pagename)
-    revisions = currentpage.getRevList()
-    if len(revisions) < 2:
-        currentpage.send_page(request, msg=_("No older revisions available!"))
-        return
-
-    if date: # this is how we get called from RecentChanges
-        rev1 = 0
-        log = editlog.EditLog(request, rootpagename=pagename)
-        for line in log.reverse():
-            if date >= line.ed_time_usecs and int(line.rev) != 99999999:
-                rev1 = int(line.rev)
-                break
-        else:
-            rev1 = 1
-        rev2 = 0
-
-    # Start output
-    # This action generate content in the user language
-    request.setContentLanguage(request.lang)
-
-    request.http_headers()
-    wikiutil.send_title(request, _('Diff for "%s"') % (pagename,), pagename=pagename, allow_doubleclick=1)
-  
-    if (rev1>0 and rev2>0 and rev1>rev2) or (rev1==0 and rev2>0):
-        rev1,rev2 = rev2,rev1
-          
-    oldrev1,oldcount1 = None,0
-    oldrev2,oldcount2 = None,0
-    # get the filename of the version to compare to
-    edit_count = 0
-    for rev in revisions:
-        edit_count += 1
-        if rev <= rev1: 
-            oldrev1,oldcount1 = rev,edit_count
-        if rev2 and rev >= rev2: 
-            oldrev2,oldcount2 = rev,edit_count
-        if (oldrev1 and oldrev2) or (oldrev1 and not rev2):
-            break
-    
-    if rev1 == -1:
-        oldpage = Page(request, pagename, rev=revisions[1])
-        oldcount1 = oldcount1 - 1
-    elif rev1 == 0:
-        oldpage = currentpage
-        # oldcount1 is still on init value 0
-    else:
-        if oldrev1:
-            oldpage = Page(request, pagename, rev=oldrev1)
-        else:
-            oldpage = Page(request, "$EmptyPage$") # hack
-            oldpage.set_raw_body("")    # avoid loading from disk
-            oldrev1 = 0 # XXX
-              
-    if rev2 == 0:
-        newpage = currentpage
-        # oldcount2 is still on init value 0
-    else:
-        if oldrev2:
-            newpage = Page(request, pagename, rev=oldrev2)
-        else:
-            newpage = Page(request, "$EmptyPage$") # hack
-            newpage.set_raw_body("")    # avoid loading from disk
-            oldrev2 = 0 # XXX
-    
-    edit_count = abs(oldcount1 - oldcount2)
-
-    # this should use the formatter, but there is none?
-    request.write('<div id="content">\n') # start content div
-    request.write('<p class="diff-header">')
-    request.write(_('Differences between revisions %d and %d') % (oldpage.get_real_rev(), newpage.get_real_rev()))
-    if edit_count > 1:
-        request.write(' ' + _('(spanning %d versions)') % (edit_count,))
-    request.write('</p>')
-  
-    if request.user.show_fancy_diff:
-        from MoinMoin.util.diff import diff
-        request.write(diff(request, oldpage.get_raw_body(), newpage.get_raw_body()))
-        newpage.send_page(request, count_hit=0, content_only=1, content_id="content-below-diff")
-    else:
-        lines = wikiutil.linediff(oldpage.getlines(), newpage.getlines())
-        if not lines:
-            msg = _("No differences found!")
-            if edit_count > 1:
-                msg = msg + '<p>' + _('The page was saved %(count)d times, though!') % {
-                    'count': edit_count}
-            request.write(msg)
-        else:
-            if ignorews:
-                request.write(_('(ignoring whitespace)') + '<br>')
-            else:
-                qstr = 'action=diff&ignorews=1'
-                if rev1: qstr = '%s&rev1=%s' % (qstr, rev1)
-                if rev2: qstr = '%s&rev2=%s' % (qstr, rev2)
-                request.write(Page(request, pagename).link_to(request,
-                    text=_('Ignore changes in the amount of whitespace'),
-                    querystr=qstr) + '<p>')
-
-            request.write('<pre>')
-            for line in lines:
-                if line[0] == "@":
-                    request.write('<hr>')
-                request.write(wikiutil.escape(line)+'\n')
-            request.write('</pre>')
-
-    request.write('</div>\n') # end content div
-    wikiutil.send_footer(request, pagename)
-
-
-def do_info(pagename, request):
-    if not request.user.may.read(pagename):
-        Page(request, pagename).send_page(request)
-        return
-
-    def general(page, pagename, request):
-        _ = request.getText
-
-        request.write('<h2>%s</h2>\n' % _('General Information'))
-        
-        # show page size
-        request.write(("<p>%s</p>" % _("Page size: %d")) % page.size())
-
-        # show SHA digest fingerprint
-        import sha
-        digest = sha.new(page.get_raw_body().encode(config.charset)).hexdigest().upper()
-        request.write('<p>%(label)s <tt>%(value)s</tt></p>' % {
-            'label': _("SHA digest of this page's content is:"),
-            'value': digest,
-            })
-
-        # show attachments (if allowed)
-        attachment_info = getHandler(request, 'AttachFile', 'info')
-        if attachment_info:
-            request.write(attachment_info(pagename, request))
-
-        # show subscribers
-        subscribers = page.getSubscribers(request,  include_self=1, return_users=1)
-        if subscribers:
-            request.write('<p>', _('The following users subscribed to this page:'))
-            for lang in subscribers.keys():
-                request.write('<br>[%s] ' % lang)
-                for user in subscribers[lang]:
-                    # do NOT disclose email addr, only WikiName
-                    userhomepage = Page(request, user.name)
-                    if userhomepage.exists():
-                        request.write(userhomepage.link_to(request) + ' ')
-                    else:
-                        request.write(user.name + ' ')
-            request.write('</p>')
-
-        # show links
-        links = page.getPageLinks(request)
-        if links:
-            request.write('<p>', _('This page links to the following pages:'), '<br>')
-            for linkedpage in links:
-                request.write("%s%s " % (Page(request, linkedpage).link_to(request), ",."[linkedpage == links[-1]]))
-            request.write("</p>")
-
-
-    def history(page, pagename, request):
-        # show history as default
-        _ = request.getText
-
-        # open log for this page
-        from MoinMoin.logfile import editlog
-        from MoinMoin.util.dataset import TupleDataset, Column
-
-        history = TupleDataset()
-        history.columns = [
-            Column('rev', label='#', align='right'),
-            Column('mtime', label=_('Date'), align='right'),
-            Column('size',  label=_('Size'), align='right'),
-            Column('diff', label='<input type="submit" value="%s">' % (_("Diff"))),
-            Column('editor', label=_('Editor'), hidden=not request.cfg.show_names),
-            Column('comment', label=_('Comment')),
-            Column('action', label=_('Action')),
-            ]
-
-        # generate history list
-        revisions = page.getRevList()
-        versions = len(revisions)
-
-        may_revert = request.user.may.revert(pagename)
-        
-        # read in the complete log of this page
-        log = editlog.EditLog(request, rootpagename=pagename)
-        count = 0
-        for line in log.reverse():
-            rev = int(line.rev)
-            actions = ""
-            if line.action in ['SAVE','SAVENEW','SAVE/REVERT',]:
-                if count == 0: # latest page
-                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
-                        text=_('view'),
-                        querystr=''))
-                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
-                        text=_('raw'),
-                        querystr='action=raw'))
-                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
-                        text=_('print'),
-                        querystr='action=print'))
-                else:
-                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
-                        text=_('view'),
-                        querystr='action=recall&rev=%d' % rev))
-                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
-                        text=_('raw'),
-                        querystr='action=raw&rev=%d' % rev))
-                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
-                        text=_('print'),
-                        querystr='action=print&rev=%d' % rev))
-                    if may_revert:
-                        actions = '%s&nbsp;%s' % (actions, page.link_to(request,
-                            text=_('revert'),
-                            querystr='action=revert&rev=%d' % (rev,)))
-                if count == 0:
-                    rchecked=' checked="checked"'
-                    lchecked = ''
-                elif count == 1:
-                    lchecked=' checked="checked"'
-                    rchecked = ''
-                else:
-                    lchecked = rchecked = ''
-                diff = '<input type="radio" name="rev1" value="%d"%s><input type="radio" name="rev2" value="%d"%s>' % (rev,lchecked,rev,rchecked)
-      
-                comment = line.comment
-                if not comment and line.action.find('/REVERT') != -1:
-                        comment = _("Revert to revision %(rev)d.") % {'rev': int(line.extra)}
-                size = page.size(rev=rev)
-            else: # ATT*
-                rev = '-'
-                diff = '-'
-                
-                filename = wikiutil.url_unquote(line.extra)
-                comment = "%s: %s %s" % (line.action, filename, line.comment)
-                size = 0
-                if line.action != 'ATTDEL':
-                    from MoinMoin.action import AttachFile
-                    page_dir = AttachFile.getAttachDir(request, pagename)
-                    filepath = os.path.join(page_dir, filename)
-                    try:
-                        # FIXME, wrong path on non-std names
-                        size = os.path.getsize(filepath)
-                    except:
-                        pass
-                    if line.action == 'ATTNEW':
-                        actions = '%s&nbsp;%s' % (actions, page.link_to(request,
-                            text=_('view'),
-                            querystr='action=AttachFile&do=view&target=%s' % filename))
-                    elif line.action == 'ATTDRW':
-                        actions = '%s&nbsp;%s' % (actions, page.link_to(request,
-                            text=_('edit'),
-                            querystr='action=AttachFile&drawing=%s' % filename.replace(".draw","")))
-
-                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
-                        text=_('get'),
-                        querystr='action=AttachFile&do=get&target=%s' % filename))
-                    actions = '%s&nbsp;%s' % (actions, page.link_to(request,
-                        text=_('del'),
-                        querystr='action=AttachFile&do=del&target=%s' % filename))
-                    # XXX use?: wikiutil.escape(filename)
-
-            history.addRow((
-                rev,
-                request.user.getFormattedDateTime(wikiutil.version2timestamp(line.ed_time_usecs)),
-                str(size),
-                diff,
-                line.getEditor(request) or _("N/A"),
-                wikiutil.escape(comment) or '&nbsp;',
-                actions,
-            ))
-            count += 1
-            if count >= 100:
-                break
-
-        # print version history
-        from MoinMoin.widget.browser import DataBrowserWidget
-
-        request.write('<h2>%s</h2>\n' % _('Revision History'))
-
-        if not count: # there was no entry in logfile
-            request.write(_('No log entries found.'))
-            return
-
-        # TODO: this form activates revert, which should use POST, but
-        # other actions should use get. Maybe we should put the revert
-        # into the page view itself, and not in this form.
-        request.write('<form method="GET" action="">\n')
-        request.write('<div id="page-history">\n')
-        request.write('<input type="hidden" name="action" value="diff">\n')
-
-        history_table = DataBrowserWidget(request)
-        history_table.setData(history)
-        history_table.render()
-        request.write('</div>\n')
-        request.write('</form>\n')
-
-
-    _ = request.getText
-    page = Page(request, pagename)
-    qpagename = wikiutil.quoteWikinameURL(pagename)
-    title = page.split_title(request)
-
-    request.http_headers()
-
-    # This action uses page or wiki language TODO: currently
-    # page.language is broken and not available now, when we fix it,
-    # this will be automatically fixed.
-    lang = page.language or request.cfg.language_default
-    request.setContentLanguage(lang)
-    
-    wikiutil.send_title(request, _('Info for "%s"') % (title,), pagename=pagename)
-
-    historylink =  wikiutil.link_tag(request, '%s?action=info' % qpagename,
-        _('Show "%(title)s"') % {'title': _('Revision History')})
-    generallink =  wikiutil.link_tag(request, '%s?action=info&amp;general=1' % qpagename,
-        _('Show "%(title)s"') % {'title': _('General Page Infos')})
-    hitcountlink = wikiutil.link_tag(request, '%s?action=info&amp;hitcounts=1' % qpagename,
-        _('Show chart "%(title)s"') % {'title': _('Page hits and edits')})
-    
-    request.write('<div id="content">\n') # start content div
-    request.write("<p>[%s]  [%s]  [%s]</p>" % (historylink, generallink, hitcountlink))
-
-    show_hitcounts = int(request.form.get('hitcounts', [0])[0]) != 0
-    show_general = int(request.form.get('general', [0])[0]) != 0
-    
-    if show_hitcounts:
-        from MoinMoin.stats import hitcounts
-        request.write(hitcounts.linkto(pagename, request, 'page=' + wikiutil.url_quote_plus(pagename)))
-    elif show_general:
-        general(page, pagename, request)
-    else:
-        history(page, pagename, request)
-        
-    request.write('</div>\n') # end content div
-    wikiutil.send_footer(request, pagename)
-
-
-def do_recall(pagename, request):
-    # We must check if the current page has different ACLs.
-    if not request.user.may.read(pagename):
-        Page(request, pagename).send_page(request)
-        return
-    if request.form.has_key('rev'):
-        try:
-            rev = request.form['rev'][0]
-            try:
-                rev = int(rev)
-            except StandardError:
-                rev = 0
-        except KeyError:
-            rev = 0
-        Page(request, pagename, rev=rev).send_page(request)
-    else:
-        Page(request, pagename).send_page(request)
-
-
-def do_show(pagename, request):
-    # We must check if the current page has different ACLs.
-    if not request.user.may.read(pagename):
-        Page(request, pagename).send_page(request)
-        return
-    if request.form.has_key('rev'):
-        try:
-            rev = request.form['rev'][0]
-            try:
-                rev = int(rev)
-            except StandardError:
-                rev = 0
-        except KeyError:
-            rev = 0
-        Page(request, pagename, rev=rev).send_page(request, count_hit=1)
-    else:
-        request.cacheable = 1
-        Page(request, pagename).send_page(request, count_hit=1)
-
-
-def do_refresh(pagename, request):
-    """ Handle refresh action """
-    # Without arguments, refresh action will refresh the page text_html
-    # cache.
-    arena = request.form.get('arena', ['Page.py'])[0]
-    if arena == 'Page.py':
-        arena = Page(request, pagename)
-    key = request.form.get('key', ['text_html'])[0]
-
-    # Remove cache entry (if exists), and send the page
-    from MoinMoin import caching
-    caching.CacheEntry(request, arena, key).remove()
-    caching.CacheEntry(request, arena, "pagelinks").remove()
-    do_show(pagename, request)
-
-
-def do_print(pagename, request):
-    do_show(pagename, request)
-
-
-def do_content(pagename, request):
-    request.http_headers()
-    page = Page(request, pagename)
-    request.write('<!-- Transclusion of %s -->' % request.getQualifiedURL(page.url(request)))
-    page.send_page(request, count_hit=0, content_only=1)
-    raise MoinMoinNoFooter
-
-
-def do_revert(pagename, request):
-    from MoinMoin.PageEditor import PageEditor
-    _ = request.getText
-
-    if not request.user.may.revert(pagename):
-        return Page(request, pagename).send_page(request,
-            msg = _('You are not allowed to revert this page!'))
-
-    rev = int(request.form['rev'][0])
-    revstr = '%08d' % rev
-    oldpg = Page(request, pagename, rev=rev)
-    pg = PageEditor(request, pagename)
-
-    try:
-        savemsg = pg.saveText(oldpg.get_raw_body(), 0, extra=revstr,
-                              action="SAVE/REVERT")
-    except pg.SaveError, msg:
-        # msg contain a unicode string
-        savemsg = unicode(msg)
-    request.reset()
-    pg.send_page(request, msg=savemsg)
-    return None
-
-def do_edit(pagename, request):
-    _ = request.getText
-
-    if not request.user.may.write(pagename):
-        Page(request, pagename).send_page(request,
-            msg = _('You are not allowed to edit this page.'))
-        return
-
-    valideditors = ['text', 'gui',]
-    editor = ''
-    if request.user.valid:
-        editor = request.user.editor_default
-    if editor not in valideditors:
-        editor = request.cfg.editor_default
-    
-    editorparam = request.form.get('editor', [editor])[0]
-    if editorparam == "guipossible":
-        lasteditor = editor
-    elif editorparam == "textonly":
-        editor = lasteditor = 'text'
-    else:
-        editor = lasteditor = editorparam
-
-    if request.cfg.editor_force:
-        editor = request.cfg.editor_default
-
-    # if it is still nothing valid, we just use the text editor
-    if editor not in valideditors:
-        editor = 'text'
-            
-    savetext = request.form.get('savetext', [None])[0]
-    rev = int(request.form.get('rev', ['0'])[0])
-    comment = request.form.get('comment', [u''])[0]
-    category = request.form.get('category', [None])[0]
-    rstrip = int(request.form.get('rstrip', ['0'])[0])
-    trivial = int(request.form.get('trivial', ['0'])[0])
-
-    if request.form.has_key('button_switch'):
-        if editor == 'text':
-            editor = 'gui'
-        else: # 'gui'
-            editor = 'text'
-
-    # load right editor class
-    if editor == 'gui':
-        from MoinMoin.PageGraphicalEditor import PageGraphicalEditor
-        pg = PageGraphicalEditor(request, pagename)
-    else: # 'text'
-        from MoinMoin.PageEditor import PageEditor
-        pg = PageEditor(request, pagename)
-
-    # is invoked without savetext start editing
-    if savetext is None:
-        pg.sendEditor()
-        return
-  
-    # did user hit cancel button?
-    cancelled = request.form.has_key('button_cancel')
-
-    # convert input from Graphical editor
-    from MoinMoin.converter.text_html_text_x_moin import convert, ConvertError
-    try:
-        if lasteditor == 'gui':
-            savetext = convert(request, pagename, savetext)
-                
-        # IMPORTANT: normalize text from the form. This should be done in
-        # one place before we manipulate the text.
-        savetext = pg.normalizeText(savetext, stripspaces=rstrip)
-    except ConvertError:
-        # we don't want to throw an exception if user cancelled anyway
-        if not cancelled:
-            raise
-
-    if cancelled:
-        pg.sendCancel(savetext or "", rev)
-        return
-
-    comment = wikiutil.clean_comment(comment)
-
-    # Add category
-
-    # TODO: this code does not work with extended links, and is doing
-    # things behind your back, and in general not needed. Either we have
-    # a full interface for categories (add, delete) or just add them by
-    # markup.
-    
-    if category and category != _('<No addition>', formatted=False): # opera 8.5 needs this
-        # strip trailing whitespace
-        savetext = savetext.rstrip()
-
-        # Add category separator if last non-empty line contains
-        # non-categories.
-        lines = filter(None, savetext.splitlines())
-        if lines:
-            
-            #TODO: this code is broken, will not work for extended links
-            #categories, e.g ["category hebrew"]
-            categories = lines[-1].split()
-            
-            if categories:
-                confirmed = wikiutil.filterCategoryPages(request, categories)
-                if len(confirmed) < len(categories):
-                    # This was not a categories line, add separator
-                    savetext += u'\n----\n'
-
-        # Add new category
-        if savetext and savetext[-1] != u'\n':
-            savetext += ' '
-        savetext += category + u'\n' # Should end with newline!
-
-    # Preview, spellcheck or spellcheck add new words
-    if (request.form.has_key('button_preview') or
-        request.form.has_key('button_spellcheck') or
-        request.form.has_key('button_newwords')):
-        pg.sendEditor(preview=savetext, comment=comment)
-    
-    # Preview with mode switch
-    elif request.form.has_key('button_switch'):
-        pg.sendEditor(preview=savetext, comment=comment, staytop=1)
-    
-    # Save new text
-    else:
-        try:
-            savemsg = pg.saveText(savetext, rev, trivial=trivial, comment=comment)
-        except pg.EditConflict, msg:
-            # Handle conflict and send editor
-
-            # TODO: conflict messages are duplicated from PageEditor,
-            # refactor to one place only.
-            conflict_msg = _('Someone else changed this page while you were editing!')
-            pg.set_raw_body(savetext, modified=1)
-            if pg.mergeEditConflict(rev):
-                conflict_msg = _("""Someone else saved this page while you were editing!
-Please review the page and save then. Do not save this page as it is!
-Have a look at the diff of %(difflink)s to see what has been changed.""") % {
-                    'difflink': pg.link_to(pg.request,
-                                           querystr='action=diff&rev=%d' % rev)
-                    }
-                # We don't send preview when we do merge conflict
-                pg.sendEditor(msg=conflict_msg, comment=comment)
-                return
-            else:
-                savemsg = conflict_msg
-        
-        except pg.SaveError, msg:
-            # msg contain a unicode string
-            savemsg = unicode(msg)
-
-        # Send new page after save or after unsuccessful conflict merge.
-        request.reset()
-        backto = request.form.get('backto', [None])[0]
-        if backto:
-            pg = Page(request, backto)
-
-        pg.send_page(request, msg=savemsg)
-        
-
-def do_quicklink(pagename, request):
-    """ Add the current wiki page to the user quicklinks 
-    
-    TODO: what if add or remove quicklink fail? display an error message?
-    """
-    _ = request.getText
-    msg = None
-
-    if not request.user.valid:
-        msg = _("You must login to add a quicklink.")    
-    elif request.user.isQuickLinkedTo([pagename]):
-        if request.user.removeQuicklink(pagename):
-            msg = _('Your quicklink to this page has been removed.')            
-    else:
-        if request.user.addQuicklink(pagename):
-            msg = _('A quicklink to this page has been added for you.')
-
-    Page(request, pagename).send_page(request, msg=msg)
-
-
-def do_subscribe(pagename, request):
-    """ Subscribe or unsubscribe the user to pagename
-    
-    TODO: what if subscribe failed? no message is displayed.
-    """
-    _ = request.getText
-    cfg = request.cfg
-    msg = None
-
-    if not request.user.may.read(pagename):
-        msg = _("You are not allowed to subscribe to a page you can't read.")
-
-    # Check if mail is enabled
-    elif not cfg.mail_enabled:
-        msg = _("This wiki is not enabled for mail processing.")
-
-    # Suggest visitors to login
-    elif not request.user.valid:
-        msg = _("You must log in to use subscribtions.")
-
-    # Suggest users without email to add their email address
-    elif not request.user.email:
-        msg = _("Add your email address in your UserPreferences to use subscriptions.")
-
-    elif request.user.isSubscribedTo([pagename]):
-        # Try to unsubscribe
-        if request.user.unsubscribe(pagename):
-            msg = _('Your subscribtion to this page has been removed.')
-        else:
-            msg = _("Can't remove regular expression subscription!") + u' ' + \
-                  _("Edit the subscription regular expressions in your "
-                    "UserPreferences.")
-            
-    else:
-        # Try to subscribe
-        if request.user.subscribe(pagename):
-            msg = _('You have been subscribed to this page.')
-
-    Page(request, pagename).send_page(request, msg=msg)
-
-
-def do_userform(pagename, request):
-    from MoinMoin import userform
-    savemsg = userform.savedata(request)
-    Page(request, pagename).send_page(request, msg=savemsg)
-
-def do_bookmark(pagename, request):
-    if request.form.has_key('time'):
-        if request.form['time'][0] == 'del':
-            tm = None
-        else:
-            try:
-                tm = long(request.form["time"][0]) # must be long for py 2.2.x
-            except StandardError:
-                tm = wikiutil.timestamp2version(time.time())
-    else:
-        tm = wikiutil.timestamp2version(time.time())
-  
-    if tm is None:
-        request.user.delBookmark()
-    else:
-        request.user.setBookmark(tm)
-    Page(request, pagename).send_page(request)
-  
-
-# Commented out by Florian Festi, cf. bug report about it
-# def do_macro(pagename, request):
-#     """ Execute a helper action within a macro.
-#     """
-
-#     from MoinMoin import wikimacro
-#     from MoinMoin.formatter.text_html import Formatter
-#     from MoinMoin.parser.wiki import Parser
-#     from MoinMoin.Page import Page
-#     macro_name = request.form["macro"][0]
-#     args = request.form.get('args', [''])[0]
-    
-#     parser = Parser('', request)
-#     parser.formatter = Formatter(request)
-#     parser.formatter.page = Page(request, 'dummy')
-#     request.http_headers()
-#     request.write(wikimacro.Macro(parser).execute(macro_name, args))
-#     request.finish()
-    
-#############################################################################
-### Special Actions
-#############################################################################
-
-def do_raw(pagename, request):
-    if not request.user.may.read(pagename):
-        Page(request, pagename).send_page(request)
-        return
-
-    if request.form.has_key('rev'):
-        try:
-            rev = request.form['rev'][0]
-            try:
-                rev = int(rev)
-            except StandardError:
-                rev = 0
-        except KeyError:
-            rev = 0
-        page = Page(request, pagename, rev=rev)
-    else:
-        page = Page(request, pagename)
-
-    page.send_raw()
-
-
-def do_format(pagename, request):
-    # get the MIME type
-    if request.form.has_key('mimetype'):
-        mimetype = request.form['mimetype'][0]
-    else:
-        mimetype = u"text/plain"
-
-    # try to load the formatter
-    formatterName = mimetype.translate({ord(u'/'): u'_', ord(u'.'): u'_'})
-    try:
-        Formatter = wikiutil.importPlugin(request.cfg, "formatter",
-                                          formatterName, "Formatter")
-    except wikiutil.PluginMissingError:
-        # default to plain text formatter
-        mimetype = "text/plain"
-        from MoinMoin.formatter.text_plain import Formatter
-
-    if "xml" in mimetype:
-        mimetype = "text/xml"
-    request.http_headers(["Content-Type: %s; charset=%s" % (mimetype, config.charset)])
-
-    Page(request, pagename, formatter=Formatter(request)).send_page(request)
-    raise MoinMoinNoFooter
-
-
-def do_chart(pagename, request):
-    """ Show page charts 
-    
-    """
-    _ = request.getText
-    if not request.user.may.read(pagename):
-        msg = _("You are not allowed to view this page.")
-        return request.page.send_page(request, msg=msg)
-    
-    if not request.cfg.chart_options:
-        msg = _("Charts are not available!")
-        return request.page.send_page(request, msg=msg)
-    
-    chart_type = request.form.get('type', [''])[0].strip()
-    if not chart_type:
-        msg = _('You need to provide a chart type!')
-        return request.page.send_page(request, msg=msg)
-    
-    try:
-        func = pysupport.importName("MoinMoin.stats." + chart_type, 'draw')
-    except (ImportError, AttributeError):
-        msg = _('Bad chart type "%s"!') % chart_type
-        return request.page.send_page(request, msg=msg)
-    
-    func(pagename, request)
-    raise MoinMoinNoFooter
-
-
-def do_dumpform(pagename, request):
-    data = util.dumpFormData(request.form)
-
-    request.http_headers()
-    request.write("<html><body>%s</body></html>" % data)
-    raise MoinMoinNoFooter
-
-
-def do_test(pagename, request):
-    from MoinMoin.wikitest import runTest
-    request.http_headers(["Content-type: text/plain;charset=%s" % config.charset])
-    request.write('MoinMoin Diagnosis\n======================\n\n')
-    runTest(request)
-    raise MoinMoinNoFooter
-
-
-#############################################################################
-### Dispatching
-#############################################################################
-
-def getPlugins(request):
-    dir = os.path.join(request.cfg.plugin_dir, 'action')
-    plugins = []
-    if os.path.isdir(dir):
-        plugins = pysupport.getPackageModules(os.path.join(dir, 'dummy'))
-    return dir, plugins
-
-
-def getHandler(request, action, identifier="execute"):
-    # check for excluded actions
-    if action in request.cfg.actions_excluded:
-        return None
-
-    from MoinMoin.formatter.text_html import Formatter
-    request.formatter = Formatter(request)
-
-    try:
-        handler = wikiutil.importPlugin(request.cfg, "action", action,
-                                        identifier)
-    except wikiutil.PluginMissingError:
-        handler = globals().get('do_' + action)
-        
-    return handler
-
--- a/MoinMoin/wikiform.py	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - User Forms
-
-    @copyright: 2001-2004 by Jürgen Hermann <jh@web.de>
-    @license: GNU GPL, see COPYING for details.
-"""
-
-from MoinMoin import wikiutil
-from MoinMoin.Page import Page
-
-#############################################################################
-### Form definitions
-#############################################################################
-
-_required_attributes = ['type', 'name', 'label']
-
-def parseDefinition(request, fielddef, fieldlist):
-    """ Parse a form field definition and return the HTML markup for it
-    """
-    _ = request.getText
-
-    row = '<tr><td nowrap="nowrap" valign="top">&nbsp;%s&nbsp;</td><td>%s</td></tr>\n'
-    fields, msg = wikiutil.parseAttributes(request, fielddef)
-
-    if not msg:
-        for required in _required_attributes:
-            if not fields.has_key(required):
-                msg = _('Required attribute "%(attrname)s" missing')  % {
-                    'attrname': required}
-                break
-
-    if msg:
-        # create visible error
-        result = row % (msg, fielddef)
-    elif fields['type'] == '"caption"':
-        # create a centered, bold italic caption
-        result = '<tr><td colspan="2" align="center"><em><strong>%s</strong></em></td></tr>\n' % (
-            fields['label'][1:-1])
-    else:
-        # for submit buttons, use `label` as the value
-        if fields['type'] == '"submit"':
-            fields['value'] = fields['label']
-            fields['label'] = ''
-
-        # make sure user cannot use a system name
-        fields['name'] = '"form_' + fields['name'][1:]
-        fieldlist.append(fields['name'][1:-1])
-
-        wrapper = ('<input', '>\n')
-        if fields['type'] == '"textarea"':
-            wrapper = ('<textarea', '></textarea>\n')
-
-        result = wrapper[0]
-        for key, val in fields.items():
-            if key == 'label': continue
-            result = '%s %s=%s' % (result, key, val)
-        result = result + wrapper[1]
-
-        #result = result + wikiutil.escape(`fields`)
-
-        if fields['type'] == '"submit"':
-            result = '<tr><td colspan="2" align="center">%s</td></tr>\n' % result
-        else:
-            result = row % (fields['label'][1:-1], result)
-
-    return result
-
-
-def _get_formvalues(form):
-    result = {}
-    for key in form.keys():
-        if key[:5] != 'form_': continue
-
-        val = form.get(key, ["<empty>"])
-        if type(val) is type([]):
-            # Multiple username fields specified
-            val = "|".join(val).replace('\r','')
-
-        result[key] = val
-
-    return result
-
-
-def do_formtest(pagename, request):
-    """ Test a user defined form.
-    """
-    _ = request.getText
-
-    result = []
-    for key, val in _get_formvalues(request.form).items():
-        result.append('<li><em>%s</em> = %s</li>' % (key.upper(), repr(wikiutil.escape(val))))
-    msg = '%s<ul>\n%s</ul>\n' % (_('Submitted form data:'), '\n'.join(result))
-
-    Page(request, pagename).send_page(request, msg=msg)
-
--- a/MoinMoin/wikimacro.py	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,617 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - Macro Implementation
-
-    These macros are used by the parser/wiki.py module
-    to implement complex and/or dynamic page content.
-
-    The sub-package "MoinMoin.macro" contains external
-    macros, you can place your extensions there.
-
-    @copyright: 2000-2004 by Jürgen Hermann <jh@web.de>
-    @license: GNU GPL, see COPYING for details.
-"""
-
-import re, time, os
-from MoinMoin import action, config, macro, util
-from MoinMoin import wikiutil, wikiaction, i18n
-from MoinMoin.Page import Page
-from MoinMoin.util import pysupport
-
-names = ["TitleSearch", "WordIndex", "TitleIndex",
-         "GoTo", "InterWiki", "SystemInfo", "PageCount", "UserPreferences",
-         # Macros with arguments
-         "Icon", "PageList", "Date", "DateTime", "Anchor", "MailTo", "GetVal",
-         "TemplateList",
-]
-names.extend(i18n.languages.keys())
-
-#############################################################################
-### Helpers
-#############################################################################
-
-def getNames(cfg):
-    if hasattr(cfg, 'macro_names'):
-        return cfg.macro_names
-    else:
-        lnames = names[:]
-        lnames.extend(wikiutil.getPlugins('macro', cfg))
-        return lnames
-
-def _make_index_key(index_letters, additional_html=""):
-    index_letters.sort()
-    links = map(lambda ch:
-                    '<a href="#%s">%s</a>' %
-                    (wikiutil.quoteWikinameURL(ch), ch.replace('~', 'Others')),
-                index_letters)
-    return "<p>%s%s</p>" % (' | '.join(links), additional_html)
-
-
-#############################################################################
-### Macros - Handlers for [[macroname]] markup
-#############################################################################
-
-class Macro:
-    """ Macro handler 
-    
-    There are three kinds of macros: 
-     * Builtin Macros - implemented in this file and named _macro_[name]
-     * Language Pseudo Macros - any lang the wiki knows can be use as
-       macro and is implemented here by _m_lang() 
-     * External macros - implemented in either MoinMoin.macro package, or
-       in the specific wiki instance in the plugin/macro directory
-    """
-    defaultDependency = ["time"]
-
-    Dependencies = {
-        "TitleSearch" : ["namespace"],
-        "Goto"        : [],
-        "WordIndex"   : ["namespace"],
-        "TitleIndex"  : ["namespace"],
-        "InterWiki"   : ["pages"],  # if interwikimap is editable
-        "SystemInfo"  : ["pages"],
-        "PageCount"   : ["namespace"],
-        "Icon"        : ["user"], # users have different themes and user prefs
-        "PageList"    : ["namespace"],
-        "Date"        : ["time"],
-        "DateTime"    : ["time"],
-        "UserPreferences" :["time"],
-        "Anchor"      : [],
-        "Mailto"      : ["user"],
-        "GetVal"      : ["pages"],
-        "TemplateList": ["namespace"],
-        }
-
-    # we need the lang macros to execute when html is generated,
-    # to have correct dir and lang html attributes
-    for lang in i18n.languages.keys():
-        Dependencies[lang] = []
-    
-
-    def __init__(self, parser):
-        self.parser = parser
-        self.form = self.parser.form
-        self.request = self.parser.request
-        self.formatter = self.request.formatter
-        self._ = self.request.getText
-        self.cfg = self.request.cfg
-        
-        # Initialized on execute
-        self.name = None
-
-    def execute(self, macro_name, args):
-        """ Get and execute a macro 
-        
-        Try to get a plugin macro, or a builtin macro or a language
-        macro, or just raise ImportError. 
-        """
-        self.name = macro_name
-        try:
-            execute = wikiutil.importPlugin(self.cfg, 'macro', macro_name)
-        except wikiutil.PluginMissingError:
-            try:
-                builtins = self.__class__
-                execute = getattr(builtins, '_macro_' + macro_name)
-            except AttributeError:
-                if macro_name in i18n.languages:
-                    execute = builtins._m_lang
-                else:
-                    raise ImportError("Cannot load macro %s" % macro_name)
-        return execute(self, args)
-
-    def _m_lang(self, text):
-        """ Set the current language for page content.
-        
-            Language macro are used in two ways:
-             * [lang] - set the current language until next lang macro
-             * [lang(text)] - insert text with specific lang inside page
-        """
-        if text:
-            return (self.formatter.lang(1, self.name) +
-                    self.formatter.text(text) +
-                    self.formatter.lang(0, self.name))
-        
-        self.request.current_lang = self.name
-        return ''
-  
-    def get_dependencies(self, macro_name):
-        if macro_name in self.Dependencies:
-            return self.Dependencies[macro_name]
-        try:
-            return wikiutil.importPlugin(self.request.cfg, 'macro',
-                                         macro_name, 'Dependencies')
-        except wikiutil.PluginError:
-            return self.defaultDependency
-
-    def _macro_TitleSearch(self, args):
-        return self._m_search("titlesearch")
-
-    def _m_search(self, type):
-        """ Make a search box
-
-        Make both Title Search and Full Search boxes, according to type.
-
-        @param type: search box type: 'titlesearch' or 'fullsearch'
-        @rtype: unicode
-        @return: search box html fragment
-        """
-        _ = self._
-        if self.form.has_key('value'):
-            default = wikiutil.escape(self.form["value"][0], quote=1)
-        else:
-            default = ''
-
-        # Title search settings
-        boxes = ''
-        button = _("Search Titles")
-
-        # Special code for fullsearch
-        if type == "fullsearch":
-            boxes = [
-                u'<br>',
-                u'<input type="checkbox" name="context" value="160" checked="checked">',
-                _('Display context of search results'),
-                u'<br>',
-                u'<input type="checkbox" name="case" value="1">',
-                _('Case-sensitive searching'),
-                ]
-            boxes = u'\n'.join(boxes)
-            button = _("Search Text")
-            
-        # Format
-        type = (type == "titlesearch")
-        html = [
-            u'<form method="get" action="">',
-            u'<div>',
-            u'<input type="hidden" name="action" value="fullsearch">',
-            u'<input type="hidden" name="titlesearch" value="%i">' % type,
-            u'<input type="text" name="value" size="30" value="%s">' % default,
-            u'<input type="submit" value="%s">' % button,
-            boxes,
-            u'</div>',
-            u'</form>',    
-            ]
-        html = u'\n'.join(html)
-        return self.formatter.rawHTML(html)
-    
-    def _macro_GoTo(self, args):
-        """ Make a goto box
-
-        @param args: macro arguments
-        @rtype: unicode
-        @return: goto box html fragment
-        """
-        _ = self._
-        html = [
-            u'<form method="get" action="">',
-            u'<div>',
-            u'<input type="hidden" name="action" value="goto">',
-            u'<input type="text" name="target" size="30">',
-            u'<input type="submit" value="%s">' % _("Go To Page"),
-            u'</div>',
-            u'</form>',
-            ]
-        html = u'\n'.join(html)
-        return self.formatter.rawHTML(html)
-
-    def _macro_WordIndex(self, args):
-        _ = self._
-        allpages = int(self.form.get('allpages', [0])[0]) != 0
-        # Get page list readable by current user
-        # Filter by isSystemPage if needed
-        if allpages:
-            # TODO: make this fast by caching full page list
-            pages = self.request.rootpage.getPageList()
-        else:
-            def filter(name):
-                return not wikiutil.isSystemPage(self.request, name)
-            pages = self.request.rootpage.getPageList(filter=filter)
-        map = {}
-        word_re = re.compile(u'[%s][%s]+' % (config.chars_upper, config.chars_lower), re.UNICODE)
-        for name in pages:
-            for word in word_re.findall(name):
-                try:
-                    if not map[word].count(name):
-                        map[word].append(name)
-                except KeyError:
-                    map[word] = [name]
-
-        all_words = map.keys()
-        all_words.sort()
-        index_letters = []
-        current_letter = None
-        html = []
-        for word in all_words:
-            letter = wikiutil.getUnicodeIndexGroup(word)
-            if letter != current_letter:
-                #html.append(self.formatter.anchordef()) # XXX no text param available!
-                html.append(u'<a name="%s"><h3>%s</h3></a>' % (
-                    wikiutil.quoteWikinameURL(letter), letter.replace('~', 'Others')))
-                current_letter = letter
-            if letter not in index_letters:
-                index_letters.append(letter)
-
-            html.append(self.formatter.strong(1))
-            html.append(word)
-            html.append(self.formatter.strong(0))
-            html.append(self.formatter.bullet_list(1))
-            links = map[word]
-            links.sort()
-            last_page = None
-            for name in links:
-                if name == last_page:
-                    continue
-                html.append(self.formatter.listitem(1))
-                html.append(Page(self.request, name).link_to(self.request))
-                html.append(self.formatter.listitem(0))
-            html.append(self.formatter.bullet_list(0))
-        
-        qpagename = wikiutil.quoteWikinameURL(self.formatter.page.page_name)
-        index = _make_index_key(index_letters, u"""<br>
-<a href="%s?allpages=%d">%s</a>
-""" % (qpagename, not allpages, (_('Include system pages'), _('Exclude system pages'))[allpages]) )
-        return u'%s%s' % (index, u''.join(html)) 
-
-
-    def _macro_TitleIndex(self, args):
-        _ = self._
-        html = []
-        index_letters = []
-        allpages = int(self.form.get('allpages', [0])[0]) != 0
-        # Get page list readable by current user
-        # Filter by isSystemPage if needed
-        if allpages:
-            # TODO: make this fast by caching full page list
-            pages = self.request.rootpage.getPageList()
-        else:
-            def filter(name):
-                return not wikiutil.isSystemPage(self.request, name)
-            pages = self.request.rootpage.getPageList(filter=filter)
-
-        # Sort ignoring case
-        tmp = [(name.upper(), name) for name in pages]
-        tmp.sort()
-        pages = [item[1] for item in tmp]
-                
-        current_letter = None
-        for name in pages:
-            letter = wikiutil.getUnicodeIndexGroup(name)
-            if letter not in index_letters:
-                index_letters.append(letter)
-            if letter != current_letter:
-                html.append(u'<a name="%s"><h3>%s</h3></a>' % (
-                    wikiutil.quoteWikinameURL(letter), letter.replace('~', 'Others')))
-                current_letter = letter
-            else:
-                html.append(u'<br>')
-            html.append(u'%s\n' % Page(self.request, name).link_to(self.request, attachment_indicator=1))
-
-        # add rss link
-        index = ''
-        if 0: # if wikixml.ok: # XXX currently switched off (not implemented)
-            from MoinMoin import wikixml
-            index = (index + self.formatter.url(1, 
-                wikiutil.quoteWikinameURL(self.formatter.page.page_name) + "?action=rss_ti", do_escape=0) +
-                     self.formatter.icon("rss") +
-                     self.formatter.url(0))
-
-        qpagename = wikiutil.quoteWikinameURL(self.formatter.page.page_name)
-        index = index + _make_index_key(index_letters, u"""<br>
-<a href="%s?allpages=%d">%s</a>&nbsp;|
-<a href="%s?action=titleindex">%s</a>&nbsp;|
-<a href="%s?action=titleindex&amp;mimetype=text/xml">%s</a>
-""" % (qpagename, not allpages, (_('Include system pages'), _('Exclude system pages'))[allpages],
-       qpagename, _('Plain title index'),
-       qpagename, _('XML title index')) )
-
-        return u'%s%s' % (index, u''.join(html)) 
-
-
-    def _macro_InterWiki(self, args):
-        from StringIO import StringIO
-
-        # load interwiki list
-        dummy = wikiutil.resolve_wiki(self.request, '')
-
-        buf = StringIO()
-        buf.write('<dl>')
-        list = self.cfg._interwiki_list.items() # this is where we cached it
-        list.sort()
-        for tag, url in list:
-            buf.write('<dt><tt><a href="%s">%s</a></tt></dt>' % (
-                wikiutil.join_wiki(url, 'RecentChanges'), tag))
-            if url.find('$PAGE') == -1:
-                buf.write('<dd><tt><a href="%s">%s</a></tt></dd>' % (url, url))
-            else:
-                buf.write('<dd><tt>%s</tt></dd>' % url)
-        buf.write('</dl>')
-
-        return self.formatter.rawHTML(buf.getvalue())
-
-    def _macro_SystemInfo(self, args):
-        import operator, sys
-        from StringIO import StringIO
-        from MoinMoin import parser, processor, version
-        from MoinMoin.logfile import editlog, eventlog
-        def _formatInReadableUnits(size):
-            size = float(size)
-            unit = u' Byte'
-            if size > 9999:
-                unit = u' KiB'
-                size /= 1024
-            if size > 9999:
-                unit = u' MiB'
-                size /= 1024
-            if size > 9999:
-                unit = u' GiB'
-                size /= 1024
-            return u"%.1f %s" % (size, unit)
-
-        def _getDirectorySize(path):
-            try:
-                dirsize = 0
-                for root, dirs, files in os.walk(path):
-                    dirsize += sum([os.path.getsize(os.path.join(root, name)) for name in files])
-            except EnvironmentError, e:
-                dirsize = -1
-            return dirsize
-
-        _ = self._
-        # check for 4XSLT
-        try:
-            import Ft
-            ftversion = Ft.__version__
-        except ImportError:
-            ftversion = None
-        except AttributeError:
-            ftversion = 'N/A'
-
-        t_count = None
-        try:
-            from threading import activeCount
-            t_count = activeCount()
-        except ImportError:
-            pass
-
-        # Get the full pagelist in the wiki
-        pagelist = self.request.rootpage.getPageList(user='')
-        totalsize = reduce(operator.add, [Page(self.request, name).size()
-                                          for name in pagelist])
-
-        buf = StringIO()
-        row = lambda label, value, buf=buf: buf.write(
-            u'<dt>%s</dt><dd>%s</dd>' % (label, value))
-
-        buf.write(u'<dl>')
-        row(_('Python Version'), sys.version)
-        row(_('MoinMoin Version'), _('Release %s [Revision %s]') % (version.release, version.revision))
-        if ftversion:
-            row(_('4Suite Version'), ftversion)
-        systemPages = [page for page in pagelist
-                       if wikiutil.isSystemPage(self.request, page)]
-        row(_('Number of pages'), str(len(pagelist)-len(systemPages)))
-        row(_('Number of system pages'), str(len(systemPages)))
-        row(_('Accumulated page sizes'), _formatInReadableUnits(totalsize))
-        data_dir = self.request.cfg.data_dir
-        row(_('Disk usage of %(data_dir)s/pages/') % {'data_dir': data_dir},
-            _formatInReadableUnits(_getDirectorySize(os.path.join(data_dir, 'pages'))))
-        row(_('Disk usage of %(data_dir)s/') % {'data_dir': data_dir},
-            _formatInReadableUnits(_getDirectorySize(data_dir)))
-
-        edlog = editlog.EditLog(self.request)
-        row(_('Entries in edit log'), "%s (%s)" % (edlog.lines(), _formatInReadableUnits(edlog.size())))
-
-        # This puts a heavy load on the server when the log is large
-        eventlogger = eventlog.EventLog(self.request)
-        nonestr = _("NONE")
-        row('Event log', _formatInReadableUnits(eventlogger.size()))
-        row(_('Global extension macros'), ', '.join(macro.extension_macros) or nonestr)
-        row(_('Local extension macros'), 
-            ', '.join(wikiutil.wikiPlugins('macro', self.cfg)) or nonestr)
-        ext_actions = [x for x in action.extension_actions
-                       if not x in self.request.cfg.actions_excluded]
-        row(_('Global extension actions'), ', '.join(ext_actions) or nonestr)
-        row(_('Local extension actions'), 
-            ', '.join(wikiaction.getPlugins(self.request)[1]) or nonestr)
-        row(_('Global parsers'), ', '.join(parser.modules) or nonestr)
-        row(_('Local extension parsers'), 
-            ', '.join(wikiutil.wikiPlugins('parser', self.cfg)) or nonestr)
-        row(_('Installed processors (DEPRECATED -- use Parsers instead)'), 
-            ', '.join(processor.processors) or nonestr)
-        state = (_('Disabled'), _('Enabled'))
-        row(_('Lupy search'), state[self.request.cfg.lupy_search])
-        row(_('Active threads'), t_count or 'N/A')
-        buf.write(u'</dl>')
-
-        return self.formatter.rawHTML(buf.getvalue())
-
-    def _macro_PageCount(self, args):
-        """ Return number of pages readable by current user
-        
-        Return either an exact count (slow!) or fast count including
-        deleted pages.
-        """
-        # Check input
-        options = {None: 0, '': 0, 'exists': 1}
-        try:
-            exists = options[args]
-        except KeyError:
-            # Wrong argument, return inline error message
-            arg = self.formatter.text(args)
-            return (self.formatter.span(1, css_class="error") +
-                    'Wrong argument: %s' % arg +
-                    self.formatter.span(0))
-        
-        count = self.request.rootpage.getPageCount(exists=exists)
-        return self.formatter.text("%d" % count)
-
-    def _macro_Icon(self, args):
-        icon = args.lower()
-        return self.formatter.icon(icon)
-
-    def _macro_PageList(self, needle):
-        from MoinMoin import search
-        _ = self._
-        literal=0
-        case=0
-
-        # If called with empty or no argument, default to regex search for .+,
-        # the full page list.
-        if not needle:
-            needle = 'regex:.+'
-
-        # With whitespace argument, return same error message as FullSearch
-        elif needle.isspace():
-            err = _('Please use a more selective search term instead of '
-                    '{{{"%s"}}}') %  needle
-            return '<span class="error">%s</span>' % err
-            
-        # Return a title search for needle, sorted by name.
-        query = search.QueryParser(literal=literal, titlesearch=1,
-                                   case=case).parse_query(needle)
-        results = search.searchPages(self.request, query)
-        results.sortByPagename()
-        return results.pageList(self.request, self.formatter)
-        
-    def _macro_TemplateList(self, args):
-        _ = self._
-        try:
-            needle_re = re.compile(args or '', re.IGNORECASE)
-        except re.error, e:
-            return "<strong>%s: %s</strong>" % (
-                _("ERROR in regex '%s'") % (args,), e)
-
-        # Get page list readable by current user, filtered by needle
-        hits = self.request.rootpage.getPageList(filter=needle_re.search)
-        hits.sort()
-        
-        result = []
-        result.append(self.formatter.bullet_list(1))
-        for pagename in hits:
-            result.append(self.formatter.listitem(1))
-            result.append(self.formatter.pagelink(1, pagename, generated=1))
-            result.append(self.formatter.text(pagename))
-            result.append(self.formatter.pagelink(0, pagename))
-            result.append(self.formatter.listitem(0))
-        result.append(self.formatter.bullet_list(0))
-        return ''.join(result)
-
-
-    def __get_Date(self, args, format_date):
-        _ = self._
-        if not args:
-            tm = time.time() # always UTC
-        elif len(args) >= 19 and args[4] == '-' and args[7] == '-' \
-                and args[10] == 'T' and args[13] == ':' and args[16] == ':':
-            # we ignore any time zone offsets here, assume UTC,
-            # and accept (and ignore) any trailing stuff
-            try:
-                year, month, day = int(args[0:4]), int(args[5:7]), int(args[8:10]) 
-                hour, minute, second = int(args[11:13]), int(args[14:16]), int(args[17:19]) 
-                tz = args[19:] # +HHMM, -HHMM or Z or nothing (then we assume Z)
-                tzoffset = 0 # we assume UTC no matter if there is a Z
-                if tz:
-                    sign = tz[0]
-                    if sign in '+-':
-                        tzh, tzm = int(tz[1:3]), int(tz[3:])
-                        tzoffset = (tzh*60+tzm)*60
-                        if sign == '-':
-                            tzoffset = -tzoffset
-                tm = (year, month, day, hour, minute, second, 0, 0, 0)
-            except ValueError, e:
-                return "<strong>%s: %s</strong>" % (
-                    _("Bad timestamp '%s'") % (args,), e)
-            # as mktime wants a localtime argument (but we only have UTC),
-            # we adjust by our local timezone's offset
-            try:
-                tm = time.mktime(tm) - time.timezone - tzoffset
-            except (OverflowError, ValueError), err:
-                tm = 0 # incorrect, but we avoid an ugly backtrace
-        else:
-            # try raw seconds since epoch in UTC
-            try:
-                tm = float(args)
-            except ValueError, e:
-                return "<strong>%s: %s</strong>" % (
-                    _("Bad timestamp '%s'") % (args,), e)
-        return format_date(tm)
-
-    def _macro_Date(self, args):
-        return self.__get_Date(args, self.request.user.getFormattedDate)
-
-    def _macro_DateTime(self, args):
-        return self.__get_Date(args, self.request.user.getFormattedDateTime)
-
-
-    def _macro_UserPreferences(self, args):
-        from MoinMoin import userform
-
-        create_only = False
-        if isinstance(args, unicode):
-            args = args.strip(" '\"")
-            create_only = (args.lower()=="createonly")
-
-        return self.formatter.rawHTML(userform.getUserForm(
-            self.request,
-            create_only=create_only))
-
-    def _macro_Anchor(self, args):
-        return self.formatter.anchordef(args or "anchor")
-
-    def _macro_MailTo(self, args):
-        from MoinMoin.util.mail import decodeSpamSafeEmail
-
-        args = args or ''
-        if args.find(',') == -1:
-            email = args
-            text = ''
-        else:
-            email, text = args.split(',', 1)
-
-        email, text = email.strip(), text.strip()
-
-        if self.request.user.valid:
-            # decode address and generate mailto: link
-            email = decodeSpamSafeEmail(email)
-            result = (self.formatter.url(1, 'mailto:' + email, css='mailto', do_escape=0) +
-                      self.formatter.text(text or email) +
-                      self.formatter.url(0))
-        else:
-            # unknown user, maybe even a spambot, so
-            # just return text as given in macro args
-            email = self.formatter.code(1) + \
-                self.formatter.text("<%s>" % email) + \
-                self.formatter.code(0)
-            if text:
-                result = self.formatter.text(text) + " " + email
-            else:
-                result = email
-
-        return result
-
-    def _macro_GetVal(self, args):
-        page,key = args.split(',')
-        d = self.request.dicts.dict(page)
-        result = d.get(key,'')
-        return self.formatter.text(result)
-
--- a/MoinMoin/wikirpc.py	Mon May 15 10:58:04 2006 +1200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,512 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - Wiki XMLRPC v1 and v2 Interface
-
-    If you want to use wikirpc function "putPage", read the comments in
-    xmlrpc_putPage or it won't work!
-    
-    Parts of this code are based on Jürgen Hermann's wikirpc.py,
-    Les Orchard's "xmlrpc.cgi" and further work by Gustavo Niemeyer.
-
-    See http://www.ecyrd.com/JSPWiki/Wiki.jsp?page=WikiRPCInterface
-    and http://www.decafbad.com/twiki/bin/view/Main/XmlRpcToWiki
-    for specs on many of the functions here.
-
-    See also http://www.jspwiki.org/Wiki.jsp?page=WikiRPCInterface2
-    for the new stuff.
-
-    The main difference between v1 and v2 is that v2 relies on utf-8
-    as transport encoding. No url-encoding and no base64 anymore, except
-    when really necessary (like for transferring binary files like
-    attachments maybe).
-
-    @copyright: 2003-2005 by Thomas Waldmann
-    @license: GNU GPL, see COPYING for details
-"""
-
-import sys, time, xmlrpclib
-
-from MoinMoin import config, user, wikiutil
-from MoinMoin.Page import Page
-from MoinMoin.PageEditor import PageEditor
-from MoinMoin.logfile import editlog
-from MoinMoin.formatter import text_html
-
-_debug = 0
-
-class XmlRpcBase:
-    def __init__(self, request):
-        """
-        Initialize an XmlRpcBase object.
-        @param request: the request object
-        """
-        self.request = request
-        self.version = None # this has to be defined in derived class
-        self.cfg = request.cfg
-
-    #############################################################################
-    ### Helper functions           
-    #############################################################################
-
-    def _instr(self, text):
-        """ Convert inbound string from utf-8.
-        
-        @param text: the text to convert
-        @rtype: str
-        @return: string in config.charset
-        """
-        raise "NotImplementedError"
-    
-    def _outstr(self, text):
-        """ Convert outbound string to utf-8.
-
-        @param text: the text to convert XXX unicode? str? both?
-        @rtype: str
-        @return: string in utf-8
-        """
-        raise "NotImplementedError"
-    
-    def _inlob(self, text):
-        """ Convert inbound base64-encoded utf-8 to Large OBject.
-        
-        @param text: the text to convert
-        @rtype: unicode
-        @return: text
-        """
-        text = text.data #this is a already base64-decoded 8bit string
-        text = unicode(text, 'utf-8')
-        return text
-
-    def _outlob(self, text):
-        """ Convert outbound Large OBject to base64-encoded utf-8.
-        
-        @param text: the text, either unicode or utf-8 string
-        @rtype: str
-        @return: xmlrpc Binary object
-        """
-        if isinstance(text, unicode):
-            text = text.encode('utf-8')
-        else:
-            if config.charset != 'utf-8':
-                text = unicode(text, config.charset).encode('utf-8')
-        return xmlrpclib.Binary(text)
-                    
-    def _dump_exc(self):
-        """ Convert an exception to a string.
-        
-        @rtype: str
-        @return: traceback as string
-        """
-        import traceback
-
-        return "%s: %s\n%s" % (
-            sys.exc_info()[0],
-            sys.exc_info()[1],
-            '\n'.join(traceback.format_tb(sys.exc_info()[2])),
-        )
-
-
-    #############################################################################
-    ### Interface implementation
-    #############################################################################
-
-    def xmlrpc_getRPCVersionSupported(self):
-        """ Returns version of the Wiki API.
-
-        @rtype: int
-        @return: 1 or 2 (wikirpc version)
-        """
-        return self.version
-
-    def xmlrpc_getAllPages(self):
-        """ Get all pages readable by current user
-
-        @rtype: list
-        @return: a list of all pages. The result is a list of utf-8 strings.
-        """
-        pagelist = self.request.rootpage.getPageList()
-        return map(self._outstr, pagelist)
-
-    def xmlrpc_getRecentChanges(self, date):
-        """ Get RecentChanges since date
-        
-        @param date: date since when rc will be listed
-        @rtype: list
-        @return: a list of changed pages since date, which should be in
-            UTC. The result is a list, where each element is a struct:
-            * name (string) :
-                Name of the page. The name is in UTF-8.
-            * lastModified (date) :
-                Date of last modification, in UTC.
-            * author (string) :
-                Name of the author (if available). UTF-8.
-            * version (int) :
-                Current version.
-        """
-        
-        return_items = []
-        
-        edit_log = editlog.EditLog(self.request)
-        for log in edit_log.reverse():
-            # get last-modified UTC (DateTime) from log
-            gmtuple = tuple(time.gmtime(wikiutil.version2timestamp(log.ed_time_usecs)))
-            lastModified_date = xmlrpclib.DateTime(gmtuple)
-
-            # skip if older than "date"
-            if lastModified_date < date:
-                break
-            
-            # skip if knowledge not permitted
-            if not self.request.user.may.read(log.pagename):
-                continue
-            
-            # get page name (str) from log
-            pagename_str = self._outstr(log.pagename)
-
-            # get user name (str) from log
-            author_str = log.hostname
-            if log.userid:
-                userdata = user.User(self.request, log.userid)
-                if userdata.name:
-                    author_str = userdata.name
-            author_str = self._outstr(author_str)
-
-            return_item = { 'name':  pagename_str,
-                            'lastModified': lastModified_date,
-                            'author': author_str,
-                            'version': int(log.rev) }
-            return_items.append(return_item)
-        
-        return return_items
-
-    def xmlrpc_getPageInfo(self, pagename):
-        """ Invoke xmlrpc_getPageInfoVersion with rev=None """
-        return self.xmlrpc_getPageInfoVersion(pagename, rev=None)
-
-    def xmlrpc_getPageInfoVersion(self, pagename, rev):
-        """ Return page information for specific revision
-        
-        @param pagename: the name of the page (utf-8)
-        @param rev: revision to get info about (XXX int?)
-        @rtype: dict XXX ??
-        @return: page information
-            * name (string): the canonical page name, UTF-8.
-            * lastModified (date): Last modification date, UTC.
-            * author (string): author name, UTF-8.
-            * version (int): current version
-
-        """
-        pn = self._instr(pagename)
-
-        # User may read this page?
-        if not self.request.user.may.read(pn):
-            return self.notAllowedFault()
-
-        if rev != None:
-            page = Page(self.request, pn, rev=rev)
-        else:
-            page = Page(self.request, pn)
-            rev = page.current_rev()
-
-        # Non existing page?
-        if not page.exists():
-            return self.noSuchPageFault()
-
-        # Get page info
-        last_edit = page.last_edit(self.request)           
-        mtime = wikiutil.version2timestamp(long(last_edit['timestamp'])) # must be long for py 2.2.x
-        gmtuple = tuple(time.gmtime(mtime))
-        
-        version = rev # our new rev numbers: 1,2,3,4,....
-
-        #######################################################################
-        # BACKWARDS COMPATIBILITY CODE - remove when 1.2.x is regarded stone age
-        # as we run a feed for BadContent on MoinMaster, we want to stay
-        # compatible here for a while with 1.2.x moins asking us for BadContent
-        # 1.3 uses the lastModified field for checking for updates, so it
-        # should be no problem putting the old UNIX timestamp style of version
-        # number in the version field
-        if self.request.cfg.sitename == 'MoinMaster' and pagename == 'BadContent':
-            version = int(mtime)
-        #######################################################################
-            
-        return {
-            'name': self._outstr(page.page_name),
-            'lastModified' : xmlrpclib.DateTime(gmtuple),
-            'author': self._outstr(last_edit['editor']),
-            'version': version,
-            }
-
-    def xmlrpc_getPage(self, pagename):
-        """ Invoke xmlrpc_getPageVersion with rev=None """
-        return self.xmlrpc_getPageVersion(pagename, rev=None)
-
-    def xmlrpc_getPageVersion(self, pagename, rev):
-        """ Get raw text from specific revision of pagename
-        
-        @param pagename: pagename (utf-8)
-        @param rev: revision number (int)
-        @rtype: str
-        @return: utf-8 encoded page data
-        """    
-        pagename = self._instr(pagename)
-
-        # User may read page?
-        if not self.request.user.may.read(pagename):
-            return self.notAllowedFault()
-
-        if rev != None:
-            page = Page(self.request, pagename, rev=rev)
-        else:
-            page = Page(self.request, pagename)
-
-        # Non existing page?
-        if not page.exists():
-            return self.noSuchPageFault()
-
-        # Return page raw text
-        if self.version == 2:
-            return self._outstr(page.get_raw_body())
-        elif self.version == 1:
-            return self._outlob(page.get_raw_body())
-
-    def xmlrpc_getPageHTML(self, pagename):
-        """ Invoke xmlrpc_getPageHTMLVersion with rev=None """
-        return self.xmlrpc_getPageHTMLVersion(pagename, rev=None)
-
-    def xmlrpc_getPageHTMLVersion(self, pagename, rev):
-        """ Get HTML of from specific revision of pagename