changeset 105:ddc707d70d9d

merged MattMaker's changes
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sat, 12 Mar 2011 14:14:22 +0100
parents 1d900f3cf7bb (diff) 9ca492c52070 (current diff)
children 5144c90c6bd8
files MoinMoin/themes/__init__.py
diffstat 106 files changed, 1845 insertions(+), 5841 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri Mar 11 23:08:35 2011 -0600
+++ b/.hgignore	Sat Mar 12 14:14:22 2011 +0100
@@ -15,7 +15,7 @@
 ^.project
 ^.pydevproject
 ^.settings
-^MANIFEST
+^MANIFEST$
 .DS_Store
 .sqlite$
 .orig$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MANIFEST.in	Sat Mar 12 14:14:22 2011 +0100
@@ -0,0 +1,35 @@
+include README.txt LICENSE.txt
+include quickinstall quickinstall.bat
+include wikiconfig.py
+include Makefile
+
+recursive-include   MoinMoin/translations *
+
+recursive-include   MoinMoin/static *
+recursive-include   MoinMoin/templates *
+
+recursive-include   MoinMoin/apps/admin/templates *
+recursive-include   MoinMoin/apps/misc/templates *
+
+recursive-include   MoinMoin/themes/modernized *
+
+global-include */_tests/*
+
+recursive-include docs *
+recursive-include contrib *
+recursive-include wiki *
+
+global-exclude *.pyc
+global-exclude *.pyo
+global-exclude *.orig
+global-exclude *.rej
+
+prune docs/_build
+prune env
+
+prune wiki/data/content
+prune wiki/data/trash
+prune wiki/data/userprofiles
+prune wiki/data/flask-cache
+exclude wiki/data/ROUTER_index.sqlite
+
--- a/Makefile	Fri Mar 11 23:08:35 2011 -0600
+++ b/Makefile	Sat Mar 12 14:14:22 2011 +0100
@@ -15,6 +15,10 @@
 docs:
 	make -C docs html
 
+# this needs the sphinx-autopackage script in the toplevel dir:
+apidocs:
+	python generate_modules.py -d docs/devel/api -s rst -f MoinMoin
+
 interwiki:
 	wget -U MoinMoin/Makefile -O contrib/interwiki/intermap.txt "http://master19.moinmo.in/InterWikiMap?action=raw"
 	chmod 664 contrib/interwiki/intermap.txt
--- a/MoinMoin/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/__init__.py	Sat Mar 12 14:14:22 2011 +0100
@@ -7,11 +7,9 @@
 """
 
 
-import os
-import sys
-
 project = "MoinMoin"
 
+import sys
 if sys.hexversion < 0x2060000:
     sys.exit("%s requires Python 2.6 or greater.\n" % project)
 
--- a/MoinMoin/_template.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/_template.py	Sat Mar 12 14:14:22 2011 +0100
@@ -2,9 +2,16 @@
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
 """
-MoinMoin - <short description>
+MoinMoin - sourcecode header template
 
-<what this stuff does ... - verbose enough>
+Please use this template when starting a new python source file.
+
+It is important that all sourcecode files have the same header structure,
+are correctly copyrighted and licensed and have a reasonable docstring
+explaining what the module / package does.
+
+Of course you'll have to edit/fix the Copyright line and also the docstring
+needs to be replaced with something making sense, but keep the structure.
 """
 
 
--- a/MoinMoin/_tests/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/_tests/__init__.py	Sat Mar 12 14:14:22 2011 +0100
@@ -80,12 +80,6 @@
     chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
     return [u"%s" % random_string(length, chars) for counter in range(count)]
 
-def nuke_xapian_index():
-    """ completely delete everything in xapian index dir """
-    fpath = app.cfg.xapian_index_dir
-    if os.path.exists(fpath):
-        shutil.rmtree(fpath, True)
-
 def nuke_item(name):
     """ complete destroys an item """
     item = Item.create(name)
--- a/MoinMoin/_tests/test_test_environ.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/_tests/test_test_environ.py	Sat Mar 12 14:14:22 2011 +0100
@@ -11,7 +11,7 @@
 from flask import current_app as app
 from flask import flaskg
 
-from MoinMoin.items import IS_SYSITEM, SYSITEM_VERSION
+from MoinMoin.config import IS_SYSITEM, SYSITEM_VERSION
 from MoinMoin.storage.error import NoSuchItemError
 
 from MoinMoin._tests import wikiconfig
--- a/MoinMoin/app.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/app.py	Sat Mar 12 14:14:22 2011 +0100
@@ -66,7 +66,16 @@
     if flask_config_file:
         app.config.from_pyfile(flask_config_file)
     else:
-        app.config.from_envvar('MOINCFG', silent=True)
+        if not app.config.from_envvar('MOINCFG', silent=True):
+            # no MOINCFG env variable set, try stuff in cwd:
+            from os import path
+            flask_config_file = path.abspath('wikiconfig_local.py')
+            if not path.exists(flask_config_file):
+                flask_config_file = path.abspath('wikiconfig.py')
+                if not path.exists(flask_config_file):
+                    flask_config_file = None
+            if flask_config_file:
+                app.config.from_pyfile(flask_config_file)
     if flask_config_dict:
         app.config.update(flask_config_dict)
     Config = moin_config_class
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/apps/admin/templates/highlighterhelp.html	Sat Mar 12 14:14:22 2011 +0100
@@ -0,0 +1,7 @@
+{% import "utils.html" as utils %}
+{% extends theme("layout.html") %}
+{% block content %}
+<h1>{{ _("Available Highlighters") }}</h1>
+{{ utils.table(headings, rows) }}
+{% endblock %}
+
--- a/MoinMoin/apps/admin/templates/index.html	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/apps/admin/templates/index.html	Sat Mar 12 14:14:22 2011 +0100
@@ -7,5 +7,11 @@
     <li><a href="{{ url_for('admin.wikiconfig') }}">{{ _("Show Wiki Configuration") }}</a></li>
     <li><a href="{{ url_for('admin.wikiconfighelp') }}">{{ _("Show Wiki Configuration Help") }}</a></li>
 </ul>
+<h1>{{ _("User Menu") }}</h1>
+<ul>
+    <li><a href="{{ url_for('admin.itemsize') }}">{{ _("Item sizes (latest revision)") }}</a></li>
+    <li><a href="{{ url_for('admin.interwikihelp') }}">{{ _("Known InterWiki names") }}</a></li>
+    <li><a href="{{ url_for('admin.highlighterhelp') }}">{{ _("Available Highlighters") }}</a></li>
+</ul>
 {% endblock %}
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/apps/admin/templates/interwikihelp.html	Sat Mar 12 14:14:22 2011 +0100
@@ -0,0 +1,7 @@
+{% import "utils.html" as utils %}
+{% extends theme("layout.html") %}
+{% block content %}
+<h1>{{ _("Known InterWiki names") }}</h1>
+{{ utils.table(headings, rows) }}
+{% endblock %}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/apps/admin/templates/itemsize.html	Sat Mar 12 14:14:22 2011 +0100
@@ -0,0 +1,7 @@
+{% import "utils.html" as utils %}
+{% extends theme("layout.html") %}
+{% block content %}
+<h1>{{ _("Item sizes (latest revision)") }}</h1>
+{{ utils.table(headings, rows) }}
+{% endblock %}
+
--- a/MoinMoin/apps/admin/views.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/apps/admin/views.py	Sat Mar 12 14:14:22 2011 +0100
@@ -1,6 +1,7 @@
-# Copyright: 2008-2011 MoinMoin:ThomasWaldmann
+# Copyright: 2007-2011 MoinMoin:ThomasWaldmann
 # Copyright: 2001-2003 Juergen Hermann <jh@web.de>
 # Copyright: 2008 MoinMoin:JohannesBerg
+# Copyright: 2009 MoinMoin:EugeneSyromyatnikov
 # Copyright: 2010 MoinMoin:DiogenesAugusto
 # Copyright: 2010 MoinMoin:ReimarBauer
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
@@ -20,6 +21,8 @@
 from MoinMoin.themes import render_template
 from MoinMoin.apps.admin import admin
 from MoinMoin import user
+from MoinMoin.storage.error import NoSuchRevisionError
+from MoinMoin.config import SIZE
 
 @admin.route('/')
 def index():
@@ -190,3 +193,57 @@
                            item_name="+admin/wikiconfighelp",
                            groups=groups)
 
+
+@admin.route('/highlighterhelp', methods=['GET', ])
+def highlighterhelp():
+    """display a table with list of available Pygments lexers"""
+    import pygments.lexers
+    headings = [_('Lexer description'),
+                _('Lexer names'),
+                _('File patterns'),
+                _('Mimetypes'),
+               ]
+    lexers = pygments.lexers.get_all_lexers()
+    rows = sorted([[desc, ' '.join(names), ' '.join(patterns), ' '.join(mimetypes), ]
+                   for desc, names, patterns, mimetypes in lexers])
+    return render_template('admin/highlighterhelp.html',
+                           item_name="+admin/highlighterhelp",
+                           headings=headings,
+                           rows=rows)
+
+
+@admin.route('/interwikihelp', methods=['GET', ])
+def interwikihelp():
+    """display a table with list of known interwiki names / urls"""
+    headings = [_('InterWiki name'),
+                _('URL'),
+               ]
+    rows = sorted(app.cfg.interwiki_map.items())
+    return render_template('admin/interwikihelp.html',
+                           item_name="+admin/interwikihelp",
+                           headings=headings,
+                           rows=rows)
+
+
+@admin.route('/itemsize', methods=['GET', ])
+def itemsize():
+    """display a table with item sizes"""
+    headings = [_('Size'),
+                _('Item name'),
+               ]
+    rows = []
+    for item in flaskg.storage.iteritems():
+        try:
+            rev = item.get_revision(-1)
+        except NoSuchRevisionError:
+            # XXX we currently also get user items, they have no revisions -
+            # but in the end, they should not be readable by the user anyways
+            continue
+        rows.append((rev[SIZE], item.name))
+    rows = sorted(rows, reverse=True)
+    return render_template('admin/itemsize.html',
+                           item_name="+admin/itemsize",
+                           headings=headings,
+                           rows=rows)
+
+
--- a/MoinMoin/apps/feed/views.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/apps/feed/views.py	Sat Mar 12 14:14:22 2011 +0100
@@ -24,7 +24,7 @@
 from MoinMoin import wikiutil
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.apps.feed import feed
-from MoinMoin.items import NAME, ACL, MIMETYPE, ACTION, ADDRESS, HOSTNAME, USERID, COMMENT
+from MoinMoin.config import NAME, ACL, MIMETYPE, ACTION, ADDRESS, HOSTNAME, USERID, COMMENT
 from MoinMoin.themes import get_editor_info
 from MoinMoin.items import Item
 
--- a/MoinMoin/apps/frontend/views.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/apps/frontend/views.py	Sat Mar 12 14:14:22 2011 +0100
@@ -36,9 +36,10 @@
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.themes import render_template
 from MoinMoin.apps.frontend import frontend
-from MoinMoin.items import Item, NonExistent, MIMETYPE, ITEMLINKS, ITEMTRANSCLUSIONS
+from MoinMoin.items import Item, NonExistent
 from MoinMoin.items import ROWS_META, COLS, ROWS_DATA
 from MoinMoin import config, user, wikiutil
+from MoinMoin.config import MIMETYPE, ITEMLINKS, ITEMTRANSCLUSIONS
 from MoinMoin.util.forms import make_generator
 from MoinMoin.security.textcha import TextCha, TextChaizedForm, TextChaValid
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, AccessDeniedError
@@ -109,16 +110,17 @@
                         item_name=item_name)
     try:
         item = Item.create(item_name, rev_no=rev)
-        rev_nos = item.rev.item.list_revisions()
     except AccessDeniedError:
         abort(403)
-    if rev_nos:
-        first_rev = rev_nos[0]
-        last_rev = rev_nos[-1]
-    else:
-        # Note: rev.revno of DummyRev is None
-        first_rev = None
-        last_rev = None
+    show_revision = show_navigation = rev >= 0
+    # Note: rev.revno of DummyRev is None
+    first_rev = None
+    last_rev = None
+    if show_navigation:
+        rev_nos = item.rev.item.list_revisions()
+        if rev_nos:
+            first_rev = rev_nos[0]
+            last_rev = rev_nos[-1]
     if isinstance(item, NonExistent):
         status = 404
     else:
@@ -130,7 +132,8 @@
                               first_rev_no=first_rev,
                               last_rev_no=last_rev,
                               data_rendered=Markup(item._render_data()),
-                              show_navigation=(rev>=0),
+                              show_revision=show_revision,
+                              show_navigation=show_navigation,
                              )
     return Response(content, status)
 
@@ -165,14 +168,15 @@
         item = Item.create(item_name, rev_no=rev)
     except AccessDeniedError:
         abort(403)
-    rev_nos = item.rev.item.list_revisions()
-    if rev_nos:
-        first_rev = rev_nos[0]
-        last_rev = rev_nos[-1]
-    else:
-        # Note: rev.revno of DummyRev is None
-        first_rev = None
-        last_rev = None
+    show_revision = show_navigation = rev >= 0
+    # Note: rev.revno of DummyRev is None
+    first_rev = None
+    last_rev = None
+    if show_navigation:
+        rev_nos = item.rev.item.list_revisions()
+        if rev_nos:
+            first_rev = rev_nos[0]
+            last_rev = rev_nos[-1]
     return render_template('meta.html',
                            item=item, item_name=item.name,
                            rev=item.rev,
@@ -180,7 +184,8 @@
                            first_rev_no=first_rev,
                            last_rev_no=last_rev,
                            meta_rendered=Markup(item._render_meta()),
-                           show_navigation=(rev>=0),
+                           show_revision=show_revision,
+                           show_navigation=show_navigation,
                           )
 
 
--- a/MoinMoin/config/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/config/__init__.py	Sat Mar 12 14:14:22 2011 +0100
@@ -63,7 +63,7 @@
               ]
 
 
-# rights that are valid in moin2
+# ACL rights that are valid in moin2
 ADMIN = 'admin'
 READ = 'read'
 WRITE = 'write'
@@ -71,3 +71,44 @@
 DESTROY = 'destroy'
 ACL_RIGHTS_VALID = [READ, WRITE, CREATE, ADMIN, DESTROY, ]
 
+# metadata keys
+UUID = "uuid"
+NAME = "name"
+NAME_OLD = "name_old"
+
+# if an item is reverted, we store the revision number we used for reverting there:
+REVERTED_TO = "reverted_to"
+
+# some metadata key constants:
+ACL = "acl"
+
+# This says: I am a system item
+IS_SYSITEM = "is_syspage"
+# This says: original sysitem as contained in release: <release>
+SYSITEM_VERSION = "syspage_version"
+
+# keys for storing group and dict information
+# group of user names, e.g. for ACLs:
+USERGROUP = "usergroup"
+# needs more precise name / use case:
+SOMEDICT = "somedict"
+
+MIMETYPE = "mimetype"
+SIZE = "size"
+LANGUAGE = "language"
+ITEMLINKS = "itemlinks"
+ITEMTRANSCLUSIONS = "itemtransclusions"
+TAGS = "tags"
+
+ACTION = "action"
+ADDRESS = "address"
+HOSTNAME = "hostname"
+USERID = "userid"
+EXTRA = "extra"
+COMMENT = "comment"
+
+# we need a specific hash algorithm to store hashes of revision data into meta
+# data. meta[HASH_ALGORITHM] = hash(rev_data, HASH_ALGORITHM)
+# some backends may use this also for other purposes.
+HASH_ALGORITHM = 'sha1'
+
--- a/MoinMoin/config/default.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/config/default.py	Sat Mar 12 14:14:22 2011 +0100
@@ -12,7 +12,6 @@
 
 import re
 import os
-import sys
 
 from babel import parse_locale
 
@@ -20,11 +19,9 @@
 logging = log.getLogger(__name__)
 
 from MoinMoin.i18n import _, L_, N_
-from MoinMoin import config, error, util
+from MoinMoin import config, error
 from MoinMoin import datastruct
 from MoinMoin.auth import MoinAuth
-import MoinMoin.auth as authmodule
-from MoinMoin.security import AccessControlList
 from MoinMoin.util import plugins
 
 
@@ -49,7 +46,6 @@
     auth_have_login = None
     auth_login_inputs = None
     _site_plugin_lists = None
-    xapian_searchers = None
 
     def __init__(self):
         """ Init Config instance """
@@ -61,10 +57,6 @@
         # define directories
         data_dir = os.path.normpath(self.data_dir)
         self.data_dir = data_dir
-        if not getattr(self, 'plugin_dir', None):
-            setattr(self, 'plugin_dir', os.path.abspath(os.path.join(data_dir, 'plugin')))
-        if not getattr(self, 'xapian_index_dir', None):
-            setattr(self, 'xapian_index_dir', os.path.abspath(os.path.join(data_dir, 'xapian')))
 
         # Try to decode certain names which allow unicode
         self._decode()
@@ -121,17 +113,6 @@
         # e.g u'%(item_root)s' % self
         self.navi_bar = [elem % self for elem in self.navi_bar]
 
-        # check if python-xapian is installed
-        if self.xapian_search:
-            try:
-                import xapian
-            except ImportError, err:
-                self.xapian_search = False
-                logging.error("xapian_search was auto-disabled because python-xapian is not installed [%s]." % str(err))
-
-        # list to cache xapian searcher objects
-        self.xapian_searchers = []
-
         # check if mail is possible and set flag:
         self.mail_enabled = (self.mail_smarthost is not None or self.mail_sendmail is not None) and self.mail_from
         self.mail_enabled = self.mail_enabled and True or False
@@ -399,8 +380,7 @@
   # ==========================================================================
   'data': ('Data storage', None, (
     ('data_dir', './data/', "Path to the data directory."),
-    ('plugin_dir', None, "Plugin directory, by default computed to be `data_dir`/plugin."),
-    ('plugin_dirs', [], "Additional plugin directories."),
+    ('plugin_dirs', [], "Plugin directories."),
 
     ('interwiki_map', {},
      "Dictionary of wiki_name -> wiki_url"),
@@ -508,8 +488,6 @@
     ('refresh', None,
      "refresh = (minimum_delay_s, targets_allowed) enables use of `#refresh 5 PageName` processing instruction, targets_allowed must be either `'internal'` or `'external'`"),
 
-    ('search_results_per_page', 25, "Number of hits shown per page in the search results"),
-
     ('siteid', 'MoinMoin', None), # XXX just default to some existing module name to
                                   # make plugin loader etc. work for now
   )),
@@ -556,17 +534,6 @@
       ('trash', 'Trash/', 'This is the namespace in which an item ends up when it is deleted.')
     )),
 
-    'xapian': ('Xapian search', "Configuration of the Xapian based indexed search, see HelpOnXapian.", (
-      ('search', False,
-       "True to enable the fast, indexed search (based on the Xapian search library)"),
-      ('index_dir', None,
-       "Directory where the Xapian search index is stored (None = auto-configure wiki local storage)"),
-      ('stemming', False,
-       "True to enable Xapian word stemmer usage for indexing / searching."),
-      ('index_history', False,
-       "True to enable indexing of non-current page revisions."),
-    )),
-
     'user': ('Users / User settings', None, (
       ('email_unique', True,
        "if True, check email addresses for uniqueness and don't accept duplicates."),
--- a/MoinMoin/converter/html_out.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/converter/html_out.py	Sat Mar 12 14:14:22 2011 +0100
@@ -148,8 +148,6 @@
     direct_inline_tags = set(['abbr', 'address', 'dfn', 'kbd'])
 
     def __call__(self, element):
-        # Base URL extracted from xml:base
-        self.base_url = ''
         return self.visit(element)
 
     def do_children(self, element):
@@ -166,14 +164,11 @@
                 new.append(child)
         return new
 
-    def new(self, tag, attrib={}, children=[]):
-        return ET.Element(tag, attrib=attrib, children=children)
-
     def new_copy(self, tag, element, attrib={}):
         attrib_new = Attributes(element).convert()
         attrib_new.update(attrib)
         children = self.do_children(element)
-        return self.new(tag, attrib_new, children)
+        return tag(attrib_new, children)
 
     def visit(self, elem):
         uri = elem.tag.uri
@@ -188,8 +183,6 @@
         return self.new_copy(elem.tag, elem)
 
     def visit_moinpage(self, elem):
-        # Get base_url from xml:base
-        self.base_url = elem.get(xml.base) or self.base_url
         n = 'visit_moinpage_' + elem.tag.name.replace('-', '_')
         f = getattr(self, n, None)
         if f:
@@ -203,8 +196,6 @@
         attrib = {}
         href = elem.get(_tag_xlink_href)
         if href:
-            if self.base_url:
-                href = ''.join([self.base_url, href])
             attrib[_tag_html_href] = href
         # XXX should support more tag attrs
         return self.new_copy(_tag_html_a, elem, attrib)
@@ -221,8 +212,7 @@
 
         # TODO: Unify somehow
         if elem.get(moin_page.class_) == 'codearea':
-            attrib = {html.class_: 'codearea'}
-            div = self.new(html.div, attrib)
+            div = html.div(attrib={html.class_: 'codearea'})
             div.append(pre)
             return div
 
@@ -250,7 +240,9 @@
             level = 1
         elif level > 6:
             level = 6
-        return self.new_copy(ET.QName('h%d' % level, html), elem)
+        ret = self.new_copy(html('h%d' % level), elem)
+        ret.level = level
+        return ret
 
     def visit_moinpage_inline_part(self, elem):
         body = error = None
@@ -282,7 +274,7 @@
 
     def visit_moinpage_line_break(self, elem):
         # TODO: attributes?
-        return self.new(html.br)
+        return html.br()
 
     def visit_moinpage_list(self, elem):
         attrib = Attributes(elem)
@@ -304,16 +296,16 @@
                 start_number = attrib.get('list-start')
                 if start_number:
                     attrib_new[html('start')] = start_number
-                ret = self.new(html.ol, attrib_new)
+                ret = html.ol(attrib_new)
             elif generate == 'unordered':
                 style = attrib.get('list-style-type')
                 if style and style == 'no-bullet':
                     attrib_new[html('class')] = 'moin-nobullet-list'
-                ret = self.new(html.ul, attrib_new)
+                ret = html.ul(attrib=attrib_new)
             else:
                 raise ElementException('page:item-label-generate does not support "%s"' % generate)
         else:
-            ret = self.new(html.dl, attrib_new)
+            ret = html.dl(attrib=attrib_new)
 
         for item in elem:
             if isinstance(item, ET.Element):
@@ -354,8 +346,6 @@
             return "object"
     def visit_moinpage_object(self, elem):
         href = elem.get(xlink.href, None)
-        if self.base_url:
-            href = ''.join([self.base_url, href])
         if href:
             if isinstance(href, unicode): # XXX sometimes we get Iri, sometimes unicode - bug?
                 h = href
@@ -382,7 +372,7 @@
             alt = ''.join(str(e) for e in elem) # XXX handle non-text e
             if alt:
                 attrib[html.alt] = alt
-            new_elem = self.new(html.img, attrib)
+            new_elem = html.img(attrib=attrib)
 
         else:
             if obj_type != "object":
@@ -482,7 +472,7 @@
 
     def visit_moinpage_table(self, elem):
         attrib = Attributes(elem).convert()
-        ret = self.new(html.table, attrib)
+        ret = html.table(attrib=attrib)
         for item in elem:
             tag = None
             if item.tag.uri == moin_page:
@@ -661,24 +651,16 @@
         else:
             return super(ConverterPage, self).visit(elem)
 
-    def visit_moinpage_h(self, elem):
-        level = elem.get(moin_page.outline_level, 1)
-        try:
-            level = int(level)
-        except ValueError:
-            raise ElementException('page:outline-level needs to be an integer')
-        if level < 1:
-            level = 1
-        elif level > 6:
-            level = 6
-        elem = self.new_copy(ET.QName('h%d' % level, html), elem)
+    def visit_moinpage_h(self, elem,
+            _tag_html_id=html.id):
+        elem = super(ConverterPage, self).visit_moinpage_h(elem)
 
-        id = elem.get(html.id)
+        id = elem.get(_tag_html_id)
         if not id:
             id = self._id.gen_text(''.join(elem.itertext()))
-            elem.set(html.id, id)
+            elem.set(_tag_html_id, id)
 
-        self._special_stack[-1].add_heading(elem, level, id)
+        self._special_stack[-1].add_heading(elem, elem.level, id)
         return elem
 
     def visit_moinpage_note(self, elem):
@@ -709,7 +691,7 @@
         level = int(elem.get(moin_page.outline_level, 6))
 
         attrib = {html.class_: 'moin-table-of-contents'}
-        elem = self.new(html.div, attrib)
+        elem = html.div(attrib=attrib)
 
         self._special_stack[-1].add_toc(elem, level)
         return elem
--- a/MoinMoin/converter/include.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/converter/include.py	Sat Mar 12 14:14:22 2011 +0100
@@ -196,7 +196,7 @@
 
                 elif xp_include_pages:
                     # We have a regex of pages to include
-                    from MoinMoin.search.term import NameFn
+                    from MoinMoin.storage.terms import NameFn
                     inc_match = re.compile(xp_include_pages)
                     root_item = Item(name=u'')
                     pagelist = [item.name for item in root_item.list_items(NameFn(inc_match))]
--- a/MoinMoin/datastruct/backends/_tests/test_wiki_dicts.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/datastruct/backends/_tests/test_wiki_dicts.py	Sat Mar 12 14:14:22 2011 +0100
@@ -11,7 +11,7 @@
 
 from MoinMoin.datastruct.backends._tests import DictsBackendTest
 from MoinMoin.datastruct.backends import wiki_dicts
-from MoinMoin.items import SOMEDICT
+from MoinMoin.config import SOMEDICT
 from MoinMoin._tests import become_trusted, update_item
 
 DATA = "This is a dict item."
--- a/MoinMoin/datastruct/backends/_tests/test_wiki_groups.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/datastruct/backends/_tests/test_wiki_groups.py	Sat Mar 12 14:14:22 2011 +0100
@@ -16,7 +16,7 @@
 from flask import flaskg
 from MoinMoin.datastruct.backends._tests import GroupsBackendTest
 from MoinMoin.datastruct import GroupDoesNotExistError
-from MoinMoin.items import USERGROUP
+from MoinMoin.config import USERGROUP
 from MoinMoin import security
 from MoinMoin.user import User
 from MoinMoin._tests import become_trusted, create_random_string_list, update_item
--- a/MoinMoin/datastruct/backends/wiki_dicts.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/datastruct/backends/wiki_dicts.py	Sat Mar 12 14:14:22 2011 +0100
@@ -10,7 +10,7 @@
 
 
 from flask import flaskg
-from MoinMoin.items import SOMEDICT
+from MoinMoin.config import SOMEDICT
 from MoinMoin.datastruct.backends import BaseDict, BaseDictsBackend, DictDoesNotExistError
 
 
--- a/MoinMoin/datastruct/backends/wiki_groups.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/datastruct/backends/wiki_groups.py	Sat Mar 12 14:14:22 2011 +0100
@@ -15,7 +15,7 @@
 """
 
 from flask import flaskg
-from MoinMoin.items import USERGROUP
+from MoinMoin.config import USERGROUP
 from MoinMoin.datastruct.backends import GreedyGroup, BaseGroupsBackend, GroupDoesNotExistError
 
 
--- a/MoinMoin/i18n/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/i18n/__init__.py	Sat Mar 12 14:14:22 2011 +0100
@@ -14,8 +14,9 @@
 """
 
 
+from babel import Locale
+
 from flask import current_app, request, flaskg
-
 from flaskext.babel import Babel, gettext, ngettext, lazy_gettext
 
 _ = gettext
@@ -41,13 +42,20 @@
     if u and u.locale is not None:
         # locale is given in user profile, use it
         locale = u.locale
+        logging.debug("user locale = %r" % locale)
     else:
         # try to guess the language from the user accept
         # header the browser transmits. The best match wins.
-        supported_languages = ['de', 'fr', 'en'] # XXX
-        locale = request.accept_languages.best_match(supported_languages)
+        logging.debug("request.accept_languages = %r" % request.accept_languages)
+        supported_locales = [Locale('en')] + current_app.babel_instance.list_translations()
+        logging.debug("supported_locales = %r" % supported_locales)
+        supported_languages = [str(l) for l in supported_locales]
+        logging.debug("supported_languages = %r" % supported_languages)
+        locale = request.accept_languages.best_match(supported_languages, 'en')
+        logging.debug("best match locale = %r" % locale)
     if not locale:
         locale = current_app.cfg.locale_default
+        logging.debug("default locale = %r" % locale)
     return locale
 
 
--- a/MoinMoin/items/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/items/__init__.py	Sat Mar 12 14:14:22 2011 +0100
@@ -15,7 +15,7 @@
 """
 
 
-import os, re, time, datetime, shutil, base64
+import os, re, time, datetime, base64
 import tarfile
 import zipfile
 import tempfile
@@ -51,47 +51,16 @@
 from MoinMoin.util.send_file import send_file
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, AccessDeniedError, \
                                    StorageError
-from MoinMoin.storage import HASH_ALGORITHM
+from MoinMoin.config import UUID, NAME, NAME_OLD, REVERTED_TO, ACL, \
+                            IS_SYSITEM, SYSITEM_VERSION,  USERGROUP, SOMEDICT, \
+                            MIMETYPE, SIZE, LANGUAGE, ITEMLINKS, ITEMTRANSCLUSIONS, \
+                            TAGS, ACTION, ADDRESS, HOSTNAME, USERID, EXTRA, COMMENT, \
+                            HASH_ALGORITHM
 
 COLS = 80
 ROWS_DATA = 20
 ROWS_META = 10
 
-UUID = "uuid"
-NAME = "name"
-NAME_OLD = "name_old"
-
-# if an item is reverted, we store the revision number we used for reverting there:
-REVERTED_TO = "reverted_to"
-
-# some metadata key constants:
-ACL = "acl"
-
-# This says: I am a system item
-IS_SYSITEM = "is_syspage"
-# This says: original sysitem as contained in release: <release>
-SYSITEM_VERSION = "syspage_version"
-
-# keys for storing group and dict information
-# group of user names, e.g. for ACLs:
-USERGROUP = "usergroup"
-# needs more precise name / use case:
-SOMEDICT = "somedict"
-
-MIMETYPE = "mimetype"
-SIZE = "size"
-LANGUAGE = "language"
-ITEMLINKS = "itemlinks"
-ITEMTRANSCLUSIONS = "itemtransclusions"
-TAGS = "tags"
-
-ACTION = "action"
-ADDRESS = "address"
-HOSTNAME = "hostname"
-USERID = "userid"
-EXTRA = "extra"
-COMMENT = "comment"
-
 
 class DummyRev(dict):
     """ if we have no stored Revision, we use this dummy """
@@ -214,8 +183,6 @@
             input_conv = reg.get(Type(self.mimetype), type_moin_document)
             if not input_conv:
                 raise TypeError("We cannot handle the conversion from %s to the DOM tree" % self.mimetype)
-            link_conv = reg.get(type_moin_document, type_moin_document,
-                    links='extern', url_root=Iri(request.url_root))
             smiley_conv = reg.get(type_moin_document, type_moin_document,
                     icon='smiley')
 
@@ -231,8 +198,6 @@
             for conv in converters:
                 if conv == 'smiley':
                     doc = smiley_conv(doc)
-                elif conv == 'link':
-                    doc = link_conv(doc)
             if cid:
                 app.cache.set(cid, doc)
         flaskg.clock.stop('conv_in_dom')
@@ -240,15 +205,21 @@
 
     def _expand_document(self, doc):
         from MoinMoin.converter import default_registry as reg
+        from MoinMoin.util.iri import Iri
         from MoinMoin.util.mime import type_moin_document
         include_conv = reg.get(type_moin_document, type_moin_document, includes='expandall')
         macro_conv = reg.get(type_moin_document, type_moin_document, macros='expandall')
+        link_conv = reg.get(type_moin_document, type_moin_document, links='extern',
+                url_root=Iri(request.url_root))
         flaskg.clock.start('conv_include')
         doc = include_conv(doc)
         flaskg.clock.stop('conv_include')
         flaskg.clock.start('conv_macro')
         doc = macro_conv(doc)
         flaskg.clock.stop('conv_macro')
+        flaskg.clock.start('conv_link')
+        doc = link_conv(doc)
+        flaskg.clock.stop('conv_link')
         return doc
 
     def _render_data(self):
@@ -310,6 +281,7 @@
                      # are automatically implanted when saving
                      NAME,
                      HASH_ALGORITHM,
+                     SIZE,
                      COMMENT,
                      ACTION,
                      ADDRESS, HOSTNAME, USERID,
@@ -470,8 +442,10 @@
         """
         remote_addr = request.remote_addr
         if remote_addr:
-            newrev[ADDRESS] = unicode(remote_addr)
-            newrev[HOSTNAME] = unicode(wikiutil.get_hostname(remote_addr))
+            if app.cfg.log_remote_addr:
+                newrev[ADDRESS] = unicode(remote_addr)
+                if app.cfg.log_reverse_dns_lookups:
+                    newrev[HOSTNAME] = unicode(wikiutil.get_hostname(remote_addr))
         if flaskg.user.valid:
             newrev[USERID] = unicode(flaskg.user.id)
 
@@ -502,7 +476,7 @@
     def get_index(self):
         """ create an index of sub items of this item """
         import re
-        from MoinMoin.search.term import NameRE
+        from MoinMoin.storage.terms import NameRE
 
         if self.name:
             prefix = self.name + u'/'
@@ -617,7 +591,7 @@
 
     def get_templates(self, mimetype=None):
         """ create a list of templates (for some specific mimetype) """
-        from MoinMoin.search.term import AND, LastRevisionMetaDataMatch
+        from MoinMoin.storage.terms import AND, LastRevisionMetaDataMatch
         term = LastRevisionMetaDataMatch(TAGS, ['template']) # XXX there might be other tags
         if mimetype:
             term = AND(term, LastRevisionMetaDataMatch(MIMETYPE, mimetype))
@@ -691,7 +665,7 @@
             mt = wikiutil.MimeType(mimestr=mimestr)
             content_disposition = mt.content_disposition(app.cfg)
             content_type = mt.content_type()
-            content_length = rev.size
+            content_length = rev[SIZE]
             file_to_send = rev
 
         # TODO: handle content_disposition is not None
--- a/MoinMoin/items/_tests/test_Item.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/items/_tests/test_Item.py	Sat Mar 12 14:14:22 2011 +0100
@@ -11,8 +11,8 @@
 from flask import flaskg
 
 from MoinMoin._tests import become_trusted
-from MoinMoin.items import Item, ApplicationXTar, NonExistent, Binary, Text, Image, TransformableBitmapImage, \
-                           MIMETYPE, ADDRESS, COMMENT, HOSTNAME, USERID, ACTION
+from MoinMoin.items import Item, ApplicationXTar, NonExistent, Binary, Text, Image, TransformableBitmapImage
+from MoinMoin.config import MIMETYPE, ADDRESS, COMMENT, HOSTNAME, USERID, ACTION
 
 class TestItem(object):
     def testNonExistent(self):
--- a/MoinMoin/macro/EditedSystemPages.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-# Copyright: 2004 Nir Soffer <nirs@freeshell.org>
-# Copyright: 2008,2011 MoinMoin:ThomasWaldmann
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-EditedSystemPages - list system pages that has been edited in this wiki.
-"""
-
-
-from flask import flaskg
-
-from MoinMoin.macro._base import MacroPageLinkListBase
-from MoinMoin.items import IS_SYSITEM
-from MoinMoin.storage.error import NoSuchRevisionError
-
-class Macro(MacroPageLinkListBase):
-    def macro(self, content, arguments, page_url, alternative):
-        edited_sys_items = []
-        for item in flaskg.storage.iteritems():
-            try:
-                rev = item.get_revision(-1)
-            except NoSuchRevisionError:
-                continue
-            is_sysitem = rev.get(IS_SYSITEM, False)
-            if is_sysitem:
-                version = rev.get(SYSITEM_VERSION)
-                if version is None:
-                    # if we don't have the version, it was edited:
-                    edited_sys_items.append(item.name)
-
-        # Format as numbered list, sorted by item name
-        edited_sys_items.sort()
-
-        return self.create_pagelink_list(edited_sys_items, ordered=True)
-
--- a/MoinMoin/macro/HighlighterList.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-# Copyright: 2009 MoinMoin:EugeneSyromyatnikov
-# Copyright: 2010,2011 MoinMoin:ThomasWaldmann
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-MoinMoin - HighlighterList Macro
-
-A simple macro for displaying a table with list of available Pygments lexers.
-
-Usage: <<HighlighterList>>
-"""
-
-
-import pygments.lexers
-
-from MoinMoin.i18n import _, L_, N_
-from MoinMoin.util.tree import moin_page
-from MoinMoin.macro._base import MacroBlockBase
-from MoinMoin.converter._table import TableMixin
-
-
-class Macro(TableMixin, MacroBlockBase):
-    def macro(self, content, arguments, page_url, alternative):
-        column_titles = [_('Lexer description'),
-                         _('Lexer names'),
-                         _('File patterns'),
-                         _('Mimetypes'),
-                        ]
-        lexers = pygments.lexers.get_all_lexers()
-        lexers = [[desc, ' '.join(names), ' '.join(patterns), ' '.join(mimetypes), ]
-                  for desc, names, patterns, mimetypes in lexers]
-
-        rows = [column_titles] + sorted(lexers)
-        return self.build_dom_table(rows)
-
--- a/MoinMoin/macro/InterWiki.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-# Copyright: 2007-2008,2011 MoinMoin:ThomasWaldmann
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-Outputs the interwiki map.
-"""
-
-
-from flask import current_app as app
-
-from MoinMoin.util.tree import moin_page, xlink
-from MoinMoin.util.interwiki import join_wiki
-from MoinMoin.macro._base import MacroBlockBase
-
-class Macro(MacroBlockBase):
-    def macro(self, content, arguments, page_url, alternative):
-        iwlist = app.cfg.interwiki_map.items()
-        iwlist.sort()
-
-        iw_list = moin_page.list()
-        for tag, url in iwlist:
-            href = join_wiki(url, 'RecentChanges')
-            link = moin_page.a(attrib={xlink.href: href}, children=[tag])
-            label = moin_page.code(children=[link])
-            iw_item_label = moin_page.list_item_label(children=[label])
-            if '$PAGE' not in url:
-                link = moin_page.a(attrib={xlink.href: url}, children=[url])
-            else:
-                link = url
-            body = moin_page.code(children=[link])
-            iw_item_body = moin_page.list_item_body(children=[body])
-            iw_item = moin_page.list_item(children=[iw_item_label, iw_item_body])
-            iw_list.append(iw_item)
-        return iw_list
-
--- a/MoinMoin/macro/PageSize.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-# Copyright: 2002 Juergen Hermann <jh@web.de>
-# Copyright: 2008,2011 MoinMoin:ThomasWaldmann
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-MoinMoin - PageSize Macro displays an ordered list with page sizes and names
-"""
-
-from flask import flaskg
-from MoinMoin.storage.error import NoSuchRevisionError
-from MoinMoin.macro._base import MacroNumberPageLinkListBase
-
-class Macro(MacroNumberPageLinkListBase):
-    def macro(self, content, arguments, page_url, alternative):
-        sizes_pagenames = []
-        for item in flaskg.storage.iteritems():
-            try:
-                rev = item.get_revision(-1)
-            except NoSuchRevisionError:
-                # XXX we currently also get user items, they have no revisions -
-                # but in the end, they should not be readable by the user anyways
-                continue
-            sizes_pagenames.append((rev.size, item.name))
-        sizes_pagenames.sort()
-        sizes_pagenames.reverse()
-
-        return self.create_number_pagelink_list(sizes_pagenames)
-
--- a/MoinMoin/script/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/script/__init__.py	Sat Mar 12 14:14:22 2011 +0100
@@ -1,236 +1,45 @@
 # Copyright: 2000-2002 Juergen Hermann <jh@web.de>
-# Copyright: 2006 MoinMoin:ThomasWaldmann
+# Copyright: 2006,2011 MoinMoin:ThomasWaldmann
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
 """
-    MoinMoin - Extension Script Package
+MoinMoin - Extension Script Package
 """
 
-
-import os, sys, time
-from StringIO import StringIO
-
-flag_quiet = 0
-
-# ScriptRequest -----------------------------------------------------------
-
-class ScriptRequest(object):
-    """this is for scripts (MoinMoin/script/*) running from the commandline (CLI).
-    """
-    def __init__(self, instream, outstream, errstream):
-        self.instream = instream
-        self.outstream = outstream
-        self.errstream = errstream
-
-    def read(self, n=None):
-        if n is None:
-            data = self.instream.read()
-        else:
-            data = self.instream.read(n)
-        return data
-
-    def write(self, data):
-        self.outstream.write(data)
-
-    def write_err(self, data):
-        self.errstream.write(data)
-
-
-class ScriptRequestCLI(ScriptRequest):
-    """ When a script runs directly on the shell, we just use the CLI request
-        object (see MoinMoin.request.request_cli) to do I/O (which will use stdin/out/err).
-    """
-    def __init__(self, request):
-        self.request = request
-
-    def read(self, n=None):
-        return self.request.read(n)
-
-    def write(self, data):
-        return self.request.write(data)
-
-    def write_err(self, data):
-        return self.request.write(data) # XXX use correct request method - log, error, whatever.
-
-class ScriptRequestStrings(ScriptRequest):
-    def __init__(self, instr):
-        self.instream = StringIO(instr)
-        self.outstream = StringIO()
-        self.errstream = StringIO()
-
-    def fetch_output(self):
-        outstr = self.outstream.get_value()
-        errstr = self.errstream.get_value()
-        self.outstream.close()
-        self.errstream.close()
-        return outstr, errstr
-
-
-# Logging -----------------------------------------------------------------
-
-def fatal(msgtext, **kw):
-    """ Print error msg to stderr and exit. """
-    sys.stderr.write("\n\nFATAL ERROR: " + msgtext + "\n")
-    sys.exit(1)
-
-
-def log(msgtext):
-    """ Optionally print error msg to stderr. """
-    if not flag_quiet:
-        sys.stderr.write(msgtext + "\n")
+import sys
 
 
-# Commandline Support --------------------------------------------------------
-
-class Script:
-    def __init__(self, cmd, usage, argv=None, def_values=None):
-        #print "argv:", argv, "def_values:", repr(def_values)
-        if argv is None:
-            self.argv = sys.argv[1:]
-        else:
-            self.argv = argv
-        self.def_values = def_values
-
-        global _start_time
-        _start_time = time.clock()
-
-        import optparse
-        from MoinMoin import project, version
-
-        rev = "%s %s" % (project, str(version))
-        sys.argv[0] = cmd
+def main(default_command='moin', wiki_config=None):
+    """
+    console_script entry point
+    """
+    from MoinMoin.app import create_app
+    from flaskext.script import Manager, Server
 
-        self.parser = optparse.OptionParser(
-            usage="%(cmd)s [command] %(usage)s" % {'cmd': os.path.basename(sys.argv[0]), 'usage': usage, },
-            version=rev, add_help_option=False)
-        self.parser.allow_interspersed_args = False
-        if def_values:
-            self.parser.set_defaults(**def_values.__dict__)
-        self.parser.add_option(
-            "-q", "--quiet",
-            action="store_true", dest="quiet",
-            help="Be quiet (no informational messages)"
-        )
-        self.parser.add_option(
-            "--show-timing",
-            action="store_true", dest="show_timing", default=False,
-            help="Show timing values [default: False]"
-        )
+    manager = Manager(create_app)
+    manager.add_option('-c', '--config', dest='config', required=False, default=wiki_config)
+    manager.add_command("moin", Server(host='127.0.0.1', port=8080))
 
-    def run(self, showtime=1):
-        """ Run the main function of a command. """
-        global flag_quiet
-        try:
-            try:
-                self.options, self.args = self.parser.parse_args(self.argv)
-                flag_quiet = self.options.quiet
-                # ToDo check if we need to initialize request (self.init_request())
-                self.mainloop()
-            except KeyboardInterrupt:
-                log("*** Interrupted by user!")
-            except SystemExit:
-                showtime = 0
-                raise
-        finally:
-            if showtime:
-                self.logRuntime()
+    from MoinMoin.script.account.create import Create_User
+    manager.add_command("account_create", Create_User())
+    from MoinMoin.script.account.disable import Disable_User
+    manager.add_command("account_disable", Disable_User())
+    from MoinMoin.script.account.resetpw import Set_Password
+    manager.add_command("account_password", Set_Password())
+    from MoinMoin.script.maint.reduce_revisions import Reduce_Revisions
+    manager.add_command("maint_reduce_revisions", Reduce_Revisions())
+    from MoinMoin.script.maint.set_meta import Set_Meta
+    manager.add_command("maint_set_meta", Set_Meta())
+    from MoinMoin.script.maint.create_item import Create_Item
+    manager.add_command("maint_create_item", Create_Item())
+    from MoinMoin.script.maint.modified_systemitems import Modified_SystemItems
+    manager.add_command("maint_modified_systemitems", Modified_SystemItems())
+    from MoinMoin.script.maint.xml import XML
+    manager.add_command("maint_xml", XML())
 
-    def logRuntime(self):
-        """ Print the total command run time. """
-        if self.options.show_timing:
-            log("Needed %.3f secs." % (time.clock() - _start_time, ))
+    return manager.run(default_command=default_command)
 
 
-class MoinScript(Script):
-    """ Moin main script class """
-
-    def __init__(self, argv=None, def_values=None):
-        Script.__init__(self, "moin", "[general options] command subcommand [specific options]", argv, def_values)
-        # those are options potentially useful for all sub-commands:
-        self.parser.add_option(
-            "--config-dir", metavar="DIR", dest="config_dir",
-            help=("Path to the directory containing the wiki "
-                  "configuration files. [default: current directory]")
-        )
-        self.parser.add_option(
-            "--wiki-url", metavar="WIKIURL", dest="wiki_url",
-            help="URL of a single wiki to migrate e.g. http://localhost/mywiki/ [default: CLI]"
-        )
-        self.parser.add_option(
-            "--page", dest="page", default='',
-            help="wiki page name [default: all pages]"
-        )
-
-    def _update_option_help(self, opt_string, help_msg):
-        """ Update the help string of an option. """
-        for option in self.parser.option_list:
-            if option.get_opt_string() == opt_string:
-                option.help = help_msg
-                break
-
-    def init_request(self):
-        """ create request """
-        from MoinMoin.web.contexts import ScriptContext
-        url = self.options.wiki_url or None
-        self.request = ScriptContext(url, self.options.page)
-
-    def mainloop(self):
-        # Insert config dir or the current directory to the start of the path.
-        config_dir = self.options.config_dir
-        if config_dir:
-            if os.path.isdir(config_dir):
-                sys.path.insert(0, os.path.abspath(config_dir))
-            else:
-                fatal("bad path given to --config-dir option")
+def fatal(msg):
+    sys.exit(msg)
 
-        args = self.args
-        if len(args) < 2:
-            self.parser.print_help()
-            fatal("""You must specify a command module and name:
-
-moin ... account check ...
-moin ... account create ...
-moin ... account disable ...
-moin ... account resetpw ...
-
-moin ... index build ...
-
-moin ... maint reducewiki ...
-
-moin ... migration data ...
-moin ... migration backend ...
-
-General options:
-    Most commands need some general parameters before command subcommand:
-    --config-dir=/config/directory
-        Mandatory for most commands and specifies the directory that contains
-        your wikiconfig.py (or farmconfig.py).
-
-    --wiki-url=http://wiki.example.org/
-        Mandatory for most commands and specifies the url of the wiki you like
-        to operate on.
-
-Specific options:
-    Most commands need additional parameters after command subcommand.
-
-    To obtain additonal help on a command use 'moin module subcommand --help'
-""")
-
-        cmd_module, cmd_name = args[:2]
-        from MoinMoin.util import plugins
-        try:
-            plugin_class = plugins.importBuiltinPlugin('script.%s' % cmd_module, cmd_name, 'PluginScript')
-        except plugins.PluginMissingError:
-            fatal("Command plugin %r, command %r was not found." % (cmd_module, cmd_name))
-
-        # We have to use the args list here instead of optparse, as optparse only
-        # deals with things coming before command subcommand.
-        if "--help" in args or "-h" in args:
-            print "MoinMoin Help - %s/ %s\n" % (cmd_module, cmd_name)
-            print plugin_class.__doc__
-            print "Command line reference:"
-            print "======================="
-            plugin_class(args[2:], self.options).parser.print_help()
-        else:
-            plugin_class(args[2:], self.options).run() # all starts again there
-
--- a/MoinMoin/script/index/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-# Copyright: 2006 MoinMoin:ThomasWaldmann
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - Fullsearch Index Script Package
-
-    TODO: rename this module back to xapian when script framework is
-    fixed to not confuse it with the xapian.org "xapian" module.
-"""
-
-
-from MoinMoin.util import pysupport
-
-# create a list of extension scripts from the subpackage directory
-index_scripts = pysupport.getPackageModules(__file__)
-modules = index_scripts
-
--- a/MoinMoin/script/index/build.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-# Copyright: 2006-2009 MoinMoin:ThomasWaldmann
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-MoinMoin - build xapian search engine's index
-"""
-
-
-import os
-import errno
-import shutil
-
-from MoinMoin.script import MoinScript
-
-class IndexScript(MoinScript):
-    """\
-Purpose:
-========
-This tool allows you to control xapian's index of Moin.
-
-Detailed Instructions:
-======================
-General syntax: moin [options] index build [build-options]
-
-[options] usually should be:
-    --config-dir=/path/to/my/cfg/ --wiki-url=http://wiki.example.org/
-
-[build-options] see below:
-    Please note:
-    * You must run this script as the owner of the wiki files,
-      usually this is the web server user.
-    * You may add the build-option --files=files.lst to let the indexer
-      also consider the filesystem filenames contained in that file (one
-      filename per line). Search results from these files will be "found"
-      under a special pseudo page called FS (like File System).
-      Without this option, the indexer will just consider wiki items.
-
-    1. Conditionally (considering modification time) update the index:
-       moin ... index build --mode=update
-
-    2. Unconditionally add to the index:
-       moin ... index build --mode=add
-
-    3. Completely rebuild the index (1-stage):
-       moin ... index build --mode=rebuild
-
-       Note: until it has completely built the new index, the wiki will still
-       use the old index. After rebuild has completed, it kills the old index
-       and moves the new index into its place.
-       If the wiki uses the index at that moment, that might have unwanted side
-       effects. If you want to avoid that and you can accept a short downtime,
-       consider using this safer method:
-
-       Completely rebuild the index (2-stage):
-       # takes long, does not interfere with wiki searches:
-       moin ... index build --mode=buildnewindex
-       stop this moin wiki process(es)
-       # quick, replaces the old index with the new one:
-       moin ... index build --mode=usenewindex
-       start this moin wiki process(es)
-"""
-
-    def __init__(self, argv, def_values):
-        MoinScript.__init__(self, argv, def_values)
-        self.parser.add_option(
-            "--files", metavar="FILES", dest="file_list",
-            help="filename of file list, e.g. files.lst (one file per line)"
-        )
-        self.parser.add_option(
-            "--mode", metavar="MODE", dest="mode",
-            help="either add (unconditionally add), update (conditional update), rebuild (complete 1-stage index rebuild)"
-                 " or buildnewindex and usenewindex (complete 2-stage index rebuild)"
-        )
-
-    def mainloop(self):
-        self.init_request()
-        # Do we have additional files to index?
-        if self.options.file_list:
-            self.files = file(self.options.file_list)
-        else:
-            self.files = None
-        self.command()
-
-class PluginScript(IndexScript):
-    """ Xapian index build script class """
-
-    def command(self):
-        from MoinMoin.search.Xapian import XapianIndex
-        mode = self.options.mode
-        if mode in ('rebuild', 'buildnewindex'):
-            # rebuilding the DB into a new index directory, so the rebuild
-            # process does not interfere with the currently in-use DB
-            idx_mode, idx_name = 'add', 'index.new'
-        elif mode in ('add', 'update'):
-            # update/add in-place
-            idx_mode, idx_name = mode, 'index'
-        elif mode == 'usenewindex':
-            pass # nothing todo
-        else:
-            pass # XXX give error msg about invalid mode
-
-        if mode != 'usenewindex':
-            idx = XapianIndex(self.request, name=idx_name)
-            idx.indexPages(self.files, idx_mode)
-
-        if mode in ('rebuild', 'usenewindex'):
-            # 'rebuild' is still a bit dirty, because just killing old index will
-            # fail currently running searches. Thus, maybe do this in a time
-            # with litte wiki activity or better use 'buildnewindex' and
-            # 'usenewindex' (see above).
-            # XXX code here assumes that idx.db is a directory
-            # TODO improve this with xapian stub DBs
-            idx_old = XapianIndex(self.request, name='index').db
-            idx_new = XapianIndex(self.request, name='index.new').db
-            try:
-                shutil.rmtree(idx_old)
-            except OSError, err:
-                if err.errno != errno.ENOENT: # ignore it if we have no current index
-                    raise
-            os.rename(idx_new, idx_old)
-
--- a/MoinMoin/script/maint/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/script/maint/__init__.py	Sat Mar 12 14:14:22 2011 +0100
@@ -5,10 +5,3 @@
     MoinMoin - Maintenance Script Package
 """
 
-
-from MoinMoin.util import pysupport
-
-# create a list of extension scripts from the subpackage directory
-maint_scripts = pysupport.getPackageModules(__file__)
-modules = maint_scripts
-
--- a/MoinMoin/script/maint/create_item.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/script/maint/create_item.py	Sat Mar 12 14:14:22 2011 +0100
@@ -2,9 +2,9 @@
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
 """
-    MoinMoin - creates an item 
+    MoinMoin - creates an item
 
-    This script creates a new revision of an item
+    This script creates a new revision of an item.
 """
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/script/maint/modified_systemitems.py	Sat Mar 12 14:14:22 2011 +0100
@@ -0,0 +1,43 @@
+# Copyright: 2004 Nir Soffer <nirs@freeshell.org>
+# Copyright: 2008,2011 MoinMoin:ThomasWaldmann
+# Copyright: 2011 MoinMoin:ReimarBauer
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+    MoinMoin - list system items that has been edited in this wiki.
+
+"""
+
+
+from flask import current_app as app
+from flaskext.script import Command
+from MoinMoin.config import IS_SYSITEM, SYSITEM_VERSION
+from MoinMoin.storage.error import NoSuchRevisionError
+
+class Modified_SystemItems(Command):
+    description = 'This command can be used to list system items that has been edited in this wiki.'
+
+    def run(self):
+        storage = app.unprotected_storage
+        edited_sys_items = []
+        for item in storage.iteritems():
+            try:
+                rev = item.get_revision(-1)
+            except NoSuchRevisionError:
+                continue
+            is_sysitem = rev.get(IS_SYSITEM, False)
+            if is_sysitem:
+                version = rev.get(SYSITEM_VERSION)
+                if version is None:
+                    # if we don't have the version, it was edited:
+                    edited_sys_items.append(item.name)
+
+        # Format as numbered list, sorted by item name
+        edited_sys_items.sort()
+        if edited_sys_items:
+            print "Edited system items:"
+            for item_name in edited_sys_items:
+                print item_name
+        else:
+            print "Not any modified system items found!"
+
--- a/MoinMoin/script/maint/reduce_revisions.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/script/maint/reduce_revisions.py	Sat Mar 12 14:14:22 2011 +0100
@@ -13,7 +13,7 @@
 from flask import current_app as app
 from flaskext.script import Command, Option
 
-from MoinMoin.search import term
+from MoinMoin.storage.terms import NameRE
 
 
 class Reduce_Revisions(Command):
@@ -25,7 +25,7 @@
 
     def run(self, pattern):
         storage = app.unprotected_storage
-        query = term.NameRE(re.compile(pattern))
+        query = NameRE(re.compile(pattern))
         # If no pattern is given, the default regex will match every item.
         for item in storage.search_items(query):
             current_revno = item.next_revno - 1
--- a/MoinMoin/script/maint/set_meta.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/script/maint/set_meta.py	Sat Mar 12 14:14:22 2011 +0100
@@ -19,7 +19,7 @@
 from flaskext.script import Command, Option
 
 from MoinMoin.script import fatal
-from MoinMoin.search import term
+from MoinMoin.storage.terms import NameRE
 from MoinMoin.storage.error import NoSuchRevisionError
 
 class Set_Meta(Command):
@@ -42,7 +42,7 @@
             fatal("You need to either specify a proper key/value pair or "
                   "only a key you want to delete (with -r set).")
 
-        query = term.NameRE(re.compile(pattern))
+        query = NameRE(re.compile(pattern))
         for item in storage.search_items(query):
             try:
                 last_rev = item.get_revision(-1)
--- a/MoinMoin/script/maint/xml.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/script/maint/xml.py	Sat Mar 12 14:14:22 2011 +0100
@@ -35,6 +35,8 @@
             help='Load (unserialize) storage contents from a xml file.'),
         Option('--file', '-f', dest='xml_file', type=unicode,
             help='Filename of xml file to use [Default: use stdin/stdout].'),
+        Option('--moin19data', dest='moin19data', type=unicode,
+            help='For migration from moin 1.9, gives the path to the moin 1.9 data_dir.'),
         Option('--nrevs', dest='nrevs', type=int, default=0,
             help='Serialize only the last n revisions of each item [Default: all everything].'),
         Option('--exceptnrevs', dest='exceptnrevs', type=int, default=0,
@@ -49,7 +51,8 @@
             help='Serialize everything except the last n hours of each item [Default: everything].')
     )
 
-    def run(self, save, load, xml_file, nrevs, exceptnrevs, ndays, exceptndays, nhours, exceptnhours):
+    def run(self, save, load, xml_file, moin19data,
+            nrevs, exceptnrevs, ndays, exceptndays, nhours, exceptnhours):
         if load == save: # either both True or both False
             fatal("You need to give either --load or --save!")
         if not xml_file:
@@ -58,7 +61,17 @@
             elif save:
                 xml_file = sys.stdout
 
-        storage = app.unprotected_storage
+        if moin19data:
+            # this is for backend migration scenario from moin 1.9
+            from MoinMoin.storage.backends import create_simple_mapping, router
+            namespace_mapping, router_index_uri = \
+                create_simple_mapping(backend_uri='fs19:%s' % moin19data)
+            storage = router.RouterBackend(
+                    [(ns, be) for ns, be, acls in namespace_mapping],
+                    index_uri=router_index_uri)
+        else:
+            # this deals with the normal storage
+            storage = app.unprotected_storage
         now = time.time()
 
         sincetime = 0
--- a/MoinMoin/script/migration/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-# Copyright: 2006 MoinMoin:ThomasWaldmann
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - Migration Script Package
-"""
-
-
-from MoinMoin.util import pysupport
-
-# create a list of extension scripts from the subpackage directory
-migration_scripts = pysupport.getPackageModules(__file__)
-modules = migration_scripts
-
--- a/MoinMoin/script/migration/backend.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-# Copyright: 2008 MoinMoin:PawelPacana
-# Copyright: 2008-2009 MoinMoin:ChristopherDenter
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - backend migration script
-
-    Migrate 1.7, 1.8 and 1.9 wiki data (including users) to the new
-    2.0 storage format.
-    Assumptions:
-    - defined namespace_mapping in wikiconfig (Contains destination backends.)
-    - defined old_instance_path in wikiconfig (may be removed after conversion)
-"""
-
-
-import shutil, sys
-from os.path import join
-
-from flask import flaskg
-from flask import current_app as app
-
-from MoinMoin.script import MoinScript, fatal
-from MoinMoin.wsgiapp import init_unprotected_backends
-from MoinMoin.storage.backends import fs19
-
-class PluginScript(MoinScript):
-    """Backend migration class."""
-    def __init__(self, argv, def_values):
-        MoinScript.__init__(self, argv, def_values)
-        self.parser.add_option(
-            "-v", "--verbose", dest="verbose", action="store_true",
-            help="Provide progress information while performing the migration"
-        )
-        self.parser.add_option(
-            "-f", "--fails", dest="show_failed", action="store_true",
-            help="Print failed migration items"
-        )
-
-    def mainloop(self):
-        self.init_request()
-        request = self.request
-        init_unprotected_backends(request)
-        cfg = app.cfg
-
-        try:
-            data_dir_old = cfg.data_dir_old
-            user_dir_old = cfg.user_dir_old
-        except AttributeError:
-            fatal("""
-The backend migration did not find your old wiki data.
-
-Please, configure in your wiki config:
-    data_dir_old = '.../data' # must be the path of your old data directory
-                              # (it must contain the pages/ subdirectory)
-    user_dir_old = '.../data/user' # must be the path of your old user profile directory
-                                   # or None (no conversion of user profiles)
-""")
-
-        page_backend = fs19.FSPageBackend(data_dir_old)
-        dest_content = flaskg.unprotected_storage.get_backend(cfg.ns_content)
-        sys.stdout.write("Starting backend migration.\nConverting data.\n")
-        content_fails = dest_content.clone(page_backend, self.options.verbose)[2]
-        if self.options.show_failed and len(content_fails):
-            sys.stdout.write("\nFailed report\n-------------\n")
-            for name in content_fails.iterkeys():
-                sys.stdout.write("%r: %s\n" % (name, content_fails[name]))
-        sys.stdout.write("Content migration finished!\n")
-
-        if user_dir_old:
-            user_backend = fs19.FSUserBackend(user_dir_old)
-            dest_userprofile = flaskg.unprotected_storage.get_backend(cfg.ns_user_profile)
-            sys.stdout.write("Converting users.\n")
-            user_fails = dest_userprofile.clone(user_backend, self.options.verbose)[2]
-            if self.options.show_failed and len(user_fails):
-                sys.stdout.write("\nFailed report\n-------------\n")
-                for name in user_fails.iterkeys():
-                    sys.stdout.write("%r: %s\n" % (name, user_fails[name]))
-            sys.stdout.write("User profile migration finished!\n")
-
--- a/MoinMoin/script/moin.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-#!/usr/bin/env python
-# Copyright: 2006 MoinMoin:ThomasWaldmann
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - "moin" is the main script command and calls other stuff as
-    a sub-command.
-
-    Usage: moin cmdmodule cmdname [options]
-"""
-
-
-def run():
-    from MoinMoin.script import MoinScript
-    MoinScript().run(showtime=0)
-
-if __name__ == "__main__":
-    # Insert the path to MoinMoin in the start of the path
-    import sys, os
-    # we use position 1 (not 0) to give a config dir inserted at 0 a chance
-    # beware: we have a wikiconfig.py at the toplevel directory in the branch
-    sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), os.pardir, os.pardir)))
-
-    run()
-
--- a/MoinMoin/script/old/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-# Copyright: 2004 by Thomas Waldmann
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - misc example Scripts
-"""
-
-
--- a/MoinMoin/script/old/print_stats.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-#!/usr/bin/env python
-# Copyright: 2005 by MoinMoin:ThomasWaldmann
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - Print statistics gathered by hotshot profiler
-
-    Usage:
-        print_stats.py statsfile
-
-    Typical usage:
-     1. Edit moin.py and activate the hotshot profiler, set profile file name
-     2. Run moin.py
-     3. Do some request, with a browser, script or ab
-     4. Stop moin.py
-     5. Run this tool: print_stats.py moin.prof
-
-    Currently CGI and twisted also have a hotshot profiler integration.
-"""
-
-
-def run():
-    import sys
-    from hotshot import stats
-
-    if len(sys.argv) != 2:
-        print __doc__
-        sys.exit()
-
-    # Load and print stats
-    s = stats.load(sys.argv[1])
-    s.strip_dirs()
-    s.sort_stats('cumulative', 'time', 'calls')
-    s.print_stats(40)
-    s.print_callers(40)
-
-if __name__ == "__main__":
-    run()
-
--- a/MoinMoin/search/Xapian/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-# Copyright: 2006-2009 MoinMoin:ThomasWaldmann
-# Copyright: 2006 MoinMoin:FranzPletz
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - xapian search engine
-"""
-
-
-from MoinMoin.search.Xapian.indexing import XapianIndex, Query, MoinSearchConnection, MoinIndexerConnection, XapianDatabaseLockError
-from MoinMoin.search.Xapian.tokenizer import WikiAnalyzer
-
--- a/MoinMoin/search/Xapian/indexing.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,537 +0,0 @@
-# Copyright: 2006-2009 MoinMoin:ThomasWaldmann
-# Copyright: 2006 MoinMoin:FranzPletz
-# Copyright: 2009 MoinMoin:DmitrijsMilajevs
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - xapian search engine indexing
-"""
-
-
-import os, re
-import xapian
-import xappy
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from flask import current_app as app
-
-from MoinMoin.search.builtin import BaseIndex
-from MoinMoin.search.Xapian.tokenizer import WikiAnalyzer
-from MoinMoin.util import filesys
-
-from MoinMoin.Page import Page
-from MoinMoin import config, wikiutil
-
-
-class Query(xapian.Query):
-    pass
-
-
-class UnicodeQuery(xapian.Query):
-    """ Xapian query object which automatically encodes unicode strings """
-
-    def __init__(self, *args, **kwargs):
-        """
-        :keyword encoding: specify the encoding manually (default: value of config.charset)
-        """
-        self.encoding = kwargs.get('encoding', config.charset)
-
-        nargs = []
-        for term in args:
-            if isinstance(term, unicode):
-                term = term.encode(self.encoding)
-            elif isinstance(term, list) or isinstance(term, tuple):
-                term = [t.encode(self.encoding) for t in term]
-            nargs.append(term)
-
-        Query.__init__(self, *nargs, **kwargs)
-
-
-class MoinSearchConnection(xappy.SearchConnection):
-
-    def get_all_documents(self, query=None):
-        """
-        Return all the documents in the index (that match query, if given).
-        """
-        document_count = self.get_doccount()
-        query = query or self.query_all()
-        hits = self.search(query, 0, document_count)
-        return hits
-
-    def get_all_documents_with_fields(self, **fields):
-        """
-        Return all the documents in the index (that match the field=value kwargs given).
-        """
-        field_queries = [self.query_field(field, value) for field, value in fields.iteritems()]
-        query = self.query_composite(self.OP_AND, field_queries)
-        return self.get_all_documents(query)
-
-
-XapianDatabaseLockError = xappy.XapianDatabaseLockError
-
-class MoinIndexerConnection(xappy.IndexerConnection):
-
-    def __init__(self, *args, **kwargs):
-        super(MoinIndexerConnection, self).__init__(*args, **kwargs)
-        self._define_fields_actions()
-
-    def _define_fields_actions(self):
-        SORTABLE = xappy.FieldActions.SORTABLE
-        INDEX_EXACT = xappy.FieldActions.INDEX_EXACT
-        INDEX_FREETEXT = xappy.FieldActions.INDEX_FREETEXT
-        STORE_CONTENT = xappy.FieldActions.STORE_CONTENT
-
-        self.add_field_action('wikiname', INDEX_EXACT)
-        self.add_field_action('wikiname', STORE_CONTENT)
-        self.add_field_action('pagename', INDEX_EXACT)
-        self.add_field_action('pagename', STORE_CONTENT)
-        self.add_field_action('pagename', SORTABLE)
-        self.add_field_action('attachment', INDEX_EXACT)
-        self.add_field_action('attachment', STORE_CONTENT)
-        self.add_field_action('mtime', INDEX_EXACT)
-        self.add_field_action('mtime', STORE_CONTENT)
-        self.add_field_action('revision', STORE_CONTENT)
-        self.add_field_action('revision', INDEX_EXACT)
-        self.add_field_action('mimetype', INDEX_EXACT)
-        self.add_field_action('mimetype', STORE_CONTENT)
-        self.add_field_action('title', INDEX_FREETEXT, weight=100)
-        self.add_field_action('title', STORE_CONTENT)
-        self.add_field_action('content', INDEX_FREETEXT, spell=True)
-        self.add_field_action('domain', INDEX_EXACT)
-        self.add_field_action('domain', STORE_CONTENT)
-        self.add_field_action('lang', INDEX_EXACT)
-        self.add_field_action('lang', STORE_CONTENT)
-        self.add_field_action('stem_lang', INDEX_EXACT)
-        self.add_field_action('author', INDEX_EXACT)
-        self.add_field_action('linkto', INDEX_EXACT)
-        self.add_field_action('linkto', STORE_CONTENT)
-
-
-class StemmedField(xappy.Field):
-
-    def __init__(self, name, value, request):
-        analyzer = WikiAnalyzer(language=app.cfg.language_default)
-        value = ' '.join(unicode('%s %s' % (word, stemmed)).strip() for word, stemmed in analyzer.tokenize(value))
-        super(StemmedField, self).__init__(name, value)
-
-
-class XapianIndex(BaseIndex):
-
-    def __init__(self, request, name='index'):
-        super(XapianIndex, self).__init__(request)
-        self.db = os.path.join(self.main_dir, name)
-
-    def _main_dir(self):
-        """ Get the directory of the xapian index """
-        return os.path.join(app.cfg.xapian_index_dir, app.cfg.siteid)
-
-    def exists(self):
-        """ Check if index exists """
-        return os.path.exists(self.db)
-
-    def mtime(self):
-        """ Modification time of the index """
-        return os.path.getmtime(self.db)
-
-    def touch(self):
-        """ Touch the index """
-        filesys.touch(self.db)
-
-    def get_search_connection(self):
-        return MoinSearchConnection(self.db)
-
-    def get_indexer_connection(self):
-        return MoinIndexerConnection(self.db)
-
-    def _search(self, query, sort='weight', historysearch=0):
-        """
-        Perform the search using xapian
-
-        :param query: the search query objects
-        :param sort: the sorting of the results (default: 'weight')
-        :param historysearch: whether to search in all page revisions (default: 0) TODO: use/implement this
-        """
-        while True:
-            try:
-                searcher, timestamp = app.cfg.xapian_searchers.pop()
-                if timestamp != self.mtime():
-                    searcher.close()
-                else:
-                    break
-            except IndexError:
-                searcher = self.get_search_connection()
-                timestamp = self.mtime()
-                break
-
-        # Refresh connection, since it may be outdated.
-        searcher.reopen()
-        query = query.xapian_term(self.request, searcher)
-
-        # Get maximum possible amount of hits from xappy, which is number of documents in the index.
-        document_count = searcher.get_doccount()
-
-        kw = {}
-        if sort == 'page_name':
-            kw['sortby'] = 'pagename'
-
-        hits = searcher.search(query, 0, document_count, **kw)
-
-        app.cfg.xapian_searchers.append((searcher, timestamp))
-        return hits
-
-    def do_queued_updates(self, amount=-1):
-        """ Index <amount> entries from the indexer queue.
-
-            :param amount: amount of queue entries to process (default: -1 == all)
-        """
-        try:
-            request = self._indexingRequest(self.request)
-            connection = self.get_indexer_connection()
-            self.touch()
-            try:
-                done_count = 0
-                while amount:
-                    # trick: if amount starts from -1, it will never get 0
-                    amount -= 1
-                    try:
-                        pagename, attachmentname, revno = self.update_queue.get()
-                    except IndexError:
-                        # queue empty
-                        break
-                    else:
-                        logging.debug("got from indexer queue: %r %r %r" % (pagename, attachmentname, revno))
-                        if not attachmentname:
-                            if revno is None:
-                                # generic "index this page completely, with attachments" request
-                                self._index_page(request, connection, pagename, mode='update')
-                            else:
-                                # "index this page revision" request
-                                self._index_page_rev(request, connection, pagename, revno, mode='update')
-                        else:
-                            # "index this attachment" request
-                            self._index_attachment(request, connection, pagename, attachmentname, mode='update')
-                        done_count += 1
-            finally:
-                logging.debug("updated xapian index with %d queued updates" % done_count)
-                connection.close()
-        except XapianDatabaseLockError:
-            # another indexer has locked the index, we can retry it later...
-            logging.debug("can't lock xapian index, not doing queued updates now")
-
-    def _get_document(self, connection, doc_id, mtime, mode):
-        do_index = False
-
-        if mode == 'update':
-            try:
-                doc = connection.get_document(doc_id)
-                docmtime = long(doc.data['mtime'][0])
-            except KeyError:
-                do_index = True
-            else:
-                do_index = mtime > docmtime
-        elif mode == 'add':
-            do_index = True
-        else:
-            raise ValueError("mode must be 'update' or 'add'")
-
-        if do_index:
-            document = xappy.UnprocessedDocument()
-            document.id = doc_id
-        else:
-            document = None
-        return document
-
-    def _add_fields_to_document(self, request, document, fields=None, multivalued_fields=None):
-
-        fields_to_stem = ['title', 'content']
-
-        if fields is None:
-            fields = {}
-        if multivalued_fields is None:
-            multivalued_fields = {}
-
-        for field, value in fields.iteritems():
-            document.fields.append(xappy.Field(field, value))
-            if field in fields_to_stem:
-                document.fields.append(StemmedField(field, value, request))
-
-        for field, values in multivalued_fields.iteritems():
-            for value in values:
-                document.fields.append(xappy.Field(field, value))
-
-    def _get_languages(self, page):
-        """ Get language of a page and the language to stem it in
-
-        :param page: the page instance
-        """
-        lang = None
-        default_lang = app.cfg.language_default
-
-        # if we should stem, we check if we have a stemmer for the language available
-        if app.cfg.xapian_stemming:
-            lang = page.pi['language']
-            try:
-                xapian.Stem(lang)
-                # if there is no exception, lang is stemmable
-                return (lang, lang)
-            except xapian.InvalidArgumentError:
-                # lang is not stemmable
-                pass
-
-        if not lang:
-            # no lang found at all.. fallback to default language
-            lang = default_lang
-
-        # return actual lang and lang to stem in
-        return (lang, default_lang)
-
-    def _get_domains(self, page):
-        """ Returns a generator with all the domains the page belongs to
-
-        :param page: page
-        """
-        if page.isStandardPage():
-            yield 'standard'
-        if wikiutil.isSystemItem(page.page_name):
-            yield 'system'
-
-    def _index_page(self, request, connection, pagename, mode='update'):
-        """ Index a page.
-
-        Index all revisions (if wanted by configuration) and all attachments.
-
-        :param request: request suitable for indexing
-        :param connection: the Indexer connection object
-        :param pagename: a page name
-        :param mode: 'add' = just add, no checks
-                     'update' = check if already in index and update if needed (mtime)
-        """
-        page = Page(request, pagename)
-        revlist = page.getRevList() # recent revs first, does not include deleted revs
-        logging.debug("indexing page %r, %d revs found" % (pagename, len(revlist)))
-
-        if not revlist:
-            # we have an empty revision list, that means the page is not there any more,
-            # likely it (== all of its revisions, all of its attachments) got either renamed or nuked
-            wikiname = app.cfg.interwikiname or u'Self'
-
-            sc = self.get_search_connection()
-            docs_to_delete = sc.get_all_documents_with_fields(wikiname=wikiname, pagename=pagename)
-                                                              # any page rev, any attachment
-            sc.close()
-
-            for doc in docs_to_delete:
-                connection.delete(doc.id)
-            logging.debug('page %s (all revs, all attachments) removed from xapian index' % pagename)
-
-        else:
-            if app.cfg.xapian_index_history:
-                index_revs, remove_revs = revlist, []
-            else:
-                if page.exists(): # is current rev not deleted?
-                    index_revs, remove_revs = revlist[:1], revlist[1:]
-                else:
-                    index_revs, remove_revs = [], revlist
-
-            for revno in index_revs:
-                updated = self._index_page_rev(request, connection, pagename, revno, mode=mode)
-                logging.debug("updated page %r rev %d (updated==%r)" % (pagename, revno, updated))
-                if not updated:
-                    # we reached the revisions that are already present in the index
-                    break
-
-            for revno in remove_revs:
-                # XXX remove_revs can be rather long for pages with many revs and
-                # XXX most page revs usually will be already deleted. optimize?
-                self._remove_page_rev(request, connection, pagename, revno)
-                logging.debug("removed page %r rev %d" % (pagename, revno))
-
-            from MoinMoin.action import AttachFile
-            for attachmentname in AttachFile._get_files(request, pagename):
-                self._index_attachment(request, connection, pagename, attachmentname, mode)
-
-    def _index_page_rev(self, request, connection, pagename, revno, mode='update'):
-        """ Index a page revision.
-
-        :param request: request suitable for indexing
-        :param connection: the Indexer connection object
-        :param pagename: the page name
-        :param revno: page revision number (int)
-        :param mode: 'add' = just add, no checks
-                     'update' = check if already in index and update if needed (mtime)
-        """
-        page = Page(request, pagename, rev=revno)
-
-        wikiname = app.cfg.interwikiname or u"Self"
-        revision = str(page.get_real_rev())
-        itemid = "%s:%s:%s" % (wikiname, pagename, revision)
-        #mtime = wikiutil.timestamp2version(page.mtime())
-        mtime = page.mtime_usecs()
-
-        doc = self._get_document(connection, itemid, mtime, mode)
-        logging.debug("%s %s %r" % (pagename, revision, doc))
-        if doc:
-            mimetype = 'text/%s' % page.pi['format']  # XXX improve this
-
-            fields = {}
-            fields['wikiname'] = wikiname
-            fields['pagename'] = pagename
-            fields['attachment'] = '' # this is a real page, not an attachment
-            fields['mtime'] = str(mtime)
-            fields['revision'] = revision
-            fields['title'] = pagename
-            fields['content'] = page.get_raw_body()
-            fields['lang'], fields['stem_lang'] = self._get_languages(page)
-            fields['author'] = page.edit_info().get('editor', '?')
-
-            multivalued_fields = {}
-            multivalued_fields['mimetype'] = [mt for mt in [mimetype] + mimetype.split('/')]
-            multivalued_fields['domain'] = self._get_domains(page)
-            multivalued_fields['linkto'] = page.getPageLinks(request)
-
-            self._add_fields_to_document(request, doc, fields, multivalued_fields)
-
-            try:
-                connection.replace(doc)
-            except xappy.IndexerError, err:
-                logging.warning("IndexerError at %r %r %r (%s)" % (
-                    wikiname, pagename, revision, str(err)))
-
-        return bool(doc)
-
-    def _remove_page_rev(self, request, connection, pagename, revno):
-        """ Remove a page revision from the index.
-
-        :param request: request suitable for indexing
-        :param connection: the Indexer connection object
-        :param pagename: the page name
-        :param revno: a real revision number (int), > 0
-        """
-        wikiname = app.cfg.interwikiname or u"Self"
-        revision = str(revno)
-        itemid = "%s:%s:%s" % (wikiname, pagename, revision)
-        connection.delete(itemid)
-        logging.debug('page %s, revision %d removed from index' % (pagename, revno))
-
-    def _index_attachment(self, request, connection, pagename, attachmentname, mode='update'):
-        """ Index an attachment
-
-        :param request: request suitable for indexing
-        :param connection: the Indexer connection object
-        :param pagename: the page name
-        :param attachmentname: the attachment's name
-        :param mode: 'add' = just add, no checks
-                     'update' = check if already in index and update if needed (mtime)
-        """
-        from MoinMoin.action import AttachFile
-        wikiname = app.cfg.interwikiname or u"Self"
-        itemid = "%s:%s//%s" % (wikiname, pagename, attachmentname)
-
-        filename = AttachFile.getFilename(request, pagename, attachmentname)
-        # check if the file is still there. as we might be doing queued index updates,
-        # the file could be gone meanwhile...
-        if os.path.exists(filename):
-            mtime = wikiutil.timestamp2version(os.path.getmtime(filename))
-            doc = self._get_document(connection, itemid, mtime, mode)
-            logging.debug("%s %s %r" % (pagename, attachmentname, doc))
-            if doc:
-                page = Page(request, pagename)
-                mimetype, att_content = self.contentfilter(filename)
-
-                fields = {}
-                fields['wikiname'] = wikiname
-                fields['pagename'] = pagename
-                fields['attachment'] = attachmentname
-                fields['mtime'] = str(mtime)
-                fields['revision'] = '0'
-                fields['title'] = '%s/%s' % (pagename, attachmentname)
-                fields['content'] = att_content
-                fields['lang'], fields['stem_lang'] = self._get_languages(page)
-
-                multivalued_fields = {}
-                multivalued_fields['mimetype'] = [mt for mt in [mimetype] + mimetype.split('/')]
-                multivalued_fields['domain'] = self._get_domains(page)
-
-                self._add_fields_to_document(request, doc, fields, multivalued_fields)
-
-                connection.replace(doc)
-                logging.debug('attachment %s (page %s) updated in index' % (attachmentname, pagename))
-        else:
-            # attachment file was deleted, remove it from index also
-            connection.delete(itemid)
-            logging.debug('attachment %s (page %s) removed from index' % (attachmentname, pagename))
-
-    def _index_file(self, request, connection, filename, mode='update'):
-        """ index files (that are NOT attachments, just arbitrary files)
-
-        :param request: request suitable for indexing
-        :param connection: the Indexer connection object
-        :param filename: a filesystem file name
-        :param mode: 'add' = just add, no checks
-                     'update' = check if already in index and update if needed (mtime)
-        """
-        wikiname = app.cfg.interwikiname or u"Self"
-        fs_rootpage = 'FS' # XXX FS hardcoded
-
-        try:
-            itemid = "%s:%s" % (wikiname, os.path.join(fs_rootpage, filename))
-            mtime = wikiutil.timestamp2version(os.path.getmtime(filename))
-
-            doc = self._get_document(connection, itemid, mtime, mode)
-            logging.debug("%s %r" % (filename, doc))
-            if doc:
-                mimetype, file_content = self.contentfilter(filename)
-
-                fields = {}
-                fields['wikiname'] = wikiname
-                fields['pagename'] = fs_rootpage
-                fields['attachment'] = filename # XXX we should treat files like real pages, not attachments
-                fields['mtime'] = str(mtime)
-                fields['revision'] = '0'
-                fields['title'] = " ".join(os.path.join(fs_rootpage, filename).split("/"))
-                fields['content'] = file_content
-
-                multivalued_fields = {}
-                multivalued_fields['mimetype'] = [mt for mt in [mimetype] + mimetype.split('/')]
-
-                self._add_fields_to_document(request, doc, fields, multivalued_fields)
-
-                connection.replace(doc)
-
-        except (OSError, IOError, UnicodeError):
-            logging.exception("_index_file crashed:")
-
-    def _index_pages(self, request, files=None, mode='update', pages=None):
-        """ Index all (given) pages (and all given files)
-
-        This should be called from indexPages only!
-
-        :param request: request suitable for indexing
-        :param files: an optional list of files to index
-        :param mode: 'add' = just add, no checks
-                     'update' = check if already in index and update if needed (mtime)
-        :param pages: list of pages to index, if not given, all pages are indexed
-        """
-        if pages is None:
-            # Index all pages
-            pages = request.rootpage.getPageList(user='', exists=1)
-
-        try:
-            connection = self.get_indexer_connection()
-            self.touch()
-            try:
-                logging.info("indexing %d pages..." % len(pages))
-                for pagename in pages:
-                    self._index_page(request, connection, pagename, mode=mode)
-                if files:
-                    logging.info("indexing all files...")
-                    for fname in files:
-                        fname = fname.strip()
-                        self._index_file(request, connection, fname, mode)
-            finally:
-                connection.close()
-        except XapianDatabaseLockError:
-            logging.warning("xapian index is locked, can't index.")
-
--- a/MoinMoin/search/Xapian/search.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-# Copyright: 2005 MoinMoin:FlorianFesti
-# Copyright: 2005 MoinMoin:NirSoffer
-# Copyright: 2005 MoinMoin:AlexanderSchremmer
-# Copyright: 2006-2009 MoinMoin:ThomasWaldmann
-# Copyright: 2006 MoinMoin:FranzPletz
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - search engine internals
-"""
-
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin.i18n import _, L_, N_
-from MoinMoin.search.builtin import BaseSearch, MoinSearch, BaseIndex
-from MoinMoin.search.Xapian.indexing import XapianIndex
-
-class IndexDoesNotExistError(Exception):
-    pass
-
-class XapianSearch(BaseSearch):
-
-    def __init__(self, request, query, sort='weight', mtime=None, historysearch=0):
-        super(XapianSearch, self).__init__(request, query, sort, mtime, historysearch)
-
-        self.index = self._xapian_index()
-
-    def _xapian_index(self):
-        """ Get the xapian index if possible
-
-        :param request: current request
-        """
-        index = XapianIndex(self.request)
-
-        if not index.exists():
-            raise IndexDoesNotExistError
-
-        return index
-
-    def _search(self):
-        """ Search using Xapian
-
-        Get a list of pages using fast xapian search and
-        return moin search in those pages if needed.
-        """
-        index = self.index
-
-        search_results = index.search(self.query, sort=self.sort, historysearch=self.historysearch)
-        logging.debug("_xapianSearch: finds: %r" % search_results)
-
-        # Note: .data is (un)pickled inside xappy, so we get back exactly what
-        #       we had put into it at indexing time (including unicode objects).
-        pages = [{'uid': r.id,
-                  'wikiname': r.data['wikiname'][0],
-                  'pagename': r.data['pagename'][0],
-                  'attachment': r.data['attachment'][0],
-                  'revision': r.data.get('revision', [0])[0]}
-                 for r in search_results]
-        if not self.query.xapian_need_postproc():
-            # xapian handled the full query
-
-            return self._getHits(pages), (search_results.estimate_is_exact and '' or _('about'), search_results.matches_estimated)
-
-        # some postprocessing by MoinSearch is required
-        return MoinSearch(self.request, self.query, self.sort, self.mtime, self.historysearch, pages=pages)._search()
-
-
--- a/MoinMoin/search/Xapian/tokenizer.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +0,0 @@
-# Copyright: 2006-2008 MoinMoin:ThomasWaldmann
-# Copyright: 2006 MoinMoin:FranzPletz
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - A text analyzer for wiki syntax
-"""
-
-
-import re
-import xapian
-
-from flask import current_app as app
-
-from MoinMoin.parser.text_moin_wiki import Parser as WikiParser
-from MoinMoin import config
-
-
-class WikiAnalyzer(object):
-    """ A text analyzer for wiki syntax
-
-    The purpose of this class is to analyze texts/pages in wiki syntax
-    and yield single terms to feed into the xapian database.
-    """
-
-    singleword = r"[%(u)s][%(l)s]+" % {
-                     'u': config.chars_upper,
-                     'l': config.chars_lower,
-                 }
-
-    singleword_re = re.compile(singleword, re.U)
-    wikiword_re = re.compile(WikiParser.word_rule, re.UNICODE|re.VERBOSE)
-
-    token_re = re.compile(
-        r"(?P<company>\w+[&@]\w+)|" + # company names like AT&T and Excite@Home.
-        r"(?P<email>\w+([.-]\w+)*@\w+([.-]\w+)*)|" +    # email addresses
-        r"(?P<acronym>(\w\.)+)|" +          # acronyms: U.S.A., I.B.M., etc.
-        r"(?P<word>\w+)",                   # words (including WikiWords)
-        re.U)
-
-    dot_re = re.compile(r"[-_/,.]")
-    mail_re = re.compile(r"[-_/,.]|(@)")
-    alpha_num_re = re.compile(r"\d+|\D+")
-
-    def __init__(self, language=None):
-        """
-        :param language: if given, the language in which to stem words
-        """
-        self.stemmer = None
-        if app.cfg.xapian_stemming and language:
-            try:
-                stemmer = xapian.Stem(language)
-                # we need this wrapper because the stemmer returns a utf-8
-                # encoded string even when it gets fed with unicode objects:
-                self.stemmer = lambda word: stemmer(word).decode('utf-8')
-            except xapian.InvalidArgumentError:
-                # lang is not stemmable or not available
-                pass
-
-    def raw_tokenize_word(self, word, pos):
-        """ try to further tokenize some word starting at pos """
-        yield (word, pos)
-        if self.wikiword_re.match(word):
-            # if it is a CamelCaseWord, we additionally try to tokenize Camel, Case and Word
-            for m in re.finditer(self.singleword_re, word):
-                mw, mp = m.group(), pos + m.start()
-                for w, p in self.raw_tokenize_word(mw, mp):
-                    yield (w, p)
-        else:
-            # if we have Foo42, yield Foo and 42
-            for m in re.finditer(self.alpha_num_re, word):
-                mw, mp = m.group(), pos + m.start()
-                if mw != word:
-                    for w, p in self.raw_tokenize_word(mw, mp):
-                        yield (w, p)
-
-    def raw_tokenize(self, value):
-        """ Yield a stream of words from a string.
-
-        :param value: string to split, must be an unicode object or a list of
-                      unicode objects
-        """
-        if isinstance(value, list): # used for page links
-            for v in value:
-                yield (v, 0)
-        else:
-            tokenstream = re.finditer(self.token_re, value)
-            for m in tokenstream:
-                if m.group("acronym"):
-                    yield (m.group("acronym").replace('.', ''), m.start())
-                elif m.group("company"):
-                    yield (m.group("company"), m.start())
-                elif m.group("email"):
-                    displ = 0
-                    for word in self.mail_re.split(m.group("email")):
-                        if word:
-                            yield (word, m.start() + displ)
-                            displ += len(word) + 1
-                elif m.group("word"):
-                    for word, pos in self.raw_tokenize_word(m.group("word"), m.start()):
-                        yield word, pos
-
-    def tokenize(self, value):
-        """
-        Yield a stream of raw lower cased and stemmed words from a string.
-
-        :param value: string to split, must be an unicode object or a list of
-                      unicode objects
-        """
-        if self.stemmer:
-
-            def stemmer(value):
-                stemmed = self.stemmer(value)
-                if stemmed != value:
-                    return stemmed
-                else:
-                    return ''
-        else:
-            stemmer = lambda v: ''
-
-        for word, pos in self.raw_tokenize(value):
-            # Xapian stemmer expects lowercase input
-            word = word.lower()
-            yield word, stemmer(word)
-
--- a/MoinMoin/search/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-# Copyright: 2005 MoinMoin:FlorianFesti
-# Copyright: 2005 MoinMoin:NirSoffer
-# Copyright: 2005 MoinMoin:AlexanderSchremmer
-# Copyright: 2006 MoinMoin:ThomasWaldmann
-# Copyright: 2006 MoinMoin:FranzPletz
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - search engine
-"""
-
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from flask import current_app as app
-
-from MoinMoin.search.queryparser import QueryParser, QueryError
-from MoinMoin.search.builtin import MoinSearch
-
-
-def searchPages(request, query, sort='weight', mtime=None, historysearch=None, **kw):
-    """
-    Search the text of all pages for query.
-
-    :param request: current request
-    :param query: the expression (string or query objects) we want to search for
-    :keyword sort: sorting of the search results, either 'weight' or 'page_name'
-    :keyword mtime: only items modified since mtime
-    :keyword historysearch: include older revisions of items in search
-    :keyword titlesearch: treat all terms as title searches (passed to qp)
-    :keyword case: do case sensitive search (passed to qp)
-    :keyword regex: treat all terms as regular expression (passed to qp)
-    :rtype: SearchResults instance
-    :returns: search results
-    """
-    return _get_searcher(request, query, sort, mtime, historysearch, **kw).run()
-
-
-def _get_searcher(request, query, sort='weight', mtime=None, historysearch=None, **kw):
-    """
-    Return a searcher object according to the configuration.
-    """
-    query = _parse_query(query, **kw)
-    searcher = None
-
-    if app.cfg.xapian_search:
-        try:
-            from MoinMoin.search.Xapian.search import XapianSearch, IndexDoesNotExistError
-            searcher = XapianSearch(request, query, sort, mtime=mtime, historysearch=historysearch)
-        except ImportError, error:
-            logging.warning("%s. You should either set xapian_search = False in your wiki config or install/upgrade Xapian." % str(error))
-        except IndexDoesNotExistError:
-            logging.warning("Slow moin search is used because the Xapian index does not exist. You should create it using the moin index build command.")
-
-    if searcher is None:
-        searcher = MoinSearch(request, query, sort, mtime=mtime, historysearch=historysearch)
-
-    return searcher
-
-def _parse_query(query, **kw):
-    if isinstance(query, str) or isinstance(query, unicode):
-        query = QueryParser(**kw).parse_query(query)
-
-    return query
-
--- a/MoinMoin/search/_tests/test_search.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,467 +0,0 @@
-# Copyright: 2005 by Nir Soffer <nirs@freeshell.org>
-# Copyright: 2007-2010 by MoinMoin:ThomasWaldmann
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - MoinMoin.search Tests
-
-    We exclude underlay/system pages for some search tests to save time.
-"""
-
-
-import os, StringIO, time
-
-import py
-
-py.test.skip("broken")
-
-from MoinMoin.search import QueryError, _get_searcher
-from MoinMoin.search.queryparser import QueryParser
-from MoinMoin.search.builtin import MoinSearch
-from MoinMoin._tests import nuke_xapian_index, wikiconfig, become_trusted, create_item
-from MoinMoin.wikiutil import Version
-
-PY_MIN_VERSION = '1.0.0'
-if Version(version=py.version) < Version(version=PY_MIN_VERSION):
-    # There are some generative tests, which won't run on older versions!
-    # XXX These tests should be refactored to be able to be run with older versions of py.
-    py.test.skip('Currently py version %s is needed' % PY_MIN_VERSION)
-
-
-class TestQueryParsing(object):
-    """ search: query parser tests """
-
-    def testQueryParser(self):
-        """ search: test the query parser """
-        parser = QueryParser()
-        for query, wanted in [
-            # Even a single term is a and expression (this is needed for xapian because it
-            # only has AND_NOT, but not a simple NOT).  This is why we have many many brackets here.
-            ("a", '["a"]'),
-            ("-a", '[-"a"]'),
-            ("a b", '["a" "b"]'),
-            ("a -b c", '["a" -"b" "c"]'),
-            ("aaa bbb -ccc", '["aaa" "bbb" -"ccc"]'),
-            ("title:aaa title:bbb -title:ccc", '[title:"aaa" title:"bbb" -title:"ccc"]'),
-            ("title:case:aaa title:re:bbb -title:re:case:ccc", '[title:case:"aaa" title:re:"bbb" -title:re:case:"ccc"]'),
-            ("linkto:aaa", '[linkto:"aaa"]'),
-            ("domain:aaa", '[domain:"aaa"]'),
-            ("re:case:title:aaa", '[title:re:case:"aaa"]'),
-            ("(aaa or bbb) and (ccc or ddd)", '[[[["aaa"] or ["bbb"]]] [[["ccc"] or ["ddd"]]]]'),
-            ("(aaa or bbb) (ccc or ddd)", '[[[["aaa"] or ["bbb"]]] [[["ccc"] or ["ddd"]]]]'),
-            ("aaa or bbb", '[[["aaa"] or ["bbb"]]]'),
-            ("aaa or bbb or ccc", '[[["aaa"] or [[["bbb"] or ["ccc"]]]]]'),
-            ("aaa or bbb and ccc", '[[["aaa"] or ["bbb" "ccc"]]]'),
-            ("aaa and bbb or ccc", '[[["aaa" "bbb"] or ["ccc"]]]'),
-            ("aaa and bbb and ccc", '["aaa" "bbb" "ccc"]'),
-            ("aaa or bbb and ccc or ddd", '[[["aaa"] or [[["bbb" "ccc"] or ["ddd"]]]]]'),
-            ("aaa or bbb ccc or ddd", '[[["aaa"] or [[["bbb" "ccc"] or ["ddd"]]]]]'),
-            ("(HelpOn) (Administration)", '[["HelpOn"] ["Administration"]]'),
-            ("(HelpOn) (-Administration)", '[["HelpOn"] [-"Administration"]]'),
-            ("(HelpOn) and (-Administration)", '[["HelpOn"] [-"Administration"]]'),
-            ("(HelpOn) and (Administration) or (Configuration)", '[[[["HelpOn"] ["Administration"]] or [["Configuration"]]]]'),
-            ("(a) and (b) or (c) or -d", '[[[["a"] ["b"]] or [[[["c"]] or [-"d"]]]]]'),
-            ("a b c d e or f g h", '[[["a" "b" "c" "d" "e"] or ["f" "g" "h"]]]'),
-            ('"no', '[""no"]'),
-            ('no"', '["no""]'),
-            ("'no", "[\"'no\"]"),
-            ("no'", "[\"no'\"]"),
-            ('"no\'', '[""no\'"]')]:
-            result = parser.parse_query(query)
-            assert str(result) == wanted
-
-    def testQueryParserExceptions(self):
-        """ search: test the query parser """
-        parser = QueryParser()
-
-        def _test(q):
-            py.test.raises(QueryError, parser.parse_query, q)
-
-        for query in ['""', '(', ')', '(a or b']:
-            yield _test, query
-
-
-class BaseSearchTest(object):
-    """ search: test search """
-    doesnotexist = u'jfhsdaASDLASKDJ'
-
-    class Config(wikiconfig.Config):
-        load_xml = wikiconfig.Config._test_items_xml
-
-    # key - page name, value - page content. If value is None page
-    # will not be created but will be used for a search. None should
-    # be used for pages which already exist.
-    pages = {u'SearchTestPage': u'this is a test page',
-             u'SearchTestLinks': u'SearchTestPage',
-             u'SearchTestLinksLowerCase': u'searchtestpage',
-             u'SearchTestOtherLinks': u'SearchTestLinks',
-             u'TestEdit': u'TestEdit',
-             u'TestOnEditing': u'another test page',
-             u'ContentSearchUpper': u'Find the NEEDLE in the haystack.',
-             u'ContentSearchLower': u'Find the needle in the haystack.',
-             u'LanguageSetup': None,
-             u'HomePageWiki': None,
-             u'FrontPage': None,
-             u'RecentChanges': None,
-             u'HelpOnCreoleSyntax': None,
-             u'HelpIndex': None,
-            }
-
-    searcher_class = None
-
-    def _index_update(self):
-        pass
-
-    @classmethod
-    def setup_class(cls):
-        pass
-
-    def teardown_class(self):
-        pass
-
-    def setup_method(cls, method):
-        request = cls.request
-        become_trusted()
-
-        for page, text in cls.pages.iteritems():
-            if text:
-                create_item(page, text)
-
-    def get_searcher(self, query):
-        raise NotImplementedError
-
-    def search(self, query):
-        if isinstance(query, str) or isinstance(query, unicode):
-            query = QueryParser().parse_query(query)
-
-        return self.get_searcher(query).run()
-
-    def test_title_search_simple(self):
-        searches = {u'title:SearchTestPage': 1,
-                    u'title:LanguageSetup': 1,
-                    u'title:HelpIndex': 1,
-                    u'title:Help': 2,
-                    u'title:TestOn': 1,
-                    u'title:SearchTestNotExisting': 0,
-                    u'title:FrontPage': 1,
-                    u'title:TestOnEditing': 1,
-                   }
-
-        def test(query, res_count):
-            result = self.search(query)
-            test_result = len(result.hits)
-            assert test_result == res_count
-
-        for query, res_count in searches.iteritems():
-            yield query, test, query, res_count
-
-    def test_title_search_re(self):
-        expected_pages = set([u'SearchTestPage', u'SearchTestLinks', u'SearchTestLinksLowerCase', u'SearchTestOtherLinks', ])
-        result = self.search(ur'-domain:underlay -domain:system title:re:\bSearchTest')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-        result = self.search(ur'-domain:underlay -domain:system title:re:\bSearchTest\b')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert not found_pages
-
-    def test_title_search_case(self):
-        expected_pages = set([u'SearchTestPage', ])
-        result = self.search(u'-domain:underlay -domain:system title:case:SearchTestPage')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-        result = self.search(u'-domain:underlay -domain:system title:case:searchtestpage')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert not found_pages
-
-    def test_title_search_case_re(self):
-        expected_pages = set([u'SearchTestPage', ])
-        result = self.search(ur'-domain:underlay -domain:system title:case:re:\bSearchTestPage\b')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-        result = self.search(ur'-domain:underlay -domain:system title:case:re:\bsearchtestpage\b')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert not found_pages
-
-    def test_linkto_search_simple(self):
-        expected_pages = set([u'SearchTestLinks', ])
-        result = self.search(u'-domain:underlay -domain:system linkto:SearchTestPage')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-        result = self.search(u'-domain:underlay -domain:system linkto:SearchTestNotExisting')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert not found_pages
-
-    def test_linkto_search_re(self):
-        expected_pages = set([u'SearchTestLinks', u'SearchTestOtherLinks', ])
-        result = self.search(ur'-domain:underlay -domain:system linkto:re:\bSearchTest')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-        result = self.search(ur'-domain:underlay -domain:system linkto:re:\bSearchTest\b')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert not found_pages
-
-    def test_linkto_search_case(self):
-        expected_pages = set([u'SearchTestLinks', ])
-        result = self.search(u'-domain:underlay -domain:system linkto:case:SearchTestPage')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-        result = self.search(u'-domain:underlay -domain:system linkto:case:searchtestpage')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert not found_pages
-
-    def test_linkto_search_case_re(self):
-        expected_pages = set([u'SearchTestLinks', ])
-        result = self.search(ur'-domain:underlay -domain:system linkto:case:re:\bSearchTestPage\b')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-        result = self.search(ur'-domain:underlay -domain:system linkto:case:re:\bsearchtestpage\b')
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert not found_pages
-
-    def test_mimetype_search_simple(self):
-        result = self.search(u'mimetype:text/wiki')
-        test_result = len(result.hits)
-        assert test_result == 14
-
-    def test_mimetype_search_re(self):
-        result = self.search(ur'mimetype:re:\btext/wiki\b')
-        test_result = len(result.hits)
-        assert test_result == 14
-
-    def test_language_search_simple(self):
-        result = self.search(u'language:en')
-        test_result = len(result.hits)
-        assert test_result == 14
-
-    def test_domain_search_simple(self):
-        result = self.search(u'domain:system')
-        assert result.hits
-
-    def test_search_and(self):
-        """ search: title search with AND expression """
-        expected_pages = set([u'HelpOnCreoleSyntax', ])
-        result = self.search(u"title:HelpOnCreoleSyntax lang:en")
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-        result = self.search(u"title:HelpOnCreoleSyntax lang:de")
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert not found_pages
-
-        result = self.search(u"title:Help title:%s" % self.doesnotexist)
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert not found_pages
-
-    def testTitleSearchOR(self):
-        """ search: title search with OR expression """
-        expected_pages = set([u'FrontPage', u'RecentChanges', ])
-        result = self.search(u"title:FrontPage or title:RecentChanges")
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-    def testTitleSearchNegatedFindAll(self):
-        """ search: negated title search for some pagename that does not exist results in all pagenames """
-        result = self.search(u"-title:%s" % self.doesnotexist)
-        n_pages = len(self.pages)
-        test_result = len(result.hits)
-        assert test_result == n_pages
-
-    def testTitleSearchNegativeTerm(self):
-        """ search: title search for a AND expression with a negative term """
-        result = self.search(u"-title:FrontPage")
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert u'FrontPage' not in found_pages
-        test_result = len(result.hits)
-        n_pages = len(self.pages) - 1
-        assert test_result == n_pages
-
-        result = self.search(u"-title:HelpOn")
-        test_result = len(result.hits)
-        n_pages = len(self.pages) - 1
-        assert test_result == n_pages
-
-    def testFullSearchNegatedFindAll(self):
-        """ search: negated full search for some string that does not exist results in all pages """
-        result = self.search(u"-%s" % self.doesnotexist)
-        test_result = len(result.hits)
-        n_pages = len(self.pages)
-        assert test_result == n_pages
-
-    def testFullSearchRegexCaseInsensitive(self):
-        """ search: full search for regular expression (case insensitive) """
-        search_re = 'ne{2}dle' # matches 'NEEDLE' or 'needle' or ...
-        expected_pages = set(['ContentSearchUpper', 'ContentSearchLower', ])
-        result = self.search(u'-domain:underlay -domain:system re:%s' % search_re)
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-    def testFullSearchRegexCaseSensitive(self):
-        """ search: full search for regular expression (case sensitive) """
-        search_re = 'ne{2}dle' # matches 'needle'
-        expected_pages = set(['ContentSearchLower', ])
-        result = self.search(u'-domain:underlay -domain:system re:case:%s' % search_re)
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-    def testFullSearchNegativeTerm(self):
-        """ search: full search for a AND expression with a negative term """
-        helpon_count = len(self.search(u"HelpOn").hits)
-        result = self.search(u"HelpOn -Thumbnails")
-        assert 0 < len(result.hits) < helpon_count
-
-    def test_title_search(self):
-        expected_pages = set(['FrontPage', ])
-        query = QueryParser(titlesearch=True).parse_query('FrontPage')
-        result = self.search(query)
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-    def test_get_searcher(self):
-        assert isinstance(_get_searcher(self.request, ''), self.searcher_class)
-
-
-class TestMoinSearch(BaseSearchTest):
-    """ search: test Moin search """
-    searcher_class = MoinSearch
-
-    def get_searcher(self, query):
-        pages = [{'pagename': page, 'attachment': '', 'wikiname': 'Self', } for page in self.pages]
-        return MoinSearch(self.request, query, pages=pages)
-
-    def test_stemming(self):
-        expected_pages = set([u'TestEdit', u'TestOnEditing', ])
-        result = self.search(u"title:edit")
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-        expected_pages = set([u'TestOnEditing', ])
-        result = self.search(u"title:editing")
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-
-class TestXapianSearch(BaseSearchTest):
-    """ search: test Xapian indexing / search """
-
-    class Config(wikiconfig.Config):
-        xapian_search = True
-
-    def _index_update(self):
-        # for xapian, we queue index updates so they can get indexed later.
-        # here we make sure the queue will be processed completely,
-        # before we continue:
-        from MoinMoin.search.Xapian import XapianIndex
-        XapianIndex(self.request).do_queued_updates()
-
-    def get_searcher(self, query):
-        from MoinMoin.search.Xapian.search import XapianSearch
-        return XapianSearch(self.request, query)
-
-    def get_moin_search_connection(self):
-        from MoinMoin.search.Xapian import XapianIndex
-        return XapianIndex(self.request).get_search_connection()
-
-    def setup_class(self):
-        py.test.skip("xapian tests broken")
-        try:
-            from MoinMoin.search.Xapian import XapianIndex
-            from MoinMoin.search.Xapian.search import XapianSearch
-            self.searcher_class = XapianSearch
-
-        except ImportError, error:
-            if not str(error).startswith('Xapian '):
-                raise
-            py.test.skip('xapian is not installed')
-
-        nuke_xapian_index()
-        index = XapianIndex(self.request)
-        # Additionally, pages which were not created but supposed to be searched
-        # are indexed.
-        pages_to_index = [page for page in self.pages if not self.pages[page]]
-        index.indexPages(mode='add', pages=pages_to_index)
-
-        super(TestXapianSearch, self).setup_class()
-
-    def teardown_class(self):
-        nuke_xapian_index()
-
-    def test_get_all_documents(self):
-        connection = self.get_moin_search_connection()
-        documents = connection.get_all_documents()
-        n_pages = len(self.pages)
-        test_result = len(documents)
-        assert test_result == n_pages
-        for document in documents:
-            assert document.data['pagename'][0] in self.pages.keys()
-
-    def test_xapian_term(self):
-        parser = QueryParser()
-        connection = self.get_moin_search_connection()
-
-        prefixes = {u'': ([u'', u're:', u'case:', u'case:re:'], u'SearchTestPage'),
-                    u'title:': ([u'', u're:', u'case:', u'case:re:'], u'SearchTestPage'),
-                    u'linkto:': ([u'', u're:', u'case:', u'case:re:'], u'FrontPage'),
-                    u'mimetype:': ([u'', u're:'], u'text/wiki'),
-                    u'language:': ([u''], u'en'),
-                    u'domain:': ([u''], u'system'),
-                   }
-
-        def test_query(query):
-            query_ = parser.parse_query(query).xapian_term(self.request, connection)
-            print str(query_)
-            assert not query_.empty()
-
-        for prefix, data in prefixes.iteritems():
-            modifiers, term = data
-            for modifier in modifiers:
-                query = ''.join([prefix, modifier, term])
-                yield query, test_query, query
-
-    def test_stemming(self):
-        expected_pages = set([u'TestEdit', ])
-        result = self.search(u"title:edit")
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-        expected_pages = set([u'TestOnEditing', ])
-        result = self.search(u"title:editing")
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-
-class TestXapianSearchStemmed(TestXapianSearch):
-    """ search: test Xapian indexing / search - with stemming enabled """
-
-    class Config(wikiconfig.Config):
-        xapian_search = True
-        xapian_stemming = True
-
-    def test_stemming(self):
-        expected_pages = set([u'TestEdit', u'TestOnEditing', ])
-        result = self.search(u"title:edit")
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-        expected_pages = set([u'TestEdit', u'TestOnEditing', ])
-        result = self.search(u"title:editing")
-        found_pages = set([hit.page_name for hit in result.hits])
-        assert found_pages == expected_pages
-
-
-class TestGetSearcher(object):
-
-    class Config(wikiconfig.Config):
-        xapian_search = True
-
-    def test_get_searcher(self):
-        assert isinstance(_get_searcher(self.request, ''), MoinSearch), 'Xapian index is not created, despite the configuration, MoinSearch must be used!'
-
-coverage_modules = ['MoinMoin.search']
-
--- a/MoinMoin/search/_tests/test_terms.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,289 +0,0 @@
-# Copyright: 2008 MoinMoin:JohannesBerg
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - Term tests.
-"""
-
-
-import re
-
-from MoinMoin.search import term
-from MoinMoin.storage.backends.memory import MemoryBackend
-
-
-_item_contents = {
-    u'a': u'abcdefg hijklmnop',
-    u'b': u'bbbbbbb bbbbbbbbb',
-    u'c': u'Abiturienten Apfeltortor',
-    u'Lorem': u'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis placerat, tortor quis sollicitudin dictum, nisi tellus aliquam quam, ac varius lacus diam eget tortor. Nulla vehicula, nisi ac hendrerit aliquam, libero erat tempor ante, lobortis placerat lacus justo vitae erat. In rutrum odio a sem. In ac risus vel diam vulputate luctus. Fusce sit amet est. Morbi consectetuer eros vel risus. In nulla lacus, ultrices id, vestibulum tempus, dictum in, mauris. Quisque rutrum faucibus nisl. Suspendisse potenti. In hac habitasse platea dictumst. Donec ac magna ac eros malesuada facilisis. Pellentesque viverra nibh nec dui. Praesent venenatis lectus vehicula eros. Phasellus pretium, ante at mollis luctus, nibh lacus ultricies eros, vitae pharetra lacus leo at neque. Nullam vel sapien. In in diam id massa nonummy suscipit. Curabitur vel dui sed tellus pellentesque pretium.',
-}
-
-_item_metadata = {
-    u'a': {'m1': 'True', 'm2': '222'},
-    u'A': {'m1': 'True', 'm2': '333'},
-    u'b': {'m1': 'False', 'm2': '222'},
-    u'c': {'m1': 'True', 'm2': '222'},
-    u'B': {'m1': 'False', 'm2': '333'},
-    u'Lorem': {'m1': '7', 'm2': '444'},
-}
-
-_lastrevision_metadata = {
-    u'a': {'a': '1'},
-    u'A': {'a': ''},
-    u'b': {'a': '0'},
-    u'c': {'a': 'False'},
-    u'B': {'a': ''},
-    u'Lorem': {'a': '42'},
-}
-
-for n in _item_contents.keys():
-    nl = n.lower()
-    nu = n.upper()
-    _item_contents[nl] = _item_contents[n].lower()
-    _item_contents[nu] = _item_contents[n].upper()
-    if not nl in _item_metadata:
-        _item_metadata[nl] = _item_metadata[n]
-    if not nu in _item_metadata:
-        _item_metadata[nu] = _item_metadata[n]
-    if not nl in _lastrevision_metadata:
-        _lastrevision_metadata[nl] = _lastrevision_metadata[n]
-    if not nu in _lastrevision_metadata:
-        _lastrevision_metadata[nu] = _lastrevision_metadata[n]
-
-memb = MemoryBackend()
-for iname, md in _item_metadata.iteritems():
-    item = memb.create_item(iname)
-    item.change_metadata()
-    item.update(md)
-    item.publish_metadata()
-
-    rev = item.create_revision(0)
-    md = _lastrevision_metadata[iname]
-    rev.update(dict(name=iname, mimetype=u"application/octet-stream"))
-    rev.update(md)
-    rev.write(_item_contents[iname])
-    item.commit()
-
-item = memb.create_item('NR')
-item.change_metadata()
-item.update({'m1': 'True'})
-item.publish_metadata()
-del item
-
-class TermTestData:
-    def __init__(self, text):
-        self.text = text
-    def read(self, size=None):
-        return self.text
-
-class CacheAssertTerm(term.Term):
-    def __init__(self):
-        term.Term.__init__(self)
-        self.evalonce = False
-
-    def _evaluate(self, item):
-        assert not self.evalonce
-        self.evalonce = True
-        return True
-
-class AssertNotCalledTerm(term.Term):
-    def _evaluate(self, item):
-        assert False
-
-class TestTerms:
-    def _evaluate(self, term, itemname, expected):
-        if itemname is not None:
-            item = memb.get_item(itemname)
-        else:
-            item = None
-        term.prepare()
-        assert expected == term.evaluate(item)
-
-    def testSimpleTextSearch(self):
-        terms = [term.Text(u'abcdefg', True), term.Text(u'ijklmn', True)]
-        for item, expected in [('a', True), ('A', False), ('b', False), ('B', False), ('lorem', False), ('NR', False)]:
-            for t in terms:
-                yield self._evaluate, t, item, expected
-
-    def testSimpleTextSearchCI(self):
-        terms = [term.Text(u'abcdefg', False), term.Text(u'ijklmn', False)]
-        for item, expected in [('a', True), ('A', True), ('b', False), ('B', False), ('lorem', False)]:
-            for t in terms:
-                yield self._evaluate, t, item, expected
-
-    def testANDOR(self):
-        tests = [
-            (True,  [1, 1, 1, 1, 1]),
-            (True,  [1, 1, 1, 1]),
-            (True,  [1, 1, 1]),
-            (True,  [1, 1]),
-            (False, [0, 1, 1]),
-            (False, [0, 1, 1, 1]),
-            (False, [1, 0, 1, 1]),
-            (False, [1, 1, 0, 1]),
-            (False, [1, 1, 1, 0]),
-            (False, [0, 1, 1, 0]),
-        ]
-        for expected, l in tests:
-            l = [term.BOOL(i) for i in l]
-            t = term.AND(*l)
-            yield self._evaluate, t, 'a', expected
-        for expected, l in tests:
-            l = [term.BOOL(1 - i) for i in l]
-            t = term.OR(*l)
-            yield self._evaluate, t, 'a', not expected
-
-    def testXOR(self):
-        tests = [
-            (False, [1, 1, 1, 1, 1]),
-            (False, [1, 1, 1, 1]),
-            (False, [1, 1, 1]),
-            (False, [1, 1]),
-            (False, [0, 1, 1]),
-            (False, [0, 1, 1, 1]),
-            (False, [1, 0, 1, 1]),
-            (False, [1, 1, 0, 1]),
-            (False, [1, 1, 1, 0]),
-            (False, [0, 1, 1, 0]),
-            (True,  [0, 0, 0, 1, 0]),
-            (True,  [0, 0, 1, 0]),
-            (True,  [1, 0, 0]),
-            (True,  [0, 1]),
-            (False, [0, 0, 0]),
-        ]
-        for expected, l in tests:
-            l = [term.BOOL(i) for i in l]
-            t = term.XOR(*l)
-            yield self._evaluate, t, 'a', expected
-
-    def testTextSearchRE(self):
-        terms = [term.TextRE(re.compile('^abc')), term.TextRE(re.compile('\shij'))]
-        for item, expected in [('a', True), ('A', False), ('b', False), ('B', False), ('lorem', False), ('NR', False)]:
-            for t in terms:
-                yield self._evaluate, t, item, expected
-
-    def testTextSearchRE2(self):
-        terms = [term.TextRE(re.compile('sollici')), term.TextRE(re.compile('susci'))]
-        for item, expected in [('a', False), ('A', False), ('b', False), ('B', False), ('lorem', True), ('NR', False)]:
-            for t in terms:
-                yield self._evaluate, t, item, expected
-
-    def testResultCaching1(self):
-        cat = CacheAssertTerm()
-        expected = True
-        t = term.AND(cat, cat, cat)
-        yield self._evaluate, t, None, expected
-
-    def testResultCaching2(self):
-        cat = CacheAssertTerm()
-        expected = True
-        t = term.OR(cat, cat, cat)
-        yield self._evaluate, t, None, expected
-
-    def testResultCaching3(self):
-        cat = CacheAssertTerm()
-        expected = False
-        t = term.AND(cat, cat, cat, term.FALSE)
-        yield self._evaluate, t, None, expected
-
-    def testResultCaching4(self):
-        cat = CacheAssertTerm()
-        expected = True
-        t = term.OR(cat, cat, cat)
-        yield self._evaluate, t, None, expected
-
-    def testShortCircuitEval1(self):
-        yield self._evaluate, term.AND(term.TRUE, term.FALSE, AssertNotCalledTerm()), None, False
-
-    def testShortCircuitEval2(self):
-        yield self._evaluate, term.OR(term.TRUE, term.FALSE, AssertNotCalledTerm()), None, True
-
-    def testSimpleTitleSearch(self):
-        for item, expected in [('a', True), ('A', False), ('b', False), ('B', False), ('lorem', False), ('NR', False)]:
-            yield self._evaluate, term.Name(u'a', True), item, expected
-
-    def testSimpleTitleSearchCI(self):
-        for item, expected in [('a', True), ('A', True), ('b', False), ('B', False), ('lorem', False), ('NR', False)]:
-            yield self._evaluate, term.Name(u'a', False), item, expected
-
-    def testTitleRESearch(self):
-        for item, expected in [('a', True), ('A', False), ('b', False), ('B', False), ('lorem', True), ('NR', False)]:
-            yield self._evaluate, term.NameRE(re.compile('(a|e)')), item, expected
-
-    def testMetaMatch1(self):
-        t = term.ItemMetaDataMatch('m1', 'True')
-        for item, expected in [('a', True), ('A', True), ('b', False), ('B', False), ('lorem', False), ('NR', True)]:
-            yield self._evaluate, t, item, expected
-
-    def testMetaMatch2(self):
-        t = term.ItemMetaDataMatch('m2', '333')
-        for item, expected in [('a', False), ('A', True), ('b', False), ('B', True), ('lorem', False), ('NR', False)]:
-            yield self._evaluate, t, item, expected
-
-    def testMetaMatch3(self):
-        t = term.ItemMetaDataMatch('m2', '444')
-        for item, expected in [('a', False), ('A', False), ('b', False), ('B', False), ('lorem', True), ('NR', False)]:
-            yield self._evaluate, t, item, expected
-
-    def testHasMeta1(self):
-        t = term.ItemHasMetaDataKey('m3')
-        for item, expected in [('a', False), ('A', False), ('b', False), ('B', False), ('lorem', False), ('NR', False)]:
-            yield self._evaluate, t, item, expected
-
-    def testHasMeta2(self):
-        t = term.ItemHasMetaDataKey('m1')
-        for item, expected in [('a', True), ('A', True), ('b', True), ('B', True), ('lorem', True), ('NR', True)]:
-            yield self._evaluate, t, item, expected
-
-    def testHasMeta3(self):
-        t = term.LastRevisionHasMetaDataKey('a')
-        for item, expected in [('a', True), ('A', True), ('b', True), ('B', True), ('lorem', True), ('NR', False)]:
-            yield self._evaluate, t, item, expected
-
-    def testHasMeta4(self):
-        t = term.LastRevisionMetaDataMatch('a', '')
-        for item, expected in [('a', False), ('A', True), ('b', False), ('B', True), ('lorem', False), ('NR', False)]:
-            yield self._evaluate, t, item, expected
-
-    def testNameFn(self):
-        t = term.NameFn(lambda x: x in ['a', 'b', 'lorem'])
-        for item, expected in [('a', True), ('A', False), ('b', True), ('B', False), ('lorem', True), ('NR', False)]:
-            yield self._evaluate, t, item, expected
-
-    def testWordCI(self):
-        t = term.Word('Curabitur', False)
-        for item, expected in [('B', False), ('Lorem', True), ('lorem', True), ('LOREM', True), ('NR', False)]:
-            yield self._evaluate, t, item, expected
-
-    def testWord(self):
-        t = term.Word('Curabitur', True)
-        for item, expected in [('B', False), ('Lorem', True), ('lorem', False), ('LOREM', False), ('NR', False)]:
-            yield self._evaluate, t, item, expected
-
-    def testWordStartCI(self):
-        t = term.WordStart('Curabi', False)
-        for item, expected in [('B', False), ('Lorem', True), ('lorem', True), ('LOREM', True), ('NR', False)]:
-            yield self._evaluate, t, item, expected
-
-    def testWordStart(self):
-        t = term.WordStart('Curabi', True)
-        for item, expected in [('c', False), ('Lorem', True), ('lorem', False), ('LOREM', False), ('NR', False)]:
-            yield self._evaluate, t, item, expected
-
-    def testWordStart2(self):
-        t = term.WordStart('abitur', True)
-        for item, expected in [('c', True), ('C', False), ('Lorem', False), ('NR', False)]:
-            yield self._evaluate, t, item, expected
-
-    def testWordStart2CI(self):
-        t = term.WordStart('abitur', False)
-        for item, expected in [('c', True), ('C', True), ('Lorem', False), ('NR', False)]:
-            yield self._evaluate, t, item, expected
-
-    def testWordEndCI(self):
-        t = term.WordEnd('abitur', False)
-        for item, expected in [('c', False), ('Lorem', True), ('lorem', True), ('LOREM', True), ('NR', False)]:
-            yield self._evaluate, t, item, expected
-
-coverage_modules = ['MoinMoin.search.terms']
--- a/MoinMoin/search/_tests/test_wiki_analyzer.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-# Copyright: 2009 MoinMoin:DmitrijsMilajevs
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - MoinMoin.search.Xapian.tokenizer Tests
-"""
-
-
-import py
-
-from flask import current_app as app
-
-from MoinMoin._tests import wikiconfig
-
-try:
-    from MoinMoin.search.Xapian.tokenizer import WikiAnalyzer
-except ImportError:
-    py.test.skip('xapian is not installed')
-
-class TestWikiAnalyzer(object):
-
-    word = u'HelpOnMoinTesting'
-    words = {word.lower(): u'',
-             u'help': u'',
-             u'on': u'',
-             u'moin': u'',
-             u'testing': u''}
-
-    def setup_class(self):
-        self.analyzer = WikiAnalyzer(language=app.cfg.language_default)
-
-    def test_tokenize(self):
-        words = self.words
-        tokens = list(self.analyzer.tokenize(self.word))
-
-        assert len(tokens) == len(words)
-
-        for token, stemmed in tokens:
-            assert token in words
-            assert words[token] == stemmed
-
-
-class TestWikiAnalyzerStemmed(TestWikiAnalyzer):
-
-    word = u'HelpOnMoinTesting'
-    words = {word.lower(): u'helponmointest',
-             u'help': u'',
-             u'on': u'',
-             u'moin': u'',
-             u'testing': u'test'}
-
-    class Config(wikiconfig.Config):
-
-        xapian_stemming = True
-
-
-class TestWikiAnalyzerSeveralWords(TestWikiAnalyzer):
-
-    word = u'HelpOnMoinTesting OtherWikiWord'
-    words = {u'helponmointesting': u'',
-             u'help': u'',
-             u'on': u'',
-             u'moin': u'',
-             u'testing': u'',
-             u'otherwikiword': u'',
-             u'other': u'',
-             u'wiki': u'',
-             u'word': u''}
-
-
-class TestWikiAnalyzerStemmedSeveralWords(TestWikiAnalyzer):
-
-    word = u'HelpOnMoinTesting OtherWikiWord'
-    words = {u'helponmointesting': u'helponmointest',
-             u'help': u'',
-             u'on': u'',
-             u'moin': u'',
-             u'testing': u'test',
-             u'otherwikiword': u'',
-             u'other': u'',
-             u'wiki': u'',
-             u'word': u''}
-
-    class Config(wikiconfig.Config):
-
-        xapian_stemming = True
-
-
-class TestWikiAnalyzerStemmedHelpOnEditing(TestWikiAnalyzer):
-
-    word = u'HelpOnEditing'
-    words = {u'helponediting': u'helponedit',
-             u'help': u'',
-             u'on': u'',
-             u'editing': u'edit'}
-
-    class Config(wikiconfig.Config):
-
-        xapian_stemming = True
-
-
-class TestWikiAnalyzerStemmedCategoryHomepage(TestWikiAnalyzer):
-
-    word = u'CategoryHomepage'
-    words = {u'categoryhomepage': u'categoryhomepag',
-             u'category': u'categori',
-             u'homepage': u'homepag'}
-
-    class Config(wikiconfig.Config):
-
-        xapian_stemming = True
--- a/MoinMoin/search/builtin.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,390 +0,0 @@
-# Copyright: 2005 MoinMoin:FlorianFesti
-# Copyright: 2005 MoinMoin:NirSoffer
-# Copyright: 2005 MoinMoin:AlexanderSchremmer
-# Copyright: 2006-2009 MoinMoin:ThomasWaldmann
-# Copyright: 2006 MoinMoin:FranzPletz
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - search engine internals
-"""
-
-
-import sys, os, time, errno, codecs
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from flask import current_app as app
-
-from flask import flaskg
-
-from MoinMoin import wikiutil, config
-from MoinMoin.util import lock, filesys
-from MoinMoin.search.results import getSearchResults, Match, TextMatch, TitleMatch, getSearchResults
-
-##############################################################################
-# Search Engine Abstraction
-##############################################################################
-
-
-class IndexerQueue(object):
-    """
-    Represents a locked on-disk queue with jobs for the xapian indexer
-
-    Each job is a tuple like: (PAGENAME, ATTACHMENTNAME, REVNO)::
-
-        PAGENAME: page name (unicode)
-        ATTACHMENTNAME: attachment name (unicode) or None (for pages)
-        REVNO: revision number (int) - meaning "look at that revision",
-               or None - meaning "look at all revisions"
-    """
-
-    def __init__(self, request, xapian_dir, queuename, timeout=10.0):
-        """
-        :param request: request object
-        :param xapian_dir: the xapian main directory
-        :param queuename: name of the queue (used for caching key)
-        :param timeout: lock acquire timeout
-        """
-        self.request = request
-        self.xapian_dir = xapian_dir
-        self.queuename = queuename
-        self.timeout = timeout
-
-    def get_cache(self, locking):
-        return caching.CacheEntry(self.xapian_dir, self.queuename,
-                                  scope='dir', use_pickle=True, do_locking=locking)
-
-    def _queue(self, cache):
-        try:
-            queue = cache.content()
-        except caching.CacheError:
-            # likely nothing there yet
-            queue = []
-        return queue
-
-    def put(self, pagename, attachmentname=None, revno=None):
-        """ Put an entry into the queue (append at end)
-
-        :param pagename: page name [unicode]
-        :param attachmentname: attachment name [unicode]
-        :param revno: revision number (int) or None (all revs)
-        """
-        cache = self.get_cache(locking=False) # we lock manually
-        cache.lock('w', 60.0)
-        try:
-            queue = self._queue(cache)
-            entry = (pagename, attachmentname, revno)
-            queue.append(entry)
-            cache.update(queue)
-        finally:
-            cache.unlock()
-
-    def get(self):
-        """ Get (and remove) first entry from the queue
-
-        Raises IndexError if queue was empty when calling get().
-        """
-        cache = self.get_cache(locking=False) # we lock manually
-        cache.lock('w', 60.0)
-        try:
-            queue = self._queue(cache)
-            entry = queue.pop(0)
-            cache.update(queue)
-        finally:
-            cache.unlock()
-        return entry
-
-
-class BaseIndex(object):
-    """ Represents a search engine index """
-
-    def __init__(self, request):
-        """
-        :param request: current request
-        """
-        self.request = request
-        self.main_dir = self._main_dir()
-        if not os.path.exists(self.main_dir):
-            os.makedirs(self.main_dir)
-        self.update_queue = IndexerQueue(request, self.main_dir, 'indexer-queue')
-
-    def _main_dir(self):
-        raise NotImplemented('...')
-
-    def exists(self):
-        """ Check if index exists """
-        raise NotImplemented('...')
-
-    def mtime(self):
-        """ Modification time of the index """
-        raise NotImplemented('...')
-
-    def touch(self):
-        """ Touch the index """
-        raise NotImplemented('...')
-
-    def _search(self, query):
-        """ Actually perfom the search
-
-        :param query: the search query objects tree
-        """
-        raise NotImplemented('...')
-
-    def search(self, query, **kw):
-        """ Search for items in the index
-
-        :param query: the search query objects to pass to the index
-        """
-        return self._search(query, **kw)
-
-    def update_item(self, pagename, attachmentname=None, revno=None, now=True):
-        """ Update a single item (page or attachment) in the index
-
-        :param pagename: the name of the page to update
-        :param attachmentname: the name of the attachment to update
-        :param revno: a specific revision number (int) or None (all revs)
-        :param now: do all updates now (default: True)
-        """
-        self.update_queue.put(pagename, attachmentname, revno)
-        if now:
-            self.do_queued_updates()
-
-    def indexPages(self, files=None, mode='update', pages=None):
-        """ Index pages (and files, if given)
-
-        :param files: iterator or list of files to index additionally
-        :param mode: set the mode of indexing the pages, either 'update' or 'add'
-        :param pages: list of pages to index, if not given, all pages are indexed
-        """
-        start = time.time()
-        request = self._indexingRequest(self.request)
-        self._index_pages(request, files, mode, pages=pages)
-        logging.info("indexing completed successfully in %0.2f seconds." %
-                    (time.time() - start))
-
-    def _index_pages(self, request, files=None, mode='update', pages=None):
-        """ Index all pages (and all given files)
-
-        This should be called from indexPages only!
-
-        :param request: current request
-        :param files: iterator or list of files to index additionally
-        :param mode: set the mode of indexing the pages, either 'update' or 'add'
-        :param pages: list of pages to index, if not given, all pages are indexed
-
-        """
-        raise NotImplemented('...')
-
-    def do_queued_updates(self, amount=-1):
-        """ Perform updates in the queues
-
-        :param request: the current request
-        :keyword amount: how many updates to perform at once (default: -1 == all)
-        """
-        raise NotImplemented('...')
-
-    def optimize(self):
-        """ Optimize the index if possible """
-        raise NotImplemented('...')
-
-    def contentfilter(self, filename):
-        """ Get a filter for content of filename and return unicode content.
-
-        :param filename: name of the file
-        """
-        mt = wikiutil.MimeType(filename=filename)
-        return mt.mime_type(), u'not implemented' # XXX see moin 1.9 code about how it was done there
-
-    def _indexingRequest(self, request):
-        """ Return a new request that can be used for index building.
-
-        This request uses a security policy that lets the current user
-        read any page. Without this policy some pages will not render,
-        which will create broken pagelinks index.
-
-        :param request: current request
-        """
-        import copy
-        from MoinMoin.security import Permissions
-
-        class SecurityPolicy(Permissions):
-
-            def read(self, *args, **kw):
-                return True
-
-        r = copy.copy(request)
-        r.user.may = SecurityPolicy(r.user) # XXX
-        return r
-
-
-##############################################################################
-### Searching
-##############################################################################
-
-
-class BaseSearch(object):
-    """ A search run """
-
-    def __init__(self, request, query, sort='weight', mtime=None, historysearch=0):
-        """
-        :param request: current request
-        :param query: search query objects tree
-        :keyword sort: the sorting of the results (default: 'weight')
-        :keyword mtime: only show items newer than this timestamp (default: None)
-        :keyword historysearch: whether to show old revisions of a page (default: 0)
-        """
-        self.request = request
-        self.query = query
-        self.sort = sort
-        self.mtime = mtime
-        self.historysearch = historysearch
-        self.filtered = False
-        self.fs_rootpage = "FS" # XXX FS hardcoded
-
-    def run(self):
-        """ Perform search and return results object """
-
-        start = time.time()
-        hits, estimated_hits = self._search()
-
-        # important - filter pages the user may not read!
-        if not self.filtered:
-            hits = self._filter(hits)
-            logging.debug("after filtering: %d hits" % len(hits))
-
-        return self._get_search_results(hits, start, estimated_hits)
-
-    def _search(self):
-        """
-        Search pages.
-
-        Return list of tuples (wikiname, page object, attachment,
-        matches, revision) and estimated number of search results (if
-        there is no estimate, None should be returned).
-
-        The list may contain deleted pages or pages the user may not read.
-        """
-        raise NotImplementedError()
-
-    def _filter(self, hits):
-        """
-        Filter out deleted or acl protected pages
-
-        :param hits: list of hits
-        """
-        userMayRead = flaskg.user.may.read
-        fs_rootpage = self.fs_rootpage + "/"
-        thiswiki = (app.cfg.interwikiname, 'Self')
-        filtered = [(wikiname, page, attachment, match, rev)
-                for wikiname, page, attachment, match, rev in hits
-                    if (not wikiname in thiswiki or
-                       page.exists() and userMayRead(page.page_name) or
-                       page.page_name.startswith(fs_rootpage)) and
-                       (not self.mtime or self.mtime <= page.mtime_usecs()/1000000)]
-        return filtered
-
-    def _get_search_results(self, hits, start, estimated_hits):
-        return getSearchResults(self.request, self.query, hits, start, self.sort, estimated_hits)
-
-    def _get_match(self, page=None, uid=None):
-        """
-        Get all matches
-
-        :param page: the current page instance
-        """
-        if page:
-            return self.query.search(page)
-
-    def _getHits(self, pages):
-        """ Get the hit tuples in pages through _get_match """
-        logging.debug("_getHits searching in %d pages ..." % len(pages))
-        from MoinMoin.Page import Page
-        hits = []
-        revisionCache = {}
-        fs_rootpage = self.fs_rootpage
-        for hit in pages:
-
-            uid = hit.get('uid')
-            wikiname = hit['wikiname']
-            pagename = hit['pagename']
-            attachment = hit['attachment']
-            revision = int(hit.get('revision', 0))
-
-            logging.debug("_getHits processing %r %r %d %r" % (wikiname, pagename, revision, attachment))
-
-            if wikiname in (app.cfg.interwikiname, 'Self'): # THIS wiki
-                page = Page(self.request, pagename, rev=revision)
-
-                if not self.historysearch and revision:
-                    revlist = page.getRevList()
-                    # revlist can be empty if page was nuked/renamed since it was included in xapian index
-                    if not revlist or revlist[0] != revision:
-                        # nothing there at all or not the current revision
-                        logging.debug("no history search, skipping non-current revision...")
-                        continue
-
-                if attachment:
-                    # revision currently is 0 ever
-                    if pagename == fs_rootpage: # not really an attachment
-                        page = Page(self.request, "%s/%s" % (fs_rootpage, attachment))
-                        hits.append((wikiname, page, None, None, revision))
-                    else:
-                        matches = self._get_match(page=None, uid=uid)
-                        hits.append((wikiname, page, attachment, matches, revision))
-                else:
-                    matches = self._get_match(page=page, uid=uid)
-                    logging.debug("self._get_match %r" % matches)
-                    if matches:
-                        if not self.historysearch and pagename in revisionCache and revisionCache[pagename][0] < revision:
-                            hits.remove(revisionCache[pagename][1])
-                            del revisionCache[pagename]
-                        hits.append((wikiname, page, attachment, matches, revision))
-                        revisionCache[pagename] = (revision, hits[-1])
-
-            else: # other wiki
-                hits.append((wikiname, pagename, attachment, None, revision))
-        logging.debug("_getHits returning %r." % hits)
-        return hits
-
-
-class MoinSearch(BaseSearch):
-
-    def __init__(self, request, query, sort='weight', mtime=None, historysearch=0, pages=None):
-        super(MoinSearch, self).__init__(request, query, sort, mtime, historysearch)
-
-        self.pages = pages
-
-    def _search(self):
-        """
-        Search pages using moin's built-in full text search
-
-        The list may contain deleted pages or pages the user may not
-        read.
-
-        if self.pages is not None, searches in that pages.
-        """
-        # if self.pages is none, we make a full pagelist, but don't
-        # search attachments (thus attachment name = '')
-        pages = self.pages or [{'pagename': p, 'attachment': '', 'wikiname': 'Self', } for p in self._getPageList()]
-
-        hits = self._getHits(pages)
-        return hits, None
-
-    def _getPageList(self):
-        """ Get list of pages to search in
-
-        If the query has a page filter, use it to filter pages before
-        searching. If not, get a unfiltered page list. The filtering
-        will happen later on the hits, which is faster with current
-        slow storage.
-        """
-        filter_ = self.query.pageFilter()
-        if filter_:
-            # There is no need to filter the results again.
-            self.filtered = True
-            return self.request.rootpage.getPageList(filter=filter_)
-        else:
-            return self.request.rootpage.getPageList(user='')
-
--- a/MoinMoin/search/queryparser/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-# Copyright: 2005 MoinMoin:FlorianFesti
-# Copyright: 2005 MoinMoin:NirSoffer
-# Copyright: 2005 MoinMoin:AlexanderSchremmer
-# Copyright: 2006-2008 MoinMoin:ThomasWaldmann
-# Copyright: 2006 MoinMoin:FranzPletz
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - search query parser
-"""
-
-
-import re
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin import config
-from MoinMoin.util.paramparser import parse_quoted_separated_ext, ParserPrefix, BracketError
-from MoinMoin.search.queryparser.expressions import AndExpression, OrExpression, TextSearch, TitleSearch, \
-    LinkSearch, DomainSearch, MimetypeSearch, LanguageSearch
-
-class QueryError(ValueError):
-    """ error raised for problems when parsing the query """
-
-
-class QueryParser(object):
-    """
-    Converts a String into a tree of Query objects.
-    """
-
-    def __init__(self, **kw):
-        """
-        :keyword titlesearch: treat all terms as title searches
-        :keyword case: do case sensitive search
-        :keyword regex: treat all terms as regular expressions
-        """
-        self.titlesearch = kw.get('titlesearch', 0)
-        self.case = kw.get('case', 0)
-        self.regex = kw.get('regex', 0)
-        self._M = ParserPrefix('-')
-
-    def _analyse_items(self, items):
-        terms = AndExpression()
-        M = self._M
-        while items:
-            item = items[0]
-            items = items[1:]
-
-            if isinstance(item, unicode):
-                if item.lower() == 'or':
-                    sub = terms.subterms()
-                    if len(sub) >= 1:
-                        last = sub[-1]
-                        if last.__class__ == OrExpression:
-                            orexpr = last
-                        else:
-                            # Note: do NOT reduce "terms" when it has a single subterm only!
-                            # Doing that would break "-someterm" searches as we rely on AndExpression
-                            # doing a "MatchAll AND_NOT someterm" for that case!
-                            orexpr = OrExpression(terms)
-                        terms = AndExpression(orexpr)
-                    else:
-                        raise QueryError('Nothing to OR')
-                    remaining = self._analyse_items(items)
-                    if remaining.__class__ == OrExpression:
-                        for sub in remaining.subterms():
-                            orexpr.append(sub)
-                    else:
-                        orexpr.append(remaining)
-                    break
-                elif item.lower() == 'and':
-                    pass
-                else:
-                    # odd workaround; we should instead ignore this term
-                    # and reject expressions that contain nothing after
-                    # being parsed rather than rejecting an empty string
-                    # before parsing...
-                    if not item:
-                        raise QueryError("Term too short")
-                    regex = self.regex
-                    case = self.case
-                    if self.titlesearch:
-                        terms.append(TitleSearch(item, use_re=regex, case=case))
-                    else:
-                        terms.append(TextSearch(item, use_re=regex, case=case))
-            elif isinstance(item, tuple):
-                negate = item[0] == M
-                title_search = self.titlesearch
-                regex = self.regex
-                case = self.case
-                linkto = False
-                lang = False
-                mimetype = False
-                domain = False
-                while len(item) > 1:
-                    m = item[0]
-                    if m is None:
-                        raise QueryError("Invalid search prefix")
-                    elif m == M:
-                        negate = True
-                    elif "title".startswith(m):
-                        title_search = True
-                    elif "regex".startswith(m):
-                        regex = True
-                    elif "case".startswith(m):
-                        case = True
-                    elif "linkto".startswith(m):
-                        linkto = True
-                    elif "language".startswith(m):
-                        lang = True
-                    elif "mimetype".startswith(m):
-                        mimetype = True
-                    elif "domain".startswith(m):
-                        domain = True
-                    else:
-                        raise QueryError("Invalid search prefix")
-                    item = item[1:]
-
-                text = item[0]
-                if mimetype:
-                    obj = MimetypeSearch(text, use_re=regex, case=False)
-                elif lang:
-                    obj = LanguageSearch(text, use_re=regex, case=False)
-                elif linkto:
-                    obj = LinkSearch(text, use_re=regex, case=case)
-                elif domain:
-                    obj = DomainSearch(text, use_re=regex, case=False)
-                elif title_search:
-                    obj = TitleSearch(text, use_re=regex, case=case)
-                else:
-                    obj = TextSearch(text, use_re=regex, case=case)
-                obj.negated = negate
-                terms.append(obj)
-            elif isinstance(item, list):
-                # strip off the opening parenthesis
-                terms.append(self._analyse_items(item[1:]))
-
-        # Note: do NOT reduce "terms" when it has a single subterm only!
-        # Doing that would break "-someterm" searches as we rely on AndExpression
-        # doing a "MatchAll AND_NOT someterm" for that case!
-        return terms
-
-    def parse_query(self, query):
-        """ transform an string into a tree of Query objects """
-        if isinstance(query, str):
-            query = query.decode(config.charset)
-        try:
-            items = parse_quoted_separated_ext(query,
-                                               name_value_separator=':',
-                                               prefixes='-',
-                                               multikey=True,
-                                               brackets=('()', ),
-                                               quotes='\'"')
-        except BracketError, err:
-            raise QueryError(str(err))
-        logging.debug("parse_quoted_separated items: %r" % items)
-        query = self._analyse_items(items)
-        logging.debug("analyse_items query: %r" % query)
-        return query
--- a/MoinMoin/search/queryparser/expressions.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,563 +0,0 @@
-# Copyright: 2005 MoinMoin:FlorianFesti
-# Copyright: 2005 MoinMoin:NirSoffer
-# Copyright: 2005 MoinMoin:AlexanderSchremmer
-# Copyright: 2006-2008 MoinMoin:ThomasWaldmann
-# Copyright: 2006 MoinMoin:FranzPletz
-# Copyright: 2009 MoinMoin:DmitrijsMilajevs
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - search query expressions
-"""
-
-
-import re
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from flask import current_app as app
-
-from MoinMoin import config, wikiutil
-from MoinMoin.search.results import Match, TitleMatch, TextMatch
-
-
-class BaseExpression(object):
-    """ Base class for all search terms """
-
-    # costs is estimated time to calculate this term.
-    # Number is relative to other terms and has no real unit.
-    # It allows to do the fast searches first.
-    costs = 0
-    _tag = ""
-
-    def __init__(self, pattern, use_re=False, case=False):
-        """ Init a text search
-
-        :param pattern: pattern to search for, ascii string or unicode
-        :param use_re: treat pattern as re of plain text, bool
-        :param case: do case sensitive search, bool
-        """
-        self._pattern = unicode(pattern)
-        self.negated = 0
-        self.use_re = use_re
-        self.case = case
-
-        if use_re:
-            self._tag += 're:'
-        if case:
-            self._tag += 'case:'
-
-        self.pattern, self.search_re = self._build_re(self._pattern, use_re=use_re, case=case)
-
-    def __str__(self):
-        return unicode(self).encode(config.charset, 'replace')
-
-    def negate(self):
-        """ Negate the result of this term """
-        self.negated = 1
-
-    def pageFilter(self):
-        """ Return a page filtering function
-
-        This function is used to filter page list before we search
-        it. Return a function that get a page name, and return bool.
-
-        The default expression does not have any filter function and
-        return None. Sub class may define custom filter functions.
-        """
-        return None
-
-    def _get_matches(self, page):
-        raise NotImplementedError
-
-    def search(self, page):
-        """ Search a page
-
-        Returns a list of Match objects or None if term didn't find
-        anything (vice versa if negate() was called).  Terms containing
-        other terms must call this method to aggregate the results.
-        This Base class returns True (Match()) if not negated.
-        """
-        logging.debug("%s searching page %r for (negated = %r) %r" % (self.__class__, page.page_name, self.negated, self._pattern))
-
-        matches = self._get_matches(page)
-
-        # Decide what to do with the results.
-        if self.negated:
-            if matches:
-                result = None
-            else:
-                result = [Match()] # represents "matched" (but as it was a negative match, we have nothing to show)
-        else: # not negated
-            if matches:
-                result = matches
-            else:
-                result = None
-        logging.debug("%s returning %r" % (self.__class__, result))
-        return result
-
-    def highlight_re(self):
-        """ Return a regular expression of what the term searches for
-
-        Used to display the needle in the page.
-        """
-        return u''
-
-    def _build_re(self, pattern, use_re=False, case=False, stemmed=False):
-        """ Make a regular expression out of a text pattern """
-        flags = case and re.U or (re.I | re.U)
-
-        try:
-            search_re = re.compile(pattern, flags)
-        except re.error:
-            pattern = re.escape(pattern)
-            search_re = re.compile(pattern, flags)
-
-        return pattern, search_re
-
-    def _get_query_for_search_re(self, connection, field_to_check=None):
-        """
-        Return a query which satisfy self.search_re for field values.
-        If field_to_check is given check values only for that field.
-        """
-        from MoinMoin.search.Xapian import Query
-
-        queries = []
-
-        documents = connection.get_all_documents()
-        for document in documents:
-            data = document.data
-            if field_to_check:
-                # Check only field with given name
-                if field_to_check in data:
-                    for term in data[field_to_check]:
-                        if self.search_re.match(term):
-                            queries.append(connection.query_field(field_to_check, term))
-            else:
-                # Check all fields
-                for field, terms in data.iteritems():
-                    for term in terms:
-                        if self.search_re.match(term):
-                            queries.append(connection.query_field(field_to_check, term))
-
-        return Query(Query.OP_OR, queries)
-
-    def xapian_need_postproc(self):
-        return self.case
-
-    def __unicode__(self):
-        neg = self.negated and '-' or ''
-        return u'%s%s"%s"' % (neg, self._tag, unicode(self._pattern))
-
-
-class AndExpression(BaseExpression):
-    """ A term connecting several sub terms with a logical AND """
-
-    operator = ' '
-
-    def __init__(self, *terms):
-        self._subterms = list(terms)
-        self.negated = 0
-
-    def append(self, expression):
-        """ Append another term """
-        self._subterms.append(expression)
-
-    def subterms(self):
-        return self._subterms
-
-    @property
-    def costs(self):
-        return sum([t.costs for t in self._subterms])
-
-    def __unicode__(self):
-        result = ''
-        for t in self._subterms:
-            result += self.operator + unicode(t)
-        return u'[' + result[len(self.operator):] + u']'
-
-    def _filter(self, terms, name):
-        """ A function that returns True if all terms filter name """
-        result = None
-        for term in terms:
-            _filter = term.pageFilter()
-            t = _filter(name)
-            if t is True:
-                result = True
-            elif t is False:
-                result = False
-                break
-        logging.debug("pageFilter AND returns %r" % result)
-        return result
-
-    def pageFilter(self):
-        """ Return a page filtering function
-
-        This function is used to filter page list before we search it.
-
-        Return a function that gets a page name, and return bool, or None.
-        """
-        # Sort terms by cost, then get all title searches
-        self.sortByCost()
-        terms = [term for term in self._subterms if isinstance(term, TitleSearch)]
-        if terms:
-            return lambda name: self._filter(terms, name)
-
-    def sortByCost(self):
-        self._subterms.sort(key=lambda t: t.costs)
-
-    def search(self, page):
-        """ Search for each term, cheap searches first """
-        self.sortByCost()
-        matches = []
-        for term in self._subterms:
-            result = term.search(page)
-            if not result:
-                return None
-            matches.extend(result)
-        return matches
-
-    def highlight_re(self):
-        result = []
-        for s in self._subterms:
-            highlight_re = s.highlight_re()
-            if highlight_re:
-                result.append(highlight_re)
-
-        return u'|'.join(result)
-
-    def xapian_need_postproc(self):
-        for term in self._subterms:
-            if term.xapian_need_postproc():
-                return True
-        return False
-
-    def xapian_term(self, request, connection):
-        from MoinMoin.search.Xapian import Query
-
-        # sort negated terms
-        terms = []
-        not_terms = []
-
-        for term in self._subterms:
-            if not term.negated:
-                terms.append(term.xapian_term(request, connection))
-            else:
-                not_terms.append(term.xapian_term(request, connection))
-
-        # prepare query for not negated terms
-        if terms:
-            query = Query(Query.OP_AND, terms)
-        else:
-            query = Query('') # MatchAll
-
-        # prepare query for negated terms
-        if not_terms:
-            query_negated = Query(Query.OP_OR, not_terms)
-        else:
-            query_negated = Query()
-
-        return Query(Query.OP_AND_NOT, query, query_negated)
-
-
-class OrExpression(AndExpression):
-    """ A term connecting several sub terms with a logical OR """
-
-    operator = ' or '
-
-    def _filter(self, terms, name):
-        """ A function that returns True if any term filters name """
-        result = None
-        for term in terms:
-            _filter = term.pageFilter()
-            t = _filter(name)
-            if t is True:
-                result = True
-                break
-            elif t is False:
-                result = False
-        logging.debug("pageFilter OR returns %r" % result)
-        return result
-
-    def search(self, page):
-        """ Search page with terms
-
-        :param page: the page instance
-        """
-        # XXX Do we have any reason to sort here? we are not breaking out
-        # of the search in any case.
-        #self.sortByCost()
-        matches = []
-        for term in self._subterms:
-            result = term.search(page)
-            if result:
-                matches.extend(result)
-        return matches
-
-    def xapian_term(self, request, connection):
-        from MoinMoin.search.Xapian import Query
-        # XXX: negated terms managed by _moinSearch?
-        return Query(Query.OP_OR, [term.xapian_term(request, connection) for term in self._subterms])
-
-
-class BaseTextFieldSearch(BaseExpression):
-
-    _field_to_search = None
-
-    def xapian_term(self, request, connection):
-        from MoinMoin.search.Xapian import Query, WikiAnalyzer
-
-        if self.use_re:
-            queries = [self._get_query_for_search_re(connection, self._field_to_search)]
-        else:
-            queries = []
-            stemmed = []
-            analyzer = WikiAnalyzer(language=app.cfg.language_default)
-
-            for term in self._pattern.split():
-                query_term = connection.query_field(self._field_to_search, term)
-                tokens = analyzer.tokenize(term)
-
-                if app.cfg.xapian_stemming:
-                    query_token = []
-                    for token, stemmed_ in tokens:
-                        if token != term.lower():
-                            if stemmed_:
-                                query_token.append(Query(Query.OP_OR,
-                                                         [connection.query_field(self._field_to_search, token),
-                                                          connection.query_field(self._field_to_search, stemmed_)]))
-#                                 stemmed.append('(%s|%s)' % (token, stemmed_))
-                            else:
-                                query_token.append(connection.query_field(self._field_to_search, token))
-#                                 stemmed.append(token)
-                    query_tokens = Query(Query.OP_AND, query_token)
-                else:
-                    query_tokens = Query(Query.OP_AND, [connection.query_field(self._field_to_search, token) for token, stemmed_ in tokens if token != term.lower()])
-
-                queries.append(Query(Query.OP_OR, [query_term, query_tokens]))
-
-            # XXX broken wrong regexp is built!
-            if not self.case and stemmed:
-                new_pat = ' '.join(stemmed)
-                self._pattern = new_pat
-                self.pattern, self.search_re = self._build_re(new_pat, use_re=False, case=self.case, stemmed=True)
-
-        return Query(Query.OP_AND, queries)
-
-
-class TextSearch(BaseTextFieldSearch):
-    """ A term that does a normal text search
-
-    Both page content and the page title are searched, using an
-    additional TitleSearch term.
-    """
-
-    costs = 10000
-    _field_to_search = 'content'
-
-    def highlight_re(self):
-        return u"(%s)" % self.pattern
-
-    def _get_matches(self, page):
-        matches = []
-
-        # Search in page name
-        results = TitleSearch(self._pattern, use_re=self.use_re, case=self.case)._get_matches(page)
-        if results:
-            matches.extend(results)
-
-        # Search in page body
-        body = page.get_raw_body()
-        for match in self.search_re.finditer(body):
-            matches.append(TextMatch(re_match=match))
-
-        return matches
-
-    def xapian_term(self, request, connection):
-        from MoinMoin.search.Xapian import Query
-        if self.use_re:
-            # if regex search is wanted, we need to match all documents, because
-            # we do not have full content stored and need post processing to do
-            # the regex searching.
-            return Query('') # MatchAll
-        else:
-            content_query = super(TextSearch, self).xapian_term(request, connection)
-            title_query = TitleSearch(self._pattern, use_re=self.use_re, case=self.case).xapian_term(request, connection)
-            return Query(OP_OR, [title_query, content_query])
-
-    def xapian_need_postproc(self):
-        # case-sensitive: xapian is case-insensitive, therefore we need postproc
-        # regex: xapian can't do regex search. also we don't have full content
-        #        stored (and we don't want to do that anyway), so regex search
-        #        needs postproc also.
-        return self.case or self.use_re
-
-
-class TitleSearch(BaseTextFieldSearch):
-    """ Term searches in pattern in page title only """
-
-    _tag = 'title:'
-    costs = 100
-    _field_to_search = 'title'
-
-    def pageFilter(self):
-        """ Page filter function for single title search """
-
-        def filter(name):
-            match = self.search_re.search(name)
-            result = bool(self.negated) ^ bool(match)
-            logging.debug("pageFilter title returns %r (%r)" % (result, self.pattern))
-            return result
-        return filter
-
-    def _get_matches(self, page):
-        """ Get matches in page name """
-        matches = []
-
-        for match in self.search_re.finditer(page.page_name):
-            matches.append(TitleMatch(re_match=match))
-
-        return matches
-
-
-class BaseFieldSearch(BaseExpression):
-
-    _field_to_search = None
-
-    def xapian_term(self, request, connection):
-        if self.use_re:
-            return self._get_query_for_search_re(connection, self._field_to_search)
-        else:
-            return connection.query_field(self._field_to_search, self._pattern)
-
-
-class LinkSearch(BaseFieldSearch):
-    """ Search the term in the pagelinks """
-
-    _tag = 'linkto:'
-    _field_to_search = 'linkto'
-    costs = 5000 # cheaper than a TextSearch
-
-    def __init__(self, pattern, use_re=False, case=True):
-        """ Init a link search
-
-        :param pattern: pattern to search for, ascii string or unicode
-        :param use_re: treat pattern as re of plain text, bool
-        :param case: do case sensitive search, bool
-        """
-
-        super(LinkSearch, self).__init__(pattern, use_re, case)
-
-        self._textpattern = '(' + pattern.replace('/', '|') + ')' # used for search in text
-        self.textsearch = TextSearch(self._textpattern, use_re=True, case=case)
-
-    def highlight_re(self):
-        return u"(%s)" % self._textpattern
-
-    def _get_matches(self, page):
-        # Get matches in page links
-        matches = []
-
-        # XXX in python 2.5 any() may be used.
-        found = False
-        for link in page.getPageLinks(page.request):
-            if self.search_re.match(link):
-                found = True
-                break
-
-        if found:
-            # Search in page text
-            results = self.textsearch.search(page)
-            if results:
-                matches.extend(results)
-            else: # This happens e.g. for pages that use navigation macros
-                matches.append(TextMatch(0, 0))
-
-        return matches
-
-
-class LanguageSearch(BaseFieldSearch):
-    """ Search the pages written in a language """
-
-    _tag = 'language:'
-    _field_to_search = 'lang'
-    costs = 5000 # cheaper than a TextSearch
-
-    def __init__(self, pattern, use_re=False, case=False):
-        """ Init a language search
-
-        :param pattern: pattern to search for, ascii string or unicode
-        :param use_re: treat pattern as re of plain text, bool
-        :param case: do case sensitive search, bool
-        """
-        # iso language code, always lowercase and not case-sensitive
-        super(LanguageSearch, self).__init__(pattern.lower(), use_re, case=False)
-
-    def _get_matches(self, page):
-
-        if self.pattern == page.pi['language']:
-            return [Match()]
-        else:
-            return []
-
-
-class MimetypeSearch(BaseFieldSearch):
-    """ Search for files belonging to a specific mimetype """
-
-    _tag = 'mimetype:'
-    _field_to_search = 'mimetype'
-    costs = 5000 # cheaper than a TextSearch
-
-    def __init__(self, pattern, use_re=False, case=False):
-        """ Init a mimetype search
-
-        :param pattern: pattern to search for, ascii string or unicode
-        :param use_re: treat pattern as re of plain text, bool
-        :param case: do case sensitive search, bool
-        """
-        # always lowercase and not case-sensitive
-        super(MimetypeSearch, self).__init__(pattern.lower(), use_re, case=False)
-
-    def _get_matches(self, page):
-
-        page_mimetype = u'text/%s' % page.pi['format']
-
-        if self.search_re.search(page_mimetype):
-            return [Match()]
-        else:
-            return []
-
-
-class DomainSearch(BaseFieldSearch):
-    """ Search for pages belonging to a specific domain """
-
-    _tag = 'domain:'
-    _field_to_search = 'domain'
-    costs = 5000 # cheaper than a TextSearch
-
-    def __init__(self, pattern, use_re=False, case=False):
-        """ Init a domain search
-
-        :param pattern: pattern to search for, ascii string or unicode
-        :param use_re: treat pattern as re of plain text, bool
-        :param case: do case sensitive search, bool
-        """
-        # always lowercase and not case-sensitive
-        super(DomainSearch, self).__init__(pattern.lower(), use_re, case=False)
-
-    def _get_matches(self, page):
-        checks = {'standard': page.isStandardPage,
-                  'system': lambda page=page: wikiutil.isSystemItem(page.page_name),
-                 }
-
-        try:
-            match = checks[self.pattern]()
-        except KeyError:
-            match = False
-
-        if match:
-            return [Match()]
-        else:
-            return []
-
--- a/MoinMoin/search/results.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,836 +0,0 @@
-# Copyright: 2005 MoinMoin:FlorianFesti
-# Copyright: 2005 MoinMoin:NirSoffer
-# Copyright: 2005 MoinMoin:AlexanderSchremmer
-# Copyright: 2006 MoinMoin:ThomasWaldmann
-# Copyright: 2006 MoinMoin:FranzPletz
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - search results processing
-"""
-
-
-import StringIO, time
-
-from flask import current_app as app
-
-from MoinMoin import wikiutil
-from MoinMoin.i18n import _, L_, N_
-
-############################################################################
-### Results
-############################################################################
-
-
-class Match(object):
-    """ Base class for all Matches (found pieces of pages).
-
-    This class represents a empty True value as returned from negated searches.
-    """
-    # Default match weight
-    _weight = 1.0
-
-    def __init__(self, start=0, end=0, re_match=None):
-        self.re_match = re_match
-        if not re_match:
-            self._start = start
-            self._end = end
-        else:
-            self._start = self._end = 0
-
-    def __len__(self):
-        return self.end - self.start
-
-    def __eq__(self, other):
-        equal = (self.__class__ == other.__class__ and
-                 self.start == other.start and
-                 self.end == other.end)
-        return equal
-
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-    def view(self):
-        return ''
-
-    def weight(self):
-        return self._weight
-
-    def _get_start(self):
-        if self.re_match:
-            return self.re_match.start()
-        return self._start
-
-    def _get_end(self):
-        if self.re_match:
-            return self.re_match.end()
-        return self._end
-
-    # object properties
-    start = property(_get_start)
-    end = property(_get_end)
-
-
-class TextMatch(Match):
-    """ Represents a match in the page content """
-    pass
-
-
-class TitleMatch(Match):
-    """ Represents a match in the page title
-
-    Has more weight than a match in the page content.
-    """
-    # Matches in titles are much more important in wikis. This setting
-    # seems to make all pages that have matches in the title to appear
-    # before pages that their title does not match.
-    _weight = 100.0
-
-
-class AttachmentMatch(Match):
-    """ Represents a match in a attachment content
-
-    Not used yet.
-    """
-    pass
-
-
-class FoundPage(object):
-    """ Represents a page in a search result """
-
-    def __init__(self, page_name, matches=None, page=None, rev=0):
-        self.page_name = page_name
-        self.attachment = '' # this is not an attachment
-        self.page = page
-        self.rev = rev
-        if matches is None:
-            matches = []
-        self._matches = matches
-
-    def weight(self, unique=1):
-        """ returns how important this page is for the terms searched for
-
-        Summarize the weight of all page matches
-
-        :param unique: ignore identical matches
-        :rtype: int
-        :returns: page weight
-        """
-        weight = 0
-        for match in self.get_matches(unique=unique):
-            weight += match.weight()
-            # More sophisticated things to be added, like increase
-            # weight of near matches.
-        if self.page.parse_processing_instructions().get('deprecated', False):
-            weight = int(weight / 4) # rank it down
-        return weight
-
-    def add_matches(self, matches):
-        """ Add found matches """
-        self._matches.extend(matches)
-
-    def get_matches(self, unique=1, sort='start', type=Match):
-        """ Return all matches of type sorted by sort
-
-        :param unique: return only unique matches (bool)
-        :param sort: match attribute to sort by (string)
-        :param type: type of match to return (Match or sub class)
-        :rtype: list
-        :returns: list of matches
-        """
-        if unique:
-            matches = self._unique_matches(type=type)
-            if sort == 'start':
-                # matches already sorted by match.start, finished.
-                return matches
-        else:
-            matches = self._matches
-
-        # Filter by type and sort by sort using fast schwartzian transform.
-        if sort == 'start':
-            tmp = [(match.start, match) for match in matches if isinstance(match, type)]
-        else:
-            tmp = [(match.weight(), match) for match in matches if isinstance(match, type)]
-        tmp.sort()
-        if sort == 'weight':
-            tmp.reverse()
-        matches = [item[1] for item in tmp]
-
-        return matches
-
-    def _unique_matches(self, type=Match):
-        """ Get a list of unique matches of type
-
-        The result is sorted by match.start, because its easy to remove
-        duplicates like this.
-
-        :param type: type of match to return
-        :rtype: list
-        :returns: list of matches of type, sorted by match.start
-        """
-        # Filter by type and sort by match.start using fast schwartzian transform.
-        tmp = [(match.start, match) for match in self._matches if isinstance(match, type)]
-        tmp.sort()
-
-        if not len(tmp):
-            return []
-
-        # Get first match into matches list
-        matches = [tmp[0][1]]
-
-        # Add the remaining ones of matches ignoring identical matches
-        for item in tmp[1:]:
-            if item[1] == matches[-1]:
-                continue
-            matches.append(item[1])
-
-        return matches
-
-
-class FoundAttachment(FoundPage):
-    """ Represents an attachment in search results """
-
-    def __init__(self, page_name, attachment, matches=None, page=None, rev=0):
-        self.page_name = page_name
-        self.attachment = attachment
-        self.rev = rev
-        self.page = page
-        if matches is None:
-            matches = []
-        self._matches = matches
-
-    def weight(self, unique=1):
-        return 1
-
-
-class FoundRemote(FoundPage):
-    """ Represents a remote search result """
-
-    def __init__(self, wikiname, page_name, attachment, matches=None, page=None, rev=0):
-        self.wikiname = wikiname
-        self.page_name = page_name
-        self.rev = rev
-        self.attachment = attachment
-        self.page = page
-        if matches is None:
-            matches = []
-        self._matches = matches
-
-    def weight(self, unique=1):
-        return 1
-
-    def get_matches(self, unique=1, sort='start', type=Match):
-        return []
-
-    def _unique_matches(self, type=Match):
-        return []
-
-
-############################################################################
-### Search results formatting
-############################################################################
-
-
-class SearchResults(object):
-    """ Manage search results, supply different views
-
-    Search results can hold valid search results and format them for
-    many requests, until the wiki content changes.
-
-    For example, one might ask for full page list sorted from A to Z,
-    and then ask for the same list sorted from Z to A. Or sort results
-    by name and then by rank.
-    """
-    # Public functions --------------------------------------------------
-
-    def __init__(self, query, hits, pages, elapsed, sort, estimated_hits):
-        self.query = query # the query
-        self.hits = hits # hits list
-        self.pages = pages # number of pages in the wiki
-        self.elapsed = elapsed # search time
-        self.estimated_hits = estimated_hits # about how much hits?
-
-        if sort == 'weight':
-            self._sortByWeight()
-        elif sort == 'page_name':
-            self._sortByPagename()
-        self.sort = sort
-
-    def _sortByWeight(self):
-        """ Sorts found pages by the weight of the matches """
-        tmp = [(hit.weight(), hit.page_name, hit.attachment, hit) for hit in self.hits]
-        tmp.sort()
-        tmp.reverse()
-        self.hits = [item[3] for item in tmp]
-
-    def _sortByPagename(self):
-        """ Sorts a list of found pages alphabetical by page/attachment name """
-        tmp = [(hit.page_name, hit.attachment, hit) for hit in self.hits]
-        tmp.sort()
-        self.hits = [item[2] for item in tmp]
-
-    def stats(self, request, formatter, hitsFrom):
-        """ Return search statistics, formatted with formatter
-
-        :param request: current request
-        :param formatter: formatter to use
-        :param hitsFrom: current position in the hits
-        :rtype: unicode
-        :returns: formatted statistics
-        """
-        if not self.estimated_hits:
-            self.estimated_hits = ('', len(self.hits))
-
-        output = [
-            formatter.paragraph(1, attr={'class': 'searchstats'}),
-            _("Results %(bs)s%(hitsFrom)d - %(hitsTo)d%(be)s "
-                    "of %(aboutHits)s %(bs)s%(hits)d%(be)s results out of "
-                    "about %(items)d items.") %
-                {'aboutHits': self.estimated_hits[0],
-                    'hits': self.estimated_hits[1], 'items': self.pages,
-                    'hitsFrom': hitsFrom + 1,
-                    'hitsTo': hitsFrom +
-                            min(self.estimated_hits[1] - hitsFrom,
-                                app.cfg.search_results_per_page),
-                    'bs': formatter.strong(1), 'be': formatter.strong(0)},
-            u' (%s %s)' % (''.join([formatter.strong(1),
-                formatter.text("%.2f" % self.elapsed),
-                formatter.strong(0)]),
-                formatter.text(_("seconds"))),
-            formatter.paragraph(0),
-            ]
-        return ''.join(output)
-
-    def pageList(self, request, formatter, info=0, numbered=1,
-            paging=True, hitsFrom=0, hitsInfo=0):
-        """ Format a list of found pages
-
-        :param request: current request
-        :param formatter: formatter to use
-        :param info: show match info in title
-        :param numbered: use numbered list for display
-        :param paging: toggle paging
-        :param hitsFrom: current position in the hits
-        :param hitsInfo: toggle hits info line
-        :rtype: unicode
-        :returns: formatted page list
-        """
-        self._reset(request, formatter)
-        f = formatter
-        write = self.buffer.write
-        if numbered:
-            lst = lambda on: f.number_list(on, start=hitsFrom+1)
-        else:
-            lst = f.bullet_list
-
-        if paging and len(self.hits) <= app.cfg.search_results_per_page:
-            paging = False
-
-        # Add pages formatted as list
-        if self.hits:
-            write(lst(1))
-
-            if paging:
-                hitsTo = hitsFrom + app.cfg.search_results_per_page
-                displayHits = self.hits[hitsFrom:hitsTo]
-            else:
-                displayHits = self.hits
-
-            for page in displayHits:
-                if isinstance(page, FoundRemote):
-                    # TODO handle FoundRemote (interwiki) search hits
-                    continue
-                elif isinstance(page, FoundAttachment):
-                    querydict = {
-                        'action': 'AttachFile',
-                        'do': 'view',
-                        'target': page.attachment,
-                    }
-                elif isinstance(page, FoundPage):
-                    if page.rev and page.rev != page.page.getRevList()[0]:
-                        querydict = {
-                            'rev': page.rev,
-                        }
-                    else:
-                        querydict = None
-                querystr = self.querystring(querydict)
-
-                matchInfo = ''
-                if info:
-                    matchInfo = self.formatInfo(f, page)
-
-                info_for_hits = u''
-                if hitsInfo:
-                    info_for_hits = self.formatHitInfoBar(page)
-
-                item = [
-                    f.listitem(1),
-                    f.pagelink(1, page.page_name, querystr=querystr),
-                    self.formatTitle(page),
-                    f.pagelink(0, page.page_name),
-                    matchInfo,
-                    info_for_hits,
-                    f.listitem(0),
-                    ]
-                write(''.join(item))
-            write(lst(0))
-            if paging:
-                write(self.formatPageLinks(hitsFrom=hitsFrom,
-                    hitsPerPage=app.cfg.search_results_per_page,
-                    hitsNum=len(self.hits)))
-
-        return self.getvalue()
-
-    def pageListWithContext(self, request, formatter, info=1, context=180,
-                            maxlines=1, paging=True, hitsFrom=0, hitsInfo=0):
-        """ Format a list of found pages with context
-
-        :param request: current request
-        :param formatter: formatter to use
-        :param info: show match info near the page link
-        :param context: how many characters to show around each match.
-        :param maxlines: how many contexts lines to show.
-        :param paging: toggle paging
-        :param hitsFrom: current position in the hits
-        :param hitsInfo: toggle hits info line
-        :rtype: unicode
-        :returns: formatted page list with context
-        """
-        self._reset(request, formatter)
-        f = formatter
-        write = self.buffer.write
-
-        if paging and len(self.hits) <= app.cfg.search_results_per_page:
-            paging = False
-
-        # Add pages formatted as definition list
-        if self.hits:
-            write(f.definition_list(1))
-
-            if paging:
-                hitsTo = hitsFrom + app.cfg.search_results_per_page
-                displayHits = self.hits[hitsFrom:hitsTo]
-            else:
-                displayHits = self.hits
-
-            for page in displayHits:
-                # TODO handle interwiki search hits
-                matchInfo = ''
-                if info:
-                    matchInfo = self.formatInfo(f, page)
-                if page.attachment:
-                    fmt_context = ""
-                    querydict = {
-                        'action': 'AttachFile',
-                        'do': 'view',
-                        'target': page.attachment,
-                    }
-                elif page.page_name.startswith('FS/'): # XXX FS hardcoded
-                    fmt_context = ""
-                    querydict = None
-                else:
-                    fmt_context = self.formatContext(page, context, maxlines)
-                    if page.rev and page.rev != page.page.getRevList()[0]:
-                        querydict = {
-                            'rev': page.rev,
-                        }
-                    else:
-                        querydict = None
-                querystr = self.querystring(querydict)
-                item = [
-                    f.definition_term(1),
-                    f.pagelink(1, page.page_name, querystr=querystr),
-                    self.formatTitle(page),
-                    f.pagelink(0, page.page_name),
-                    matchInfo,
-                    f.definition_term(0),
-                    f.definition_desc(1),
-                    fmt_context,
-                    f.definition_desc(0),
-                    self.formatHitInfoBar(page),
-                    ]
-                write(''.join(item))
-            write(f.definition_list(0))
-            if paging:
-                write(self.formatPageLinks(hitsFrom=hitsFrom,
-                    hitsPerPage=app.cfg.search_results_per_page,
-                    hitsNum=len(self.hits)))
-
-        return self.getvalue()
-
-    # Private -----------------------------------------------------------
-
-    # This methods are not meant to be used by clients and may change
-    # without notice.
-
-    def formatContext(self, page, context, maxlines):
-        """ Format search context for each matched page
-
-        Try to show first maxlines interesting matches context.
-        """
-        f = self.formatter
-        if not page.page:
-            from MoinMoin.Page import Page
-            page.page = Page(self.request, page.page_name)
-        body = page.page.get_raw_body()
-        last = len(body) - 1
-        lineCount = 0
-        output = []
-
-        # Get unique text matches sorted by match.start, try to ignore
-        # matches in page header, and show the first maxlines matches.
-        # TODO: when we implement weight algorithm for text matches, we
-        # should get the list of text matches sorted by weight and show
-        # the first maxlines matches.
-        matches = page.get_matches(unique=1, sort='start', type=TextMatch)
-        i, start = self.firstInterestingMatch(page, matches)
-
-        # Format context
-        while i < len(matches) and lineCount < maxlines:
-            match = matches[i]
-
-            # Get context range for this match
-            start, end = self.contextRange(context, match, start, last)
-
-            # Format context lines for matches. Each complete match in
-            # the context will be highlighted, and if the full match is
-            # in the context, we increase the index, and will not show
-            # same match again on a separate line.
-
-            output.append(f.text(u'...'))
-
-            # Get the index of the first match completely within the
-            # context.
-            for j in xrange(0, len(matches)):
-                if matches[j].start >= start:
-                    break
-
-            # Add all matches in context and the text between them
-            while True:
-                match = matches[j]
-                # Ignore matches behind the current position
-                if start < match.end:
-                    # Append the text before match
-                    if start < match.start:
-                        output.append(f.text(body[start:match.start]))
-                    # And the match
-                    output.append(self.formatMatch(body, match, start))
-                    start = match.end
-                # Get next match, but only if its completely within the context
-                if j < len(matches) - 1 and matches[j + 1].end <= end:
-                    j += 1
-                else:
-                    break
-
-            # Add text after last match and finish the line
-            if match.end < end:
-                output.append(f.text(body[match.end:end]))
-            output.append(f.text(u'...'))
-            output.append(f.linebreak(preformatted=0))
-
-            # Increase line and point to the next match
-            lineCount += 1
-            i = j + 1
-
-        output = ''.join(output)
-
-        if not output:
-            # Return the first context characters from the page text
-            output = f.text(page.page.getPageText(length=context))
-            output = output.strip()
-            if not output:
-                # This is a page with no text, only header, for example,
-                # a redirect page.
-                output = f.text(page.page.getPageHeader(length=context))
-
-        return output
-
-    def firstInterestingMatch(self, page, matches):
-        """ Return the first interesting match
-
-        This function is needed only because we don't have yet a weight
-        algorithm for page text matches.
-
-        Try to find the first match in the page text. If we can't find
-        one, we return the first match and start=0.
-
-        :rtype: tuple
-        :returns: index of first match, start of text
-        """
-        header = page.page.getPageHeader()
-        start = len(header)
-        # Find first match after start
-        for i in xrange(len(matches)):
-            if matches[i].start >= start and \
-                    isinstance(matches[i], TextMatch):
-                return i, start
-        return 0, 0
-
-    def contextRange(self, context, match, start, last):
-        """ Compute context range
-
-        Add context around each match. If there is no room for context
-        before or after the match, show more context on the other side.
-
-        :param context: context length
-        :param match: current match
-        :param start: context should not start before that index, unless
-                      end is past the last character.
-        :param last: last character index
-        :rtype: tuple
-        :returns: start, end of context
-        """
-        # Start by giving equal context on both sides of match
-        contextlen = max(context - len(match), 0)
-        cstart = match.start - contextlen / 2
-        cend = match.end + contextlen / 2
-
-        # If context start before start, give more context on end
-        if cstart < start:
-            cend += start - cstart
-            cstart = start
-
-        # But if end if after last, give back context to start
-        if cend > last:
-            cstart -= cend - last
-            cend = last
-
-        # Keep context start positive for very short texts
-        cstart = max(cstart, 0)
-
-        return cstart, cend
-
-    def formatTitle(self, page):
-        """ Format page title
-
-        Invoke format match on all unique matches in page title.
-
-        :param page: found page
-        :rtype: unicode
-        :returns: formatted title
-        """
-        # Get unique title matches sorted by match.start
-        matches = page.get_matches(unique=1, sort='start', type=TitleMatch)
-
-        # Format
-        pagename = page.page_name
-        f = self.formatter
-        output = []
-        start = 0
-        for match in matches:
-            # Ignore matches behind the current position
-            if start < match.end:
-                # Append the text before the match
-                if start < match.start:
-                    output.append(f.text(pagename[start:match.start]))
-                # And the match
-                output.append(self.formatMatch(pagename, match, start))
-                start = match.end
-        # Add text after match
-        if start < len(pagename):
-            output.append(f.text(pagename[start:]))
-
-        if page.attachment: # show the attachment that matched
-            output.extend([
-                    " ",
-                    f.strong(1),
-                    f.text("(%s)" % page.attachment),
-                    f.strong(0)])
-
-        return ''.join(output)
-
-    def formatMatch(self, body, match, location):
-        """ Format single match in text
-
-        Format the part of the match after the current location in the
-        text. Matches behind location are ignored and an empty string is
-        returned.
-
-        :param body: text containing match
-        :param match: search match in text
-        :param location: current location in text
-        :rtype: unicode
-        :returns: formatted match or empty string
-        """
-        start = max(location, match.start)
-        if start < match.end:
-            f = self.formatter
-            output = [
-                f.strong(1),
-                f.text(body[start:match.end]),
-                f.strong(0),
-                ]
-            return ''.join(output)
-        return ''
-
-    def formatPageLinks(self, hitsFrom, hitsPerPage, hitsNum):
-        """ Format previous and next page links in page
-
-        :param hitsFrom: current position in the hits
-        :param hitsPerPage: number of hits per page
-        :param hitsNum: number of hits
-        :rtype: unicode
-        :returns: links to previous and next pages (if exist)
-        """
-        f = self.formatter
-        querydict = dict(wikiutil.parseQueryString(self.request.query_string))
-
-        def page_url(n):
-            querydict.update({'from': n * hitsPerPage})
-            return XXX.page.url(self.request, querydict, escape=0)
-
-        pages = hitsNum // hitsPerPage
-        remainder = hitsNum % hitsPerPage
-        if remainder:
-            pages += 1
-        cur_page = hitsFrom // hitsPerPage
-
-        textlinks = []
-
-        # previous page available
-        if cur_page > 0:
-            textlinks.append(''.join([
-                        f.url(1, href=page_url(cur_page-1)),
-                        f.text(_('Previous')),
-                        f.url(0)]))
-        else:
-            textlinks.append('')
-
-        # list of pages to be shown
-        page_range = range(*(
-            cur_page - 5 < 0 and
-                (0, pages > 10 and 10 or pages) or
-                (cur_page - 5, cur_page + 6 > pages and
-                    pages or cur_page + 6)))
-        textlinks.extend([''.join([
-                i != cur_page and f.url(1, href=page_url(i)) or '',
-                f.text(str(i+1)),
-                i != cur_page and f.url(0) or '',
-            ]) for i in page_range])
-
-        # next page available
-        if cur_page < pages - 1:
-            textlinks.append(''.join([
-                f.url(1, href=page_url(cur_page+1)),
-                f.text(_('Next')),
-                f.url(0)]))
-        else:
-            textlinks.append('')
-
-        return ''.join([
-            f.table(1, attrs={'tableclass': 'searchpages'}),
-            f.table_row(1),
-                f.table_cell(1),
-                # textlinks
-                (f.table_cell(0) + f.table_cell(1)).join(textlinks),
-                f.table_cell(0),
-            f.table_row(0),
-            f.table(0),
-        ])
-
-    def formatHitInfoBar(self, page):
-        """ Returns the code for the information below a search hit
-
-        :param page: the FoundPage instance
-        """
-        request = self.request
-        f = self.formatter
-        p = page.page
-
-        rev = p.get_real_rev()
-        if rev is None:
-            rev = 0
-
-        size_str = '%.1fk' % (p.size()/1024.0)
-        revisions = p.getRevList()
-        if len(revisions) and rev == revisions[0]:
-            rev_str = '%s: %d (%s)' % (_('rev'), rev, _('current'))
-        else:
-            rev_str = '%s: %d' % (_('rev'), rev, )
-        lastmod_str = _('last modified: %s') % p.mtime(printable=True)
-
-        result = f.paragraph(1, attr={'class': 'searchhitinfobar'}) + \
-                 f.text('%s - %s %s' % (size_str, rev_str, lastmod_str)) + \
-                 f.paragraph(0)
-        return result
-
-    def querystring(self, querydict=None):
-        """ Return query string, used in the page link
-
-        :keyword querydict: use these parameters (default: None)
-        """
-        if querydict is None:
-            querydict = {}
-        if 'action' not in querydict or querydict['action'] == 'AttachFile':
-            highlight = self.query.highlight_re()
-            if highlight:
-                querydict.update({'highlight': highlight})
-        querystr = wikiutil.makeQueryString(querydict)
-        return querystr
-
-    def formatInfo(self, formatter, page):
-        """ Return formatted match info
-
-        :param formatter: the formatter instance to use
-        :param page: the current page instance
-        """
-        template = u' . . . %s %s'
-        template = u"%s%s%s" % (formatter.span(1, css_class="info"),
-                                template,
-                                formatter.span(0))
-        # Count number of unique matches in text of all types
-        count = len(page.get_matches(unique=1))
-        info = template % (count, self.matchLabel[count != 1])
-        return info
-
-    def getvalue(self):
-        """ Return output in div with CSS class """
-        value = [
-            self.formatter.div(1, css_class='searchresults'),
-            self.buffer.getvalue(),
-            self.formatter.div(0),
-            ]
-        return '\n'.join(value)
-
-    def _reset(self, request, formatter):
-        """ Update internal state before new output
-
-        Do not call this, it should be called only by the instance code.
-
-        Each request might need different translations or other user preferences.
-
-        :param request: current request
-        :param formatter: the formatter instance to use
-        """
-        self.buffer = StringIO.StringIO()
-        self.formatter = formatter
-        self.request = request
-        # Use 1 match, 2 matches...
-        self.matchLabel = (_('match'), _('matches'))
-
-
-def getSearchResults(request, query, hits, start, sort, estimated_hits):
-    """ Return a SearchResults object with the specified properties
-
-    :param request: current request
-    :param query: the search query object tree
-    :param hits: list of hits
-    :param start: position to start showing the hits
-    :param sort: sorting of the results, either 'weight' or 'page_name'
-    :param estimated_hits: if true, use this estimated hit count
-    """
-    result_hits = []
-    for wikiname, page, attachment, match, rev in hits:
-        if wikiname in (app.cfg.interwikiname, 'Self'): # a local match
-            if attachment:
-                result_hits.append(FoundAttachment(page.page_name, attachment, matches=match, page=page, rev=rev))
-            else:
-                result_hits.append(FoundPage(page.page_name, matches=match, page=page, rev=rev))
-        else:
-            page_name = page # for remote wikis, we have the page_name, not the page obj
-            result_hits.append(FoundRemote(wikiname, page_name, attachment, matches=match, rev=rev))
-    elapsed = time.time() - start
-    count = 0 # XXX was: count of items in storage
-    return SearchResults(query, result_hits, count, elapsed, sort,
-            estimated_hits)
-
--- a/MoinMoin/search/term.py	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,459 +0,0 @@
-# Copyright: 2008 MoinMoin:JohannesBerg
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - search expression object representation
-
-    This module defines the possible search terms for a query to the
-    storage backend. This is used, for example, to implement searching,
-    page lists etc.
-
-    Note that some backends can optimise some of the search terms, for
-    example a backend that has indexed various metadata keys can optimise
-    easy expressions containing ItemMetaDataMatch terms. This is only allowed
-    for classes documented as being 'final' which hence also means that
-    their _evaluate function may not be overridden by descendent classes.
-
-    For example, that metadata backend could test if the expression is an
-    ItemMetaDataMatch expression, and if so, simply return the appropriate
-    index; or if it is an AND() expression build the page list from the
-    index, remove the ItemMetaDataMatch instance from the AND list and match
-    the resulting expression only for pages in that list. Etc.
-
-    TODO: Should we write some generic code for picking apart expressions
-          like that?
-"""
-
-
-import re
-
-from MoinMoin.storage.error import NoSuchRevisionError
-
-# Base classes
-
-class Term(object):
-    """
-    Base class for search terms.
-    """
-    # relative cost of this search term
-    _cost = 0
-
-    def __init__(self):
-        pass
-
-    def evaluate(self, item):
-        """
-        Evaluate this term and return True or False if the
-        item identified by the parameters matches.
-
-        :param item: the item
-        """
-        assert hasattr(self, '_result')
-
-        if self._result is None:
-            self._result = self._evaluate(item)
-
-        return self._result
-
-    def _evaluate(self, item):
-        """
-        Implements the actual evaluation
-        """
-        raise NotImplementedError()
-
-    def prepare(self):
-        """
-        Prepare this search term to make it ready for testing.
-        Must be called before each outermost-level evaluate.
-        """
-        self._result = None
-
-    def copy(self):
-        """
-        Make a copy of this search term.
-        """
-        return self.__class__()
-
-class UnaryTerm(Term):
-    """
-    Base class for search terms that has a single contained
-    search term, e.g. NOT.
-    """
-    def __init__(self, term):
-        Term.__init__(self)
-        assert isinstance(term, Term)
-        self.term = term
-
-    def prepare(self):
-        Term.prepare(self)
-        self.term.prepare()
-        self._cost = self.term._cost
-
-    def __repr__(self):
-        return u'<%s(%r)>' % (self.__class__.__name__, self.term)
-
-    def copy(self):
-        return self.__class__(self.term.copy())
-
-class ListTerm(Term):
-    """
-    Base class for search terms that contain multiple other
-    search terms, e.g. AND.
-    """
-    def __init__(self, *terms):
-        Term.__init__(self)
-        for e in terms:
-            assert isinstance(e, Term)
-        self.terms = list(terms)
-
-    def prepare(self):
-        Term.prepare(self)
-        # the sum of all costs is a bit of a worst-case cost...
-        self._cost = 0
-        for e in self.terms:
-            e.prepare()
-            self._cost += e._cost
-        self.terms.sort(cmp=lambda x, y: cmp(x._cost, y._cost))
-
-    def remove(self, subterm):
-        self.terms.remove(subterm)
-
-    def add(self, subterm):
-        self.terms.append(subterm)
-
-    def __repr__(self):
-        return u'<%s(%s)>' % (self.__class__.__name__,
-                              ', '.join([repr(t) for t in self.terms]))
-
-    def copy(self):
-        terms = [t.copy() for t in self.terms]
-        return self.__class__(*terms)
-
-# Logical expression classes
-
-class AND(ListTerm):
-    """
-    AND connection between multiple terms. Final.
-    """
-    def _evaluate(self, item):
-        for e in self.terms:
-            if not e.evaluate(item):
-                return False
-        return True
-
-class OR(ListTerm):
-    """
-    OR connection between multiple terms. Final.
-    """
-    def _evaluate(self, item):
-        for e in self.terms:
-            if e.evaluate(item):
-                return True
-        return False
-
-class NOT(UnaryTerm):
-    """
-    Inversion of a single term. Final.
-    """
-    def _evaluate(self, item):
-        return not self.term.evaluate(item)
-
-class XOR(ListTerm):
-    """
-    XOR connection between multiple terms, i.e. exactly
-    one must be True. Final.
-    """
-    def _evaluate(self, item):
-        count = 0
-        for e in self.terms:
-            if e.evaluate(item):
-                count += 1
-        return count == 1
-
-class _BOOL(Term):
-    _cost = 0
-    def __init__(self, val):
-        self._val = val
-
-    def prepare(self):
-        self._result = self._val
-
-    def __repr__(self):
-        return '<%s>' % str(self._val).upper()
-
-    def copy(self):
-        return self
-
-TRUE = _BOOL(True)
-FALSE = _BOOL(False)
-
-def BOOL(b):
-    if b:
-        return TRUE
-    return FALSE
-
-# Actual Moin search terms
-
-class TextRE(Term):
-    """
-    Regular expression full text match, use as last resort.
-    """
-    _cost = 1000 # almost prohibitive
-    def __init__(self, needle_re):
-        Term.__init__(self)
-        assert hasattr(needle_re, 'search')
-        self._needle_re = needle_re
-
-    def _evaluate(self, item):
-        try:
-            rev = item.get_revision(-1)
-        except NoSuchRevisionError:
-            return False
-        data = rev.read()
-        return not (not self._needle_re.search(data))
-
-    def __repr__(self):
-        return u'<term.TextRE(...)>'
-
-    def copy(self):
-        return TextRE(self._needle_re)
-
-class Text(TextRE):
-    """
-    Full text match including middle of words and over word
-    boundaries. Final.
-    """
-    def __init__(self, needle, case_sensitive):
-        flags = re.UNICODE
-        if not case_sensitive:
-            flags = flags | re.IGNORECASE
-        _needle_re = re.compile(re.escape(needle), flags)
-        TextRE.__init__(self, _needle_re)
-        self.needle = needle
-        self.case_sensitive = case_sensitive
-
-    def __repr__(self):
-        return u'<term.Text(%s, %s)>' % (self.needle, self.case_sensitive)
-
-    def copy(self):
-        return Text(self.needle, self.case_sensitive)
-
-class Word(TextRE):
-    """
-    Full text match finding exact words. Final.
-    """
-    def __init__(self, needle, case_sensitive):
-        flags = re.UNICODE
-        if not case_sensitive:
-            flags = flags | re.IGNORECASE
-        _needle_re = re.compile('\\b' + re.escape(needle) + '\\b', flags)
-        TextRE.__init__(self, _needle_re)
-        self.needle = needle
-        self.case_sensitive = case_sensitive
-
-    def __repr__(self):
-        return u'<term.Word(%s, %s)>' % (self.needle, self.case_sensitive)
-
-    def copy(self):
-        return Word(self.needle, self.case_sensitive)
-
-class WordStart(TextRE):
-    """
-    Full text match finding the start of a word. Final.
-    """
-    def __init__(self, needle, case_sensitive):
-        flags = re.UNICODE
-        if not case_sensitive:
-            flags = flags | re.IGNORECASE
-        _needle_re = re.compile('\\b' + re.escape(needle), flags)
-        TextRE.__init__(self, _needle_re)
-        self.needle = needle
-        self.case_sensitive = case_sensitive
-
-    def __repr__(self):
-        return u'<term.WordStart(%s, %s)>' % (self.needle, self.case_sensitive)
-
-    def copy(self):
-        return WordStart(self.needle, self.case_sensitive)
-
-class WordEnd(TextRE):
-    """
-    Full text match finding the end of a word. Final.
-    """
-    def __init__(self, needle, case_sensitive):
-        flags = re.UNICODE
-        if not case_sensitive:
-            flags = flags | re.IGNORECASE
-        _needle_re = re.compile(re.escape(needle) + '\\b', flags)
-        TextRE.__init__(self, _needle_re)
-        self.needle = needle
-        self.case_sensitive = case_sensitive
-
-    def __repr__(self):
-        return u'<term.WordEnd(%s, %s)>' % (self.needle, self.case_sensitive)
-
-    def copy(self):
-        return WordEnd(self.needle, self.case_sensitive)
-
-class NameRE(Term):
-    """
-    Matches the item's name with a given regular expression.
-    """
-    _cost = 10 # one of the cheapest
-    def __init__(self, needle_re):
-        Term.__init__(self)
-        assert hasattr(needle_re, 'search')
-        self._needle_re = needle_re
-
-    def _evaluate(self, item):
-        return not (not self._needle_re.search(item.name))
-
-    def __repr__(self):
-        return u'<term.NameRE(...)>'
-
-    def copy(self):
-        return NameRE(self._needle_re)
-
-class Name(NameRE):
-    """
-    Item name match, given needle must occur in item's name. Final.
-    """
-    def __init__(self, needle, case_sensitive):
-        assert isinstance(needle, unicode)
-        flags = re.UNICODE
-        if not case_sensitive:
-            flags = flags | re.IGNORECASE
-        _needle_re = re.compile(re.escape(needle), flags)
-        NameRE.__init__(self, _needle_re)
-        self.needle = needle
-        self.case_sensitive = case_sensitive
-
-    def __repr__(self):
-        return u'<term.Name(%s, %s)>' % (self.needle, self.case_sensitive)
-
-    def copy(self):
-        return Name(self.needle, self.case_sensitive)
-
-class NameFn(Term):
-    """
-    Arbitrary item name matching function.
-    """
-    def __init__(self, fn):
-        Term.__init__(self)
-        assert callable(fn)
-        self._fn = fn
-
-    def _evaluate(self, item):
-        return not (not self._fn(item.name))
-
-    def __repr__(self):
-        return u'<term.NameFn(%r)>' % (self._fn, )
-
-    def copy(self):
-        return NameFn(self._fn)
-
-class ItemMetaDataMatch(Term):
-    """
-    Matches a metadata key/value pair of an item, requires
-    existence of the metadata key. Final.
-    """
-    _cost = 100 # fairly expensive but way cheaper than text
-    def __init__(self, key, val):
-        Term.__init__(self)
-        self.key = key
-        self.val = val
-
-    def _evaluate(self, item):
-        return self.key in item and item[self.key] == self.val
-
-    def __repr__(self):
-        return u'<%s(%s: %s)>' % (self.__class__.__name__, self.key, self.val)
-
-    def copy(self):
-        return ItemMetaDataMatch(self.key, self.val)
-
-class ItemHasMetaDataValue(Term):
-    """
-    Match when the metadata value for a given key contains the given
-    value (when the item's metadata value is a dict or list), requires
-    existence of the metadata key. Final.
-    """
-    _cost = 100 # fairly expensive but way cheaper than text
-    def __init__(self, key, val):
-        Term.__init__(self)
-        self.key = key
-        self.val = val
-
-    def _evaluate(self, item):
-        return self.key in item and self.val in item[self.key]
-
-    def __repr__(self):
-        return u'<%s(%s: %s)>' % (self.__class__.__name__, self.key, self.val)
-
-    def copy(self):
-        return ItemHasMetaDataValue(self.key, self.val)
-
-class ItemHasMetaDataKey(Term):
-    """
-    Requires existence of the metadata key. Final.
-    """
-    _cost = 90 # possibly cheaper than ItemMetaDataMatch
-    def __init__(self, key):
-        Term.__init__(self)
-        self.key = key
-
-    def _evaluate(self, item):
-        return self.key in item
-
-    def __repr__(self):
-        return u'<%s(%s)>' % (self.__class__.__name__, self.key)
-
-    def copy(self):
-        return ItemHasMetaDataKey(self.key)
-
-class LastRevisionMetaDataMatch(Term):
-    """
-    Matches a metadata key/value pair of an item, requires
-    existence of the metadata key. Final.
-    """
-    _cost = 100 # fairly expensive but way cheaper than text
-    def __init__(self, key, val):
-        Term.__init__(self)
-        self.key = key
-        self.val = val
-
-    def _evaluate(self, item):
-        try:
-            rev = item.get_revision(-1)
-        except NoSuchRevisionError:
-            return False
-        return self.key in rev and rev[self.key] == self.val
-
-    def __repr__(self):
-        return u'<%s(%s: %s)>' % (self.__class__.__name__, self.key, self.val)
-
-    def copy(self):
-        return LastRevisionMetaDataMatch(self.key, self.val)
-
-class LastRevisionHasMetaDataKey(Term):
-    """
-    Requires existence of the metadata key. Final.
-    """
-    _cost = 90 # possibly cheaper than LastRevisionMetaDataMatch
-    def __init__(self, key):
-        Term.__init__(self)
-        self.key = key
-
-    def _evaluate(self, item):
-        try:
-            rev = item.get_revision(-1)
-        except NoSuchRevisionError:
-            return False
-        return self.key in rev
-
-    def __repr__(self):
-        return u'<%s(%s)>' % (self.__class__.__name__, self.key)
-
-    def copy(self):
-        return LastRevisionHasMetaDataKey(self.key)
-
--- a/MoinMoin/security/_tests/test_security.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/security/_tests/test_security.py	Sat Mar 12 14:14:22 2011 +0100
@@ -19,7 +19,7 @@
 AccessControlList = security.AccessControlList
 
 from MoinMoin.user import User
-from MoinMoin.items import ACL
+from MoinMoin.config import ACL
 
 from MoinMoin._tests import update_item
 from MoinMoin._tests import become_trusted
--- a/MoinMoin/storage/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/__init__.py	Sat Mar 12 14:14:22 2011 +0100
@@ -51,10 +51,7 @@
                                    BackendError, NoSuchItemError, \
                                    RevisionAlreadyExistsError, ItemAlreadyExistsError
 
-# we need a specific hash algorithm to store hashes of revision data into meta
-# data. meta[HASH_ALGORITHM] = hash(rev_data, HASH_ALGORITHM)
-# some backends may use this also for other purposes.
-HASH_ALGORITHM = 'sha1'
+from MoinMoin.config import SIZE, HASH_ALGORITHM
 
 import hashlib
 
@@ -417,18 +414,6 @@
         """
         raise NotImplementedError()
 
-    def _get_revision_size(self, revision):
-        """
-        Lazily access the revision's data size. This needs not be implemented
-        if all StoredRevision objects are instantiated with the size= keyword
-        parameter.
-
-        :type revision: Object of a subclass of Revision.
-        :param revision: The revision on which we want to operate.
-        :returns: int
-        """
-        raise NotImplementedError()
-
     def _seek_revision_data(self, revision, position, mode):
         """
         Set the revision's cursor on the revision's data.
@@ -467,8 +452,6 @@
             for k, v in rev1.iteritems():
                 if rev2[k] != v:
                     return False
-            if rev1.size != rev2.size:
-                return False
             return True
 
         if name is None:
@@ -741,6 +724,7 @@
         rev = self._uncommitted_revision
         assert rev is not None
         rev[HASH_ALGORITHM] = unicode(rev._rev_hash.hexdigest())
+        rev[SIZE] = rev._size
         self._backend._commit_item(rev)
         self._uncommitted_revision = None
 
@@ -862,12 +846,11 @@
     Do NOT create instances of this class directly, but use item.get_revision or
     one of the other methods intended for getting stored revisions.
     """
-    def __init__(self, item, revno, timestamp=None, size=None):
+    def __init__(self, item, revno, timestamp=None):
         """
         Initialize the StoredRevision
         """
         Revision.__init__(self, item, revno, timestamp)
-        self._size = size
 
     def _get_ts(self):
         if self._timestamp is None:
@@ -876,15 +859,6 @@
 
     timestamp = property(_get_ts, doc="This property returns the creation timestamp of the revision")
 
-    def _get_size(self):
-        if self._size is None:
-            self._size = self._backend._get_revision_size(self)
-            assert self._size is not None
-
-        return self._size
-
-    size = property(_get_size, doc="Size of revision's data")
-
     def __setitem__(self, key, value):
         """
         Revision metadata cannot be altered, thus, we raise an Exception.
@@ -934,6 +908,8 @@
         """
         Revision.__init__(self, item, revno, None)
         self._metadata = {}
+        # these values need to be kept uptodate to that item.commit() can
+        # use them to update the metadata of the rev before committing it:
         self._size = 0
         self._rev_hash = hashlib.new(HASH_ALGORITHM)
 
@@ -946,11 +922,6 @@
 
     timestamp = property(_get_ts, _set_ts, doc="This property accesses the creation timestamp of the revision")
 
-    def _get_size(self):
-        return self._size
-
-    size = property(_get_size, doc="Size of data written so far")
-
     def __setitem__(self, key, value):
         """
         Internal method used for dict-like access to the NewRevisions metadata-dict.
--- a/MoinMoin/storage/_tests/test_backends.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/_tests/test_backends.py	Sat Mar 12 14:14:22 2011 +0100
@@ -23,7 +23,8 @@
 from MoinMoin.storage import Item, NewRevision
 from MoinMoin.storage.backends import memory
 from MoinMoin.storage.error import NoSuchItemError, ItemAlreadyExistsError, NoSuchRevisionError, RevisionAlreadyExistsError
-from MoinMoin.search import term
+from MoinMoin.storage import terms
+from MoinMoin.config import SIZE
 
 item_names = (u"quite_normal",
               u"äöüßłóąćółąńśćżź",
@@ -175,7 +176,7 @@
             self.create_rev_item_helper(name)
         self.create_meta_item_helper(u"new_song_player")
         query_string = u"song"
-        query = term.Name(query_string, True)
+        query = terms.Name(query_string, True)
         for num, item in enumerate(self.backend.search_items(query)):
             assert item.name.find(query_string) != -1
         assert num == 2
@@ -191,11 +192,11 @@
             assert len(found) == expected
 
         # must be /part/ of the name
-        yield _test_search, term.Name(u'AbCdEf', False), 3
-        yield _test_search, term.Name(u'AbCdEf', True), 0
-        yield _test_search, term.Name(u'abcdef', True), 3
-        yield _test_search, term.NameRE(re.compile(u'abcde.*')), 4
-        yield _test_search, term.NameFn(lambda n: n == u'abcdef'), 1
+        yield _test_search, terms.Name(u'AbCdEf', False), 3
+        yield _test_search, terms.Name(u'AbCdEf', True), 0
+        yield _test_search, terms.Name(u'abcdef', True), 3
+        yield _test_search, terms.NameRE(re.compile(u'abcde.*')), 4
+        yield _test_search, terms.NameFn(lambda n: n == u'abcdef'), 1
 
     def test_iteritems_1(self):
         for num in range(10, 20):
@@ -549,32 +550,26 @@
         item = self.backend.create_item(u'size1')
         rev = item.create_revision(0)
         rev.write('asdf')
-        assert rev.size == 4
         rev.write('asdf')
-        assert rev.size == 8
         item.commit()
         rev = item.get_revision(0)
-        assert rev.size == 8
+        assert rev[SIZE] == 8
 
         for nrev in self.backend.history():
-            assert nrev.size == 8
+            assert nrev[SIZE] == 8
 
     def test_size_2(self):
         item = self.backend.create_item(u'size2')
         rev0 = item.create_revision(0)
         data0 = 'asdf'
         rev0.write(data0)
-        assert rev0.size == len(data0)
         item.commit()
         rev1 = item.create_revision(1)
-        rev1["size"] = "should be 0" # invalid
-        assert rev1.size == 0
-        data1 = '' # we never write this to the rev1!
         item.commit()
         rev1 = item.get_revision(1)
-        assert rev1.size == len(data1)
+        assert rev1[SIZE] == 0
         rev0 = item.get_revision(0)
-        assert rev0.size == len(data0)
+        assert rev0[SIZE] == len(data0)
 
     def test_various_revision_metadata_values(self):
         def test_value(value, no):
--- a/MoinMoin/storage/_tests/test_backends_fs19.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/_tests/test_backends_fs19.py	Sat Mar 12 14:14:22 2011 +0100
@@ -13,7 +13,7 @@
 
 from flask import current_app as app
 
-from MoinMoin.items import TAGS
+from MoinMoin.config import TAGS
 from MoinMoin.storage import Item
 from MoinMoin.storage.backends._fsutils import quoteWikinameFS, unquoteWikiname
 from MoinMoin.storage.backends.fs19 import FSPageBackend, regenerate_acl, process_categories
--- a/MoinMoin/storage/_tests/test_backends_hg.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/_tests/test_backends_hg.py	Sat Mar 12 14:14:22 2011 +0100
@@ -82,7 +82,7 @@
         item.commit()
         item = self.backend.get_item('existing')
         rev = item.get_revision(-1)
-        assert len(dict(rev)) == 10000 + 1 # 'sha1' key is added automatically on commit
+        assert len(dict(rev)) == 10000 + 2 # 'sha1' and 'size' key is added automatically on commit
         for num in xrange(10000):
             revval = "revision metadata value for key %d" % num
             assert rev["%s" % num] == revval * 10
--- a/MoinMoin/storage/_tests/test_middleware_acl.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/_tests/test_middleware_acl.py	Sat Mar 12 14:14:22 2011 +0100
@@ -12,7 +12,7 @@
 
 from flask import flaskg
 
-from MoinMoin.items import ACL
+from MoinMoin.config import ACL
 from MoinMoin.storage.error import AccessDeniedError
 from MoinMoin.storage._tests.test_backends import BackendTest
 from MoinMoin._tests import wikiconfig
--- a/MoinMoin/storage/_tests/test_serialization.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/_tests/test_serialization.py	Sat Mar 12 14:14:22 2011 +0100
@@ -39,6 +39,7 @@
                     '<entry key="sha1"><str>763675d6a1d8d0a3a28deca62bb68abd8baf86f3</str>\n</entry>\n'
                     '<entry key="m1"><str>m1</str>\n</entry>\n'
                     '<entry key="name"><str>foo1</str>\n</entry>\n'
+                    '<entry key="size"><int>4</int>\n</entry>\n'
                     '<entry key="uuid"><str>foo1</str>\n</entry>\n'
                     '</meta>\n'
                     '<data coding="base64"><chunk>YmFyMQ==</chunk>\n</data>\n'
@@ -68,6 +69,7 @@
                     '<entry key="sha1"><str>033c4846b506a4a48e32cdf54515c91d3499adb3</str>\n</entry>\n'
                     '<entry key="m1"><str>m1r0</str>\n</entry>\n'
                     '<entry key="name"><str>foo2</str>\n</entry>\n'
+                    '<entry key="size"><int>4</int>\n</entry>\n'
                     '<entry key="uuid"><str>foo2</str>\n</entry>\n'
                     '</meta>\n'
                     '<data coding="base64"><chunk>YmFyMg==</chunk>\n</data>\n'
@@ -78,6 +80,7 @@
                     '<entry key="sha1"><str>f91d8fc20a5de853e62105cc1ee0bf47fd7ded0f</str>\n</entry>\n'
                     '<entry key="m1"><str>m1r1</str>\n</entry>\n'
                     '<entry key="name"><str>foo2</str>\n</entry>\n'
+                    '<entry key="size"><int>4</int>\n</entry>\n'
                     '<entry key="uuid"><str>foo2</str>\n</entry>\n'
                     '</meta>\n'
                     '<data coding="base64"><chunk>YmF6Mg==</chunk>\n</data>\n'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/storage/_tests/test_terms.py	Sat Mar 12 14:14:22 2011 +0100
@@ -0,0 +1,289 @@
+# Copyright: 2008 MoinMoin:JohannesBerg
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+    MoinMoin - Term tests.
+"""
+
+
+import re
+
+from MoinMoin.storage import terms as term
+from MoinMoin.storage.backends.memory import MemoryBackend
+
+
+_item_contents = {
+    u'a': u'abcdefg hijklmnop',
+    u'b': u'bbbbbbb bbbbbbbbb',
+    u'c': u'Abiturienten Apfeltortor',
+    u'Lorem': u'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis placerat, tortor quis sollicitudin dictum, nisi tellus aliquam quam, ac varius lacus diam eget tortor. Nulla vehicula, nisi ac hendrerit aliquam, libero erat tempor ante, lobortis placerat lacus justo vitae erat. In rutrum odio a sem. In ac risus vel diam vulputate luctus. Fusce sit amet est. Morbi consectetuer eros vel risus. In nulla lacus, ultrices id, vestibulum tempus, dictum in, mauris. Quisque rutrum faucibus nisl. Suspendisse potenti. In hac habitasse platea dictumst. Donec ac magna ac eros malesuada facilisis. Pellentesque viverra nibh nec dui. Praesent venenatis lectus vehicula eros. Phasellus pretium, ante at mollis luctus, nibh lacus ultricies eros, vitae pharetra lacus leo at neque. Nullam vel sapien. In in diam id massa nonummy suscipit. Curabitur vel dui sed tellus pellentesque pretium.',
+}
+
+_item_metadata = {
+    u'a': {'m1': 'True', 'm2': '222'},
+    u'A': {'m1': 'True', 'm2': '333'},
+    u'b': {'m1': 'False', 'm2': '222'},
+    u'c': {'m1': 'True', 'm2': '222'},
+    u'B': {'m1': 'False', 'm2': '333'},
+    u'Lorem': {'m1': '7', 'm2': '444'},
+}
+
+_lastrevision_metadata = {
+    u'a': {'a': '1'},
+    u'A': {'a': ''},
+    u'b': {'a': '0'},
+    u'c': {'a': 'False'},
+    u'B': {'a': ''},
+    u'Lorem': {'a': '42'},
+}
+
+for n in _item_contents.keys():
+    nl = n.lower()
+    nu = n.upper()
+    _item_contents[nl] = _item_contents[n].lower()
+    _item_contents[nu] = _item_contents[n].upper()
+    if not nl in _item_metadata:
+        _item_metadata[nl] = _item_metadata[n]
+    if not nu in _item_metadata:
+        _item_metadata[nu] = _item_metadata[n]
+    if not nl in _lastrevision_metadata:
+        _lastrevision_metadata[nl] = _lastrevision_metadata[n]
+    if not nu in _lastrevision_metadata:
+        _lastrevision_metadata[nu] = _lastrevision_metadata[n]
+
+memb = MemoryBackend()
+for iname, md in _item_metadata.iteritems():
+    item = memb.create_item(iname)
+    item.change_metadata()
+    item.update(md)
+    item.publish_metadata()
+
+    rev = item.create_revision(0)
+    md = _lastrevision_metadata[iname]
+    rev.update(dict(name=iname, mimetype=u"application/octet-stream"))
+    rev.update(md)
+    rev.write(_item_contents[iname])
+    item.commit()
+
+item = memb.create_item('NR')
+item.change_metadata()
+item.update({'m1': 'True'})
+item.publish_metadata()
+del item
+
+class TermTestData:
+    def __init__(self, text):
+        self.text = text
+    def read(self, size=None):
+        return self.text
+
+class CacheAssertTerm(term.Term):
+    def __init__(self):
+        term.Term.__init__(self)
+        self.evalonce = False
+
+    def _evaluate(self, item):
+        assert not self.evalonce
+        self.evalonce = True
+        return True
+
+class AssertNotCalledTerm(term.Term):
+    def _evaluate(self, item):
+        assert False
+
+class TestTerms:
+    def _evaluate(self, term, itemname, expected):
+        if itemname is not None:
+            item = memb.get_item(itemname)
+        else:
+            item = None
+        term.prepare()
+        assert expected == term.evaluate(item)
+
+    def testSimpleTextSearch(self):
+        terms = [term.Text(u'abcdefg', True), term.Text(u'ijklmn', True)]
+        for item, expected in [('a', True), ('A', False), ('b', False), ('B', False), ('lorem', False), ('NR', False)]:
+            for t in terms:
+                yield self._evaluate, t, item, expected
+
+    def testSimpleTextSearchCI(self):
+        terms = [term.Text(u'abcdefg', False), term.Text(u'ijklmn', False)]
+        for item, expected in [('a', True), ('A', True), ('b', False), ('B', False), ('lorem', False)]:
+            for t in terms:
+                yield self._evaluate, t, item, expected
+
+    def testANDOR(self):
+        tests = [
+            (True,  [1, 1, 1, 1, 1]),
+            (True,  [1, 1, 1, 1]),
+            (True,  [1, 1, 1]),
+            (True,  [1, 1]),
+            (False, [0, 1, 1]),
+            (False, [0, 1, 1, 1]),
+            (False, [1, 0, 1, 1]),
+            (False, [1, 1, 0, 1]),
+            (False, [1, 1, 1, 0]),
+            (False, [0, 1, 1, 0]),
+        ]
+        for expected, l in tests:
+            l = [term.BOOL(i) for i in l]
+            t = term.AND(*l)
+            yield self._evaluate, t, 'a', expected
+        for expected, l in tests:
+            l = [term.BOOL(1 - i) for i in l]
+            t = term.OR(*l)
+            yield self._evaluate, t, 'a', not expected
+
+    def testXOR(self):
+        tests = [
+            (False, [1, 1, 1, 1, 1]),
+            (False, [1, 1, 1, 1]),
+            (False, [1, 1, 1]),
+            (False, [1, 1]),
+            (False, [0, 1, 1]),
+            (False, [0, 1, 1, 1]),
+            (False, [1, 0, 1, 1]),
+            (False, [1, 1, 0, 1]),
+            (False, [1, 1, 1, 0]),
+            (False, [0, 1, 1, 0]),
+            (True,  [0, 0, 0, 1, 0]),
+            (True,  [0, 0, 1, 0]),
+            (True,  [1, 0, 0]),
+            (True,  [0, 1]),
+            (False, [0, 0, 0]),
+        ]
+        for expected, l in tests:
+            l = [term.BOOL(i) for i in l]
+            t = term.XOR(*l)
+            yield self._evaluate, t, 'a', expected
+
+    def testTextSearchRE(self):
+        terms = [term.TextRE(re.compile('^abc')), term.TextRE(re.compile('\shij'))]
+        for item, expected in [('a', True), ('A', False), ('b', False), ('B', False), ('lorem', False), ('NR', False)]:
+            for t in terms:
+                yield self._evaluate, t, item, expected
+
+    def testTextSearchRE2(self):
+        terms = [term.TextRE(re.compile('sollici')), term.TextRE(re.compile('susci'))]
+        for item, expected in [('a', False), ('A', False), ('b', False), ('B', False), ('lorem', True), ('NR', False)]:
+            for t in terms:
+                yield self._evaluate, t, item, expected
+
+    def testResultCaching1(self):
+        cat = CacheAssertTerm()
+        expected = True
+        t = term.AND(cat, cat, cat)
+        yield self._evaluate, t, None, expected
+
+    def testResultCaching2(self):
+        cat = CacheAssertTerm()
+        expected = True
+        t = term.OR(cat, cat, cat)
+        yield self._evaluate, t, None, expected
+
+    def testResultCaching3(self):
+        cat = CacheAssertTerm()
+        expected = False
+        t = term.AND(cat, cat, cat, term.FALSE)
+        yield self._evaluate, t, None, expected
+
+    def testResultCaching4(self):
+        cat = CacheAssertTerm()
+        expected = True
+        t = term.OR(cat, cat, cat)
+        yield self._evaluate, t, None, expected
+
+    def testShortCircuitEval1(self):
+        yield self._evaluate, term.AND(term.TRUE, term.FALSE, AssertNotCalledTerm()), None, False
+
+    def testShortCircuitEval2(self):
+        yield self._evaluate, term.OR(term.TRUE, term.FALSE, AssertNotCalledTerm()), None, True
+
+    def testSimpleTitleSearch(self):
+        for item, expected in [('a', True), ('A', False), ('b', False), ('B', False), ('lorem', False), ('NR', False)]:
+            yield self._evaluate, term.Name(u'a', True), item, expected
+
+    def testSimpleTitleSearchCI(self):
+        for item, expected in [('a', True), ('A', True), ('b', False), ('B', False), ('lorem', False), ('NR', False)]:
+            yield self._evaluate, term.Name(u'a', False), item, expected
+
+    def testTitleRESearch(self):
+        for item, expected in [('a', True), ('A', False), ('b', False), ('B', False), ('lorem', True), ('NR', False)]:
+            yield self._evaluate, term.NameRE(re.compile('(a|e)')), item, expected
+
+    def testMetaMatch1(self):
+        t = term.ItemMetaDataMatch('m1', 'True')
+        for item, expected in [('a', True), ('A', True), ('b', False), ('B', False), ('lorem', False), ('NR', True)]:
+            yield self._evaluate, t, item, expected
+
+    def testMetaMatch2(self):
+        t = term.ItemMetaDataMatch('m2', '333')
+        for item, expected in [('a', False), ('A', True), ('b', False), ('B', True), ('lorem', False), ('NR', False)]:
+            yield self._evaluate, t, item, expected
+
+    def testMetaMatch3(self):
+        t = term.ItemMetaDataMatch('m2', '444')
+        for item, expected in [('a', False), ('A', False), ('b', False), ('B', False), ('lorem', True), ('NR', False)]:
+            yield self._evaluate, t, item, expected
+
+    def testHasMeta1(self):
+        t = term.ItemHasMetaDataKey('m3')
+        for item, expected in [('a', False), ('A', False), ('b', False), ('B', False), ('lorem', False), ('NR', False)]:
+            yield self._evaluate, t, item, expected
+
+    def testHasMeta2(self):
+        t = term.ItemHasMetaDataKey('m1')
+        for item, expected in [('a', True), ('A', True), ('b', True), ('B', True), ('lorem', True), ('NR', True)]:
+            yield self._evaluate, t, item, expected
+
+    def testHasMeta3(self):
+        t = term.LastRevisionHasMetaDataKey('a')
+        for item, expected in [('a', True), ('A', True), ('b', True), ('B', True), ('lorem', True), ('NR', False)]:
+            yield self._evaluate, t, item, expected
+
+    def testHasMeta4(self):
+        t = term.LastRevisionMetaDataMatch('a', '')
+        for item, expected in [('a', False), ('A', True), ('b', False), ('B', True), ('lorem', False), ('NR', False)]:
+            yield self._evaluate, t, item, expected
+
+    def testNameFn(self):
+        t = term.NameFn(lambda x: x in ['a', 'b', 'lorem'])
+        for item, expected in [('a', True), ('A', False), ('b', True), ('B', False), ('lorem', True), ('NR', False)]:
+            yield self._evaluate, t, item, expected
+
+    def testWordCI(self):
+        t = term.Word('Curabitur', False)
+        for item, expected in [('B', False), ('Lorem', True), ('lorem', True), ('LOREM', True), ('NR', False)]:
+            yield self._evaluate, t, item, expected
+
+    def testWord(self):
+        t = term.Word('Curabitur', True)
+        for item, expected in [('B', False), ('Lorem', True), ('lorem', False), ('LOREM', False), ('NR', False)]:
+            yield self._evaluate, t, item, expected
+
+    def testWordStartCI(self):
+        t = term.WordStart('Curabi', False)
+        for item, expected in [('B', False), ('Lorem', True), ('lorem', True), ('LOREM', True), ('NR', False)]:
+            yield self._evaluate, t, item, expected
+
+    def testWordStart(self):
+        t = term.WordStart('Curabi', True)
+        for item, expected in [('c', False), ('Lorem', True), ('lorem', False), ('LOREM', False), ('NR', False)]:
+            yield self._evaluate, t, item, expected
+
+    def testWordStart2(self):
+        t = term.WordStart('abitur', True)
+        for item, expected in [('c', True), ('C', False), ('Lorem', False), ('NR', False)]:
+            yield self._evaluate, t, item, expected
+
+    def testWordStart2CI(self):
+        t = term.WordStart('abitur', False)
+        for item, expected in [('c', True), ('C', True), ('Lorem', False), ('NR', False)]:
+            yield self._evaluate, t, item, expected
+
+    def testWordEndCI(self):
+        t = term.WordEnd('abitur', False)
+        for item, expected in [('c', False), ('Lorem', True), ('lorem', True), ('LOREM', True), ('NR', False)]:
+            yield self._evaluate, t, item, expected
+
+coverage_modules = ['MoinMoin.storage.terms']
--- a/MoinMoin/storage/backends/_flatutils.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/backends/_flatutils.py	Sat Mar 12 14:14:22 2011 +0100
@@ -5,6 +5,7 @@
     MoinMoin - helpers for flatfile meta/data stores
 """
 
+from MoinMoin.config import NAME, ACL, MIMETYPE, LANGUAGE
 
 
 def split_body(body):
@@ -57,8 +58,6 @@
     """
     Adds the processing instructions to the data.
     """
-    from MoinMoin.items import NAME, ACL, MIMETYPE, LANGUAGE
-
     meta_keys = [NAME, ACL, MIMETYPE, LANGUAGE, ]
 
     metadata_data = ""
--- a/MoinMoin/storage/backends/acl.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/backends/acl.py	Sat Mar 12 14:14:22 2011 +0100
@@ -48,13 +48,12 @@
 
 from flask import flaskg
 
-from MoinMoin.items import ACL
 from MoinMoin.security import AccessControlList
 
 from MoinMoin.storage import Item, NewRevision, StoredRevision
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, AccessDeniedError
 
-from MoinMoin.config import ADMIN, READ, WRITE, CREATE, DESTROY
+from MoinMoin.config import ACL, ADMIN, READ, WRITE, CREATE, DESTROY
 
 
 class AclWrapperBackend(object):
@@ -452,13 +451,6 @@
 
     timestamp = property(_get_ts, _set_ts, doc="This property accesses the creation timestamp of the revision")
 
-    @property
-    def size(self):
-        """
-        @see: NewRevision.size.__doc__
-        """
-        return self._revision.size
-
     def __setitem__(self, key, value):
         """
         In order to change an ACL on an item you must have the ADMIN privilege.
--- a/MoinMoin/storage/backends/fileserver.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/backends/fileserver.py	Sat Mar 12 14:14:22 2011 +0100
@@ -23,7 +23,7 @@
 from MoinMoin.storage import Backend, Item, StoredRevision
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError
 
-from MoinMoin.items import ACL, MIMETYPE, ACTION, COMMENT
+from MoinMoin.config import ACL, MIMETYPE, ACTION, COMMENT, SIZE
 MTIME = '__timestamp' # does not exist in storage any more
 
 class FSError(Exception):
@@ -113,9 +113,6 @@
     def _get_revision_timestamp(self, rev):
         return rev._fs_meta[MTIME]
 
-    def _get_revision_size(self, rev):
-        return rev._fs_meta['__size']
-
 
 # Specialized Items/Revisions
 
@@ -161,7 +158,7 @@
         meta = { # make something up
             MTIME: st.st_mtime,
             ACTION: 'SAVE',
-            '__size': st.st_size,
+            SIZE: st.st_size,
         }
         self._fs_meta = meta
         self._fs_data_fname = filepath
--- a/MoinMoin/storage/backends/flatfile.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/backends/flatfile.py	Sat Mar 12 14:14:22 2011 +0100
@@ -33,7 +33,8 @@
                                    RevisionAlreadyExistsError
 from MoinMoin.storage.backends._fsutils import quoteWikinameFS, unquoteWikiname
 from MoinMoin.storage.backends._flatutils import add_metadata_to_body, split_body
-from MoinMoin.items import MIMETYPE, ACTION
+from MoinMoin.config import MIMETYPE, ACTION
+
 
 class FlatFileBackend(Backend):
     def __init__(self, path):
@@ -101,8 +102,8 @@
         data = open(revpath, 'rb').read()
         rev._metadata, data = split_body(data)
         rev._metadata[ACTION] = 'SAVE'
+        rev._metadata[SIZE] = len(data)
         rev._data = StringIO(data)
-        rev._data_size = len(data)
         return rev
 
     def _list_revisions(self, item):
@@ -176,9 +177,6 @@
         revpath = self._rev_path(rev.item.name)
         return os.stat(revpath).st_ctime
 
-    def _get_revision_size(self, rev):
-        return rev._data_size
-
     def _seek_revision_data(self, rev, position, mode):
         rev._data.seek(position, mode)
 
--- a/MoinMoin/storage/backends/fs.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/backends/fs.py	Sat Mar 12 14:14:22 2011 +0100
@@ -465,11 +465,6 @@
             self._get_revision_metadata(rev)
         return rev._fs_metadata['__timestamp']
 
-    def _get_revision_size(self, rev):
-        if rev._fs_file is None:
-            self._get_revision_metadata(rev)
-        return os.stat(rev._fs_revpath).st_size - rev._datastart
-
     def _seek_revision_data(self, rev, position, mode):
         if rev._fs_file is None:
             self._get_revision_metadata(rev)
--- a/MoinMoin/storage/backends/fs19.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/backends/fs19.py	Sat Mar 12 14:14:22 2011 +0100
@@ -32,15 +32,14 @@
 logging = log.getLogger(__name__)
 
 from MoinMoin import wikiutil, config
+from MoinMoin.config import ACL, MIMETYPE, UUID, NAME, NAME_OLD, REVERTED_TO, \
+                            ACTION, ADDRESS, HOSTNAME, USERID, EXTRA, COMMENT, \
+                            IS_SYSITEM, SYSITEM_VERSION, \
+                            TAGS, SIZE, HASH_ALGORITHM
 from MoinMoin.storage import Backend, Item, StoredRevision
-from MoinMoin.items import ACL, MIMETYPE, UUID, NAME, NAME_OLD, REVERTED_TO, \
-                           ACTION, ADDRESS, HOSTNAME, USERID, EXTRA, COMMENT, \
-                           IS_SYSITEM, SYSITEM_VERSION, \
-                           TAGS
 from MoinMoin.storage.backends._fsutils import quoteWikinameFS, unquoteWikiname
 from MoinMoin.storage.backends._flatutils import split_body
 
-from MoinMoin.storage import HASH_ALGORITHM
 
 MTIME = '__timestamp' # does not exist in storage any more
 
@@ -272,9 +271,6 @@
     def _get_revision_timestamp(self, rev):
         return rev._fs_meta[MTIME]
 
-    def _get_revision_size(self, rev):
-        return rev._fs_meta['__size']
-
 
 # Specialized Items/Revisions
 
@@ -383,9 +379,9 @@
             meta[SYSITEM_VERSION] = item._syspages
         data = self._process_data(meta, data)
         data = data.encode(config.charset)
-        meta['__size'] = len(data) # needed for converter checks
-        hash_name, hash_digest = hash_hexdigest(data)
+        size, hash_name, hash_digest = hash_hexdigest(data)
         meta[hash_name] = hash_digest
+        meta[SIZE] = size
         self._fs_meta = {}
         for k, v in meta.iteritems():
             if isinstance(v, list):
@@ -486,13 +482,13 @@
                 ACTION: u'SAVE',
             }
         meta = editlog_data
-        meta['__size'] = 0 # not needed for converter
         # attachments in moin 1.9 were protected by their "parent" page's acl
         if item._fs_parent_acl is not None:
             meta[ACL] = item._fs_parent_acl # XXX not needed for acl_hierarchic
         meta[MIMETYPE] = unicode(wikiutil.MimeType(filename=item._fs_attachname).mime_type())
-        hash_name, hash_digest = hash_hexdigest(open(attpath, 'rb'))
+        size, hash_name, hash_digest = hash_hexdigest(open(attpath, 'rb'))
         meta[hash_name] = hash_digest
+        meta[SIZE] = size
         if item._syspages:
             meta[IS_SYSITEM] = True
             meta[SYSITEM_VERSION] = item._syspages
@@ -790,16 +786,19 @@
     return dict(items)
 
 def hash_hexdigest(content, bufsize=4096):
+    size = 0
     hash = hashlib.new(HASH_ALGORITHM)
     if hasattr(content, "read"):
         while True:
             buf = content.read(bufsize)
             hash.update(buf)
+            size += len(buf)
             if not buf:
                 break
     elif isinstance(content, str):
         hash.update(content)
+        size = len(content)
     else:
         raise ValueError("unsupported content object: %r" % content)
-    return HASH_ALGORITHM, unicode(hash.hexdigest())
+    return size, HASH_ALGORITHM, unicode(hash.hexdigest())
 
--- a/MoinMoin/storage/backends/fs2.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/backends/fs2.py	Sat Mar 12 14:14:22 2011 +0100
@@ -44,7 +44,7 @@
 PICKLEPROTOCOL = 1
 
 MAX_NAME_LEN = 500
-from MoinMoin.storage import HASH_ALGORITHM
+from MoinMoin.config import HASH_ALGORITHM
 
 UUID_LEN = len(make_uuid().hex)
 
@@ -420,9 +420,6 @@
     def _get_revision_timestamp(self, rev):
         return rev._fs_metadata['__timestamp']
 
-    def _get_revision_size(self, rev):
-        return os.stat(rev._fs_path_data).st_size
-
     def _open_revision_data(self, rev, mode='rb'):
         if rev._fs_file_data is None:
             rev._fs_file_data = open(rev._fs_path_data, mode) # XXX keeps file open as long as rev exists
--- a/MoinMoin/storage/backends/hg.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/backends/hg.py	Sat Mar 12 14:14:22 2011 +0100
@@ -56,7 +56,7 @@
 except ImportError:
     from MoinMoin.util import pycdb as cdb
 
-from MoinMoin.items import USERID, COMMENT
+from MoinMoin.config import USERID, COMMENT
 from MoinMoin.storage import Backend, Item, StoredRevision, NewRevision
 from MoinMoin.storage.error import (BackendError, NoSuchItemError, NoSuchRevisionError,
                                    RevisionNumberMismatchError, ItemAlreadyExistsError,
@@ -435,10 +435,6 @@
         """Return given Revision timestamp"""
         return long(self._get_filectx(revision).date()[0])
 
-    def _get_revision_size(self, revision):
-        """Return size of given Revision in bytes."""
-        return self._get_filectx(revision).size()
-
     def _seek_revision_data(self, revision, position, mode):
         """Set the Revisions cursor on the Revisions data."""
         self._open_revision_data(revision)
@@ -652,8 +648,8 @@
 
 class MercurialStoredRevision(StoredRevision):
 
-    def __init__(self, item, revno, timestamp=None, size=None):
-        StoredRevision.__init__(self, item, revno, timestamp, size)
+    def __init__(self, item, revno, timestamp=None):
+        StoredRevision.__init__(self, item, revno, timestamp)
         self._data = None
 
     def get_parents(self):
--- a/MoinMoin/storage/backends/indexing.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/backends/indexing.py	Sat Mar 12 14:14:22 2011 +0100
@@ -26,8 +26,7 @@
 logging = log.getLogger(__name__)
 
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError
-from MoinMoin.items import ACL, MIMETYPE, UUID, NAME, NAME_OLD, \
-                           TAGS
+from MoinMoin.config import ACL, MIMETYPE, UUID, NAME, NAME_OLD, TAGS
 
 
 class IndexingBackendMixin(object):
--- a/MoinMoin/storage/backends/memory.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/backends/memory.py	Sat Mar 12 14:14:22 2011 +0100
@@ -166,7 +166,7 @@
         except KeyError:
             raise NoSuchRevisionError("No Revision #%d on Item %s - Available revisions: %r" % (revno, item.name, revisions))
         else:
-            revision = self.StoredRevision(item, revno, timestamp=metadata['__timestamp'], size=len(data))
+            revision = self.StoredRevision(item, revno, timestamp=metadata['__timestamp'])
             revision._data = StringIO.StringIO(data)
             revision._metadata = metadata
             return revision
--- a/MoinMoin/storage/backends/router.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/backends/router.py	Sat Mar 12 14:14:22 2011 +0100
@@ -347,10 +347,6 @@
 
     timestamp = property(_get_ts, _set_ts, doc="This property accesses the creation timestamp of the revision")
 
-    @property
-    def size(self):
-        return self._revision.size
-
     def __setitem__(self, key, value):
         """
         We only need to redirect this manually here because python doesn't do that
@@ -422,10 +418,6 @@
     def timestamp(self):
         return self._revision.timestamp
 
-    @property
-    def size(self):
-        return self._revision.size
-
     def __getitem__(self, key):
         return self._revision.__getitem__(key)
 
--- a/MoinMoin/storage/backends/sqla.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/backends/sqla.py	Sat Mar 12 14:14:22 2011 +0100
@@ -622,6 +622,7 @@
         Write the given amount of data.
         """
         self._data.write(data)
+        self._size = self._data.size
 
     def read(self, amount=None):
         """
@@ -650,10 +651,6 @@
     def __setitem__(self, key, value):
         NewRevision.__setitem__(self, key, value)
 
-    @property
-    def size(self):
-        return self._data.size
-
     def destroy(self):
         """
         @see: Backend.Revision.destroy.__doc__
--- a/MoinMoin/storage/serialization.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/storage/serialization.py	Sat Mar 12 14:14:22 2011 +0100
@@ -157,7 +157,7 @@
 
 class TermMatch(XMLSelectiveGenerator):
     def __init__(self, out, term):
-        self.term = term  # see MoinMoin.search.term
+        self.term = term  # see MoinMoin.storage.terms
         XMLSelectiveGenerator.__init__(self, out)
 
     def shall_serialize(self, item=None, rev=None,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/storage/terms.py	Sat Mar 12 14:14:22 2011 +0100
@@ -0,0 +1,459 @@
+# Copyright: 2008 MoinMoin:JohannesBerg
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+    MoinMoin - search expression object representation
+
+    This module defines the possible search terms for a query to the
+    storage backend. This is used, for example, to implement searching,
+    page lists etc.
+
+    Note that some backends can optimise some of the search terms, for
+    example a backend that has indexed various metadata keys can optimise
+    easy expressions containing ItemMetaDataMatch terms. This is only allowed
+    for classes documented as being 'final' which hence also means that
+    their _evaluate function may not be overridden by descendent classes.
+
+    For example, that metadata backend could test if the expression is an
+    ItemMetaDataMatch expression, and if so, simply return the appropriate
+    index; or if it is an AND() expression build the page list from the
+    index, remove the ItemMetaDataMatch instance from the AND list and match
+    the resulting expression only for pages in that list. Etc.
+
+    TODO: Should we write some generic code for picking apart expressions
+          like that?
+"""
+
+
+import re
+
+from MoinMoin.storage.error import NoSuchRevisionError
+
+# Base classes
+
+class Term(object):
+    """
+    Base class for search terms.
+    """
+    # relative cost of this search term
+    _cost = 0
+
+    def __init__(self):
+        pass
+
+    def evaluate(self, item):
+        """
+        Evaluate this term and return True or False if the
+        item identified by the parameters matches.
+
+        :param item: the item
+        """
+        assert hasattr(self, '_result')
+
+        if self._result is None:
+            self._result = self._evaluate(item)
+
+        return self._result
+
+    def _evaluate(self, item):
+        """
+        Implements the actual evaluation
+        """
+        raise NotImplementedError()
+
+    def prepare(self):
+        """
+        Prepare this search term to make it ready for testing.
+        Must be called before each outermost-level evaluate.
+        """
+        self._result = None
+
+    def copy(self):
+        """
+        Make a copy of this search term.
+        """
+        return self.__class__()
+
+class UnaryTerm(Term):
+    """
+    Base class for search terms that has a single contained
+    search term, e.g. NOT.
+    """
+    def __init__(self, term):
+        Term.__init__(self)
+        assert isinstance(term, Term)
+        self.term = term
+
+    def prepare(self):
+        Term.prepare(self)
+        self.term.prepare()
+        self._cost = self.term._cost
+
+    def __repr__(self):
+        return u'<%s(%r)>' % (self.__class__.__name__, self.term)
+
+    def copy(self):
+        return self.__class__(self.term.copy())
+
+class ListTerm(Term):
+    """
+    Base class for search terms that contain multiple other
+    search terms, e.g. AND.
+    """
+    def __init__(self, *terms):
+        Term.__init__(self)
+        for e in terms:
+            assert isinstance(e, Term)
+        self.terms = list(terms)
+
+    def prepare(self):
+        Term.prepare(self)
+        # the sum of all costs is a bit of a worst-case cost...
+        self._cost = 0
+        for e in self.terms:
+            e.prepare()
+            self._cost += e._cost
+        self.terms.sort(cmp=lambda x, y: cmp(x._cost, y._cost))
+
+    def remove(self, subterm):
+        self.terms.remove(subterm)
+
+    def add(self, subterm):
+        self.terms.append(subterm)
+
+    def __repr__(self):
+        return u'<%s(%s)>' % (self.__class__.__name__,
+                              ', '.join([repr(t) for t in self.terms]))
+
+    def copy(self):
+        terms = [t.copy() for t in self.terms]
+        return self.__class__(*terms)
+
+# Logical expression classes
+
+class AND(ListTerm):
+    """
+    AND connection between multiple terms. Final.
+    """
+    def _evaluate(self, item):
+        for e in self.terms:
+            if not e.evaluate(item):
+                return False
+        return True
+
+class OR(ListTerm):
+    """
+    OR connection between multiple terms. Final.
+    """
+    def _evaluate(self, item):
+        for e in self.terms:
+            if e.evaluate(item):
+                return True
+        return False
+
+class NOT(UnaryTerm):
+    """
+    Inversion of a single term. Final.
+    """
+    def _evaluate(self, item):
+        return not self.term.evaluate(item)
+
+class XOR(ListTerm):
+    """
+    XOR connection between multiple terms, i.e. exactly
+    one must be True. Final.
+    """
+    def _evaluate(self, item):
+        count = 0
+        for e in self.terms:
+            if e.evaluate(item):
+                count += 1
+        return count == 1
+
+class _BOOL(Term):
+    _cost = 0
+    def __init__(self, val):
+        self._val = val
+
+    def prepare(self):
+        self._result = self._val
+
+    def __repr__(self):
+        return '<%s>' % str(self._val).upper()
+
+    def copy(self):
+        return self
+
+TRUE = _BOOL(True)
+FALSE = _BOOL(False)
+
+def BOOL(b):
+    if b:
+        return TRUE
+    return FALSE
+
+# Actual Moin search terms
+
+class TextRE(Term):
+    """
+    Regular expression full text match, use as last resort.
+    """
+    _cost = 1000 # almost prohibitive
+    def __init__(self, needle_re):
+        Term.__init__(self)
+        assert hasattr(needle_re, 'search')
+        self._needle_re = needle_re
+
+    def _evaluate(self, item):
+        try:
+            rev = item.get_revision(-1)
+        except NoSuchRevisionError:
+            return False
+        data = rev.read()
+        return not (not self._needle_re.search(data))
+
+    def __repr__(self):
+        return u'<term.TextRE(...)>'
+
+    def copy(self):
+        return TextRE(self._needle_re)
+
+class Text(TextRE):
+    """
+    Full text match including middle of words and over word
+    boundaries. Final.
+    """
+    def __init__(self, needle, case_sensitive):
+        flags = re.UNICODE
+        if not case_sensitive:
+            flags = flags | re.IGNORECASE
+        _needle_re = re.compile(re.escape(needle), flags)
+        TextRE.__init__(self, _needle_re)
+        self.needle = needle
+        self.case_sensitive = case_sensitive
+
+    def __repr__(self):
+        return u'<term.Text(%s, %s)>' % (self.needle, self.case_sensitive)
+
+    def copy(self):
+        return Text(self.needle, self.case_sensitive)
+
+class Word(TextRE):
+    """
+    Full text match finding exact words. Final.
+    """
+    def __init__(self, needle, case_sensitive):
+        flags = re.UNICODE
+        if not case_sensitive:
+            flags = flags | re.IGNORECASE
+        _needle_re = re.compile('\\b' + re.escape(needle) + '\\b', flags)
+        TextRE.__init__(self, _needle_re)
+        self.needle = needle
+        self.case_sensitive = case_sensitive
+
+    def __repr__(self):
+        return u'<term.Word(%s, %s)>' % (self.needle, self.case_sensitive)
+
+    def copy(self):
+        return Word(self.needle, self.case_sensitive)
+
+class WordStart(TextRE):
+    """
+    Full text match finding the start of a word. Final.
+    """
+    def __init__(self, needle, case_sensitive):
+        flags = re.UNICODE
+        if not case_sensitive:
+            flags = flags | re.IGNORECASE
+        _needle_re = re.compile('\\b' + re.escape(needle), flags)
+        TextRE.__init__(self, _needle_re)
+        self.needle = needle
+        self.case_sensitive = case_sensitive
+
+    def __repr__(self):
+        return u'<term.WordStart(%s, %s)>' % (self.needle, self.case_sensitive)
+
+    def copy(self):
+        return WordStart(self.needle, self.case_sensitive)
+
+class WordEnd(TextRE):
+    """
+    Full text match finding the end of a word. Final.
+    """
+    def __init__(self, needle, case_sensitive):
+        flags = re.UNICODE
+        if not case_sensitive:
+            flags = flags | re.IGNORECASE
+        _needle_re = re.compile(re.escape(needle) + '\\b', flags)
+        TextRE.__init__(self, _needle_re)
+        self.needle = needle
+        self.case_sensitive = case_sensitive
+
+    def __repr__(self):
+        return u'<term.WordEnd(%s, %s)>' % (self.needle, self.case_sensitive)
+
+    def copy(self):
+        return WordEnd(self.needle, self.case_sensitive)
+
+class NameRE(Term):
+    """
+    Matches the item's name with a given regular expression.
+    """
+    _cost = 10 # one of the cheapest
+    def __init__(self, needle_re):
+        Term.__init__(self)
+        assert hasattr(needle_re, 'search')
+        self._needle_re = needle_re
+
+    def _evaluate(self, item):
+        return not (not self._needle_re.search(item.name))
+
+    def __repr__(self):
+        return u'<term.NameRE(...)>'
+
+    def copy(self):
+        return NameRE(self._needle_re)
+
+class Name(NameRE):
+    """
+    Item name match, given needle must occur in item's name. Final.
+    """
+    def __init__(self, needle, case_sensitive):
+        assert isinstance(needle, unicode)
+        flags = re.UNICODE
+        if not case_sensitive:
+            flags = flags | re.IGNORECASE
+        _needle_re = re.compile(re.escape(needle), flags)
+        NameRE.__init__(self, _needle_re)
+        self.needle = needle
+        self.case_sensitive = case_sensitive
+
+    def __repr__(self):
+        return u'<term.Name(%s, %s)>' % (self.needle, self.case_sensitive)
+
+    def copy(self):
+        return Name(self.needle, self.case_sensitive)
+
+class NameFn(Term):
+    """
+    Arbitrary item name matching function.
+    """
+    def __init__(self, fn):
+        Term.__init__(self)
+        assert callable(fn)
+        self._fn = fn
+
+    def _evaluate(self, item):
+        return not (not self._fn(item.name))
+
+    def __repr__(self):
+        return u'<term.NameFn(%r)>' % (self._fn, )
+
+    def copy(self):
+        return NameFn(self._fn)
+
+class ItemMetaDataMatch(Term):
+    """
+    Matches a metadata key/value pair of an item, requires
+    existence of the metadata key. Final.
+    """
+    _cost = 100 # fairly expensive but way cheaper than text
+    def __init__(self, key, val):
+        Term.__init__(self)
+        self.key = key
+        self.val = val
+
+    def _evaluate(self, item):
+        return self.key in item and item[self.key] == self.val
+
+    def __repr__(self):
+        return u'<%s(%s: %s)>' % (self.__class__.__name__, self.key, self.val)
+
+    def copy(self):
+        return ItemMetaDataMatch(self.key, self.val)
+
+class ItemHasMetaDataValue(Term):
+    """
+    Match when the metadata value for a given key contains the given
+    value (when the item's metadata value is a dict or list), requires
+    existence of the metadata key. Final.
+    """
+    _cost = 100 # fairly expensive but way cheaper than text
+    def __init__(self, key, val):
+        Term.__init__(self)
+        self.key = key
+        self.val = val
+
+    def _evaluate(self, item):
+        return self.key in item and self.val in item[self.key]
+
+    def __repr__(self):
+        return u'<%s(%s: %s)>' % (self.__class__.__name__, self.key, self.val)
+
+    def copy(self):
+        return ItemHasMetaDataValue(self.key, self.val)
+
+class ItemHasMetaDataKey(Term):
+    """
+    Requires existence of the metadata key. Final.
+    """
+    _cost = 90 # possibly cheaper than ItemMetaDataMatch
+    def __init__(self, key):
+        Term.__init__(self)
+        self.key = key
+
+    def _evaluate(self, item):
+        return self.key in item
+
+    def __repr__(self):
+        return u'<%s(%s)>' % (self.__class__.__name__, self.key)
+
+    def copy(self):
+        return ItemHasMetaDataKey(self.key)
+
+class LastRevisionMetaDataMatch(Term):
+    """
+    Matches a metadata key/value pair of an item, requires
+    existence of the metadata key. Final.
+    """
+    _cost = 100 # fairly expensive but way cheaper than text
+    def __init__(self, key, val):
+        Term.__init__(self)
+        self.key = key
+        self.val = val
+
+    def _evaluate(self, item):
+        try:
+            rev = item.get_revision(-1)
+        except NoSuchRevisionError:
+            return False
+        return self.key in rev and rev[self.key] == self.val
+
+    def __repr__(self):
+        return u'<%s(%s: %s)>' % (self.__class__.__name__, self.key, self.val)
+
+    def copy(self):
+        return LastRevisionMetaDataMatch(self.key, self.val)
+
+class LastRevisionHasMetaDataKey(Term):
+    """
+    Requires existence of the metadata key. Final.
+    """
+    _cost = 90 # possibly cheaper than LastRevisionMetaDataMatch
+    def __init__(self, key):
+        Term.__init__(self)
+        self.key = key
+
+    def _evaluate(self, item):
+        try:
+            rev = item.get_revision(-1)
+        except NoSuchRevisionError:
+            return False
+        return self.key in rev
+
+    def __repr__(self):
+        return u'<%s(%s)>' % (self.__class__.__name__, self.key)
+
+    def copy(self):
+        return LastRevisionHasMetaDataKey(self.key)
+
--- a/MoinMoin/templates/base.html	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/templates/base.html	Sat Mar 12 14:14:22 2011 +0100
@@ -7,7 +7,7 @@
 <head>
 {% block head %}
     {% block head_meta %}
-    <meta charset="{{ theme_supp.output_charset }}" />{# must be at the beginning #}
+    <meta charset="utf-8" />{# must be at the beginning #}
     {% if pi_refresh -%}
         {{ '<meta http-equiv="refresh" content="%d;URL=%s" />' % pi_refresh }}
     {%- endif %}
--- a/MoinMoin/templates/meta.html	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/templates/meta.html	Sat Mar 12 14:14:22 2011 +0100
@@ -7,7 +7,7 @@
 {% block headline %}
 <h1>
     {{ _("Metadata of '%(item_name)s'", item_name=item_name) }}
-    {% if rev.revno != last_rev_no %}({{ _("Revision") }} {{ rev.revno }}){% endif %}
+    {% if show_revision %}({{ _("Revision") }} {{ rev.revno }}){% endif %}
 </h1>
 {% endblock %}
 
--- a/MoinMoin/templates/show.html	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/templates/show.html	Sat Mar 12 14:14:22 2011 +0100
@@ -47,7 +47,7 @@
     {{ _("This item exists, but it has no revisions.") }}
     {% else %}
     {% block headline %}
-    {# <h1>{{ rev.item.name }} {% if rev.revno != last_rev_no %}(Revision {{ rev.revno }}){% endif %}</h1> #}
+    {# <h1>{{ rev.item.name }} {% if show_revision %}(Revision {{ rev.revno }}){% endif %}</h1> #}
     {% endblock %}
     {% block content_data %}
     {% if data_rendered %}
--- a/MoinMoin/templates/utils.html	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/templates/utils.html	Sat Mar 12 14:14:22 2011 +0100
@@ -14,3 +14,26 @@
     </span>
   {%- endif -%}
 {% endmacro %}
+
+
+{% macro table(headings, rows) %}
+<table>
+<thead>
+    <tr>
+        {% for heading in headings %}
+        <td><strong>{{ heading }}</strong></td>
+        {% endfor %}
+    </tr>
+</thead>
+<tbody>
+{% for row in rows %}
+    <tr>
+        {% for col in row %}
+        <td>{{ col }}</td>
+        {% endfor %}
+    </tr>
+{% endfor %}
+</tbody>
+</table>
+{% endmacro %}
+
--- a/MoinMoin/themes/__init__.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/themes/__init__.py	Sat Mar 12 14:14:22 2011 +0100
@@ -8,7 +8,6 @@
 """
 
 
-import os
 import urllib
 
 from flask import current_app as app
@@ -21,6 +20,7 @@
 
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin import wikiutil, user
+from MoinMoin.config import USERID, ADDRESS, HOSTNAME
 from MoinMoin.util.interwiki import split_interwiki, resolve_interwiki, join_wiki, getInterwikiHome
 
 
@@ -34,7 +34,7 @@
     try:
         return get_theme(theme_name)
     except KeyError:
-        logging.warning("theme %s was not found; using default of %s instead." % (theme_name,app.cfg.theme_default))
+        logging.warning("theme %s was not found; using default of %s instead." % (theme_name, app.cfg.theme_default))
         theme_name = app.cfg.theme_default
         return get_theme(theme_name)
 
@@ -68,8 +68,6 @@
         self.cfg = cfg
         self.user = flaskg.user
         self.storage = flaskg.storage
-        self.output_mimetype = 'text/html'  # was: page.output_mimetype
-        self.output_charset = 'utf-8'  # was: page.output_charset
         self.ui_lang = 'en' # XXX
         self.ui_dir = 'ltr' # XXX
         self.content_lang = flaskg.content_lang # XXX
@@ -321,7 +319,6 @@
 
 
 def get_editor_info(rev, external=False):
-    from MoinMoin.items import USERID, ADDRESS, HOSTNAME
     addr = rev.get(ADDRESS)
     hostname = rev.get(HOSTNAME)
     text = _('anonymous')  # link text
--- a/MoinMoin/translations/MoinMoin.pot	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/translations/MoinMoin.pot	Sat Mar 12 14:14:22 2011 +0100
@@ -1,14 +1,13 @@
-# Translations template for moin.
-# Copyright (C) 2011 Moin Core Team, see http://moinmo.in/MoinCoreTeamGroup
-# This file is distributed under the same license as the moin project.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
+# Translations template for MoinMoin.
+# Copyright: 2011 AUTHOR <EMAIL@ADDRESS>
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 #
 #, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: moin 2.0.0-alpha\n"
+"Project-Id-Version: moin 2.0.0a0\n"
 "Report-Msgid-Bugs-To: English <moin-user@lists.sourceforge.net>\n"
-"POT-Creation-Date: 2011-01-25 23:31+0100\n"
+"POT-Creation-Date: 2011-03-06 17:36+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,7 +16,7 @@
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Babel 0.9.5\n"
 
-#: MoinMoin/user.py:57
+#: MoinMoin/user.py:52
 #, python-format
 msgid ""
 "Invalid user name '%(name)s'.\n"
@@ -25,34 +24,34 @@
 "space between words. Group page name is not allowed."
 msgstr ""
 
-#: MoinMoin/user.py:61
+#: MoinMoin/user.py:58
 msgid "This user name already belongs to somebody else."
 msgstr ""
 
-#: MoinMoin/user.py:67
+#: MoinMoin/user.py:64
 #, python-format
 msgid "Password not acceptable: %(msg)s"
 msgstr ""
 
-#: MoinMoin/user.py:79
+#: MoinMoin/user.py:76
 msgid ""
 "Please provide your email address. If you lose your login information, "
 "you can get it by email."
 msgstr ""
 
-#: MoinMoin/user.py:85
+#: MoinMoin/user.py:82
 msgid "This email already belongs to somebody else."
 msgstr ""
 
-#: MoinMoin/user.py:91
+#: MoinMoin/user.py:87
 msgid "This OpenID already belongs to somebody else."
 msgstr ""
 
-#: MoinMoin/user.py:805
+#: MoinMoin/user.py:800
 msgid "<unknown>"
 msgstr ""
 
-#: MoinMoin/user.py:878
+#: MoinMoin/user.py:873
 #, python-format
 msgid ""
 "Somebody has requested to email you a password recovery link.\n"
@@ -65,22 +64,22 @@
 "\n"
 msgstr ""
 
-#: MoinMoin/user.py:881
+#: MoinMoin/user.py:876
 #, python-format
 msgid "[%(sitename)s] Your wiki password recovery link"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:60
+#: MoinMoin/apps/admin/views.py:59
 #, python-format
 msgid "User profile of %(username)s: %(email)r"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:109
+#: MoinMoin/apps/admin/views.py:108
 #, python-format
 msgid "System items upgrade failed due to the following error: %(error)s."
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:111
+#: MoinMoin/apps/admin/views.py:110
 msgid "System items have been upgraded successfully!"
 msgstr ""
 
@@ -97,6 +96,14 @@
 msgid "Upgrade system items"
 msgstr ""
 
+#: MoinMoin/apps/admin/templates/index.html:7
+msgid "Show Wiki Configuration"
+msgstr ""
+
+#: MoinMoin/apps/admin/templates/index.html:8
+msgid "Show Wiki Configuration Help"
+msgstr ""
+
 #: MoinMoin/apps/admin/templates/sysitems_upgrade.html:3
 msgid "System items upgrade"
 msgstr ""
@@ -140,231 +147,286 @@
 msgid "Mail account data"
 msgstr ""
 
-#: MoinMoin/apps/feed/views.py:66
+#: MoinMoin/apps/admin/templates/wikiconfig.html:3
+msgid "Wiki configuration"
+msgstr ""
+
+#: MoinMoin/apps/admin/templates/wikiconfig.html:5
+msgid ""
+"This table shows all settings in this wiki that do not have default "
+"values. Settings that the configuration system doesn't know about are "
+"shown in italic, those may be due to third-party extensions needing "
+"configuration or settings that were removed from Moin."
+msgstr ""
+
+#: MoinMoin/apps/admin/templates/wikiconfig.html:14
+#: MoinMoin/apps/admin/templates/wikiconfighelp.html:12
+msgid "Variable name"
+msgstr ""
+
+#: MoinMoin/apps/admin/templates/wikiconfig.html:15
+msgid "Setting"
+msgstr ""
+
+#: MoinMoin/apps/admin/templates/wikiconfighelp.html:3
+msgid "WikiConfig Help"
+msgstr ""
+
+#: MoinMoin/apps/admin/templates/wikiconfighelp.html:13
+msgid "Default"
+msgstr ""
+
+#: MoinMoin/apps/admin/templates/wikiconfighelp.html:14
+msgid "Description"
+msgstr ""
+
+#: MoinMoin/apps/feed/views.py:63
 msgid "MoinMoin feels unhappy."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:569 MoinMoin/apps/frontend/views.py:588
+#: MoinMoin/apps/frontend/views.py:501
+msgid "Refers Here"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:567 MoinMoin/themes/__init__.py:296
+msgid "Wanted Items"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:615 MoinMoin/themes/__init__.py:297
+msgid "Orphaned Items"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:651 MoinMoin/apps/frontend/views.py:670
 #, python-format
 msgid "You must login to use this action: %(action)s."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:572
+#: MoinMoin/apps/frontend/views.py:654
 msgid "A quicklink to this page could not be added for you."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:575
+#: MoinMoin/apps/frontend/views.py:657
 msgid "Your quicklink to this page could not be removed."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:590
+#: MoinMoin/apps/frontend/views.py:672
 msgid "You are not allowed to subscribe to an item you may not read."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:592
+#: MoinMoin/apps/frontend/views.py:674
 msgid "This wiki is not enabled for mail processing."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:594
+#: MoinMoin/apps/frontend/views.py:676
 msgid "Add your email address in your user settings to use subscriptions."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:598
+#: MoinMoin/apps/frontend/views.py:680
 msgid "Can't remove regular expression subscription!"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:599
+#: MoinMoin/apps/frontend/views.py:681
 msgid "Edit the subscription regular expressions in your settings."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:603
+#: MoinMoin/apps/frontend/views.py:685
 msgid "You could not get subscribed to this item."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:612 MoinMoin/apps/frontend/views.py:758
-#: MoinMoin/apps/frontend/views.py:917
+#: MoinMoin/apps/frontend/views.py:694 MoinMoin/apps/frontend/views.py:898
+#: MoinMoin/apps/frontend/views.py:1058
 msgid "The passwords do not match."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:629 MoinMoin/apps/frontend/views.py:709
-#: MoinMoin/apps/frontend/views.py:776 MoinMoin/apps/frontend/views.py:851
-#: MoinMoin/apps/frontend/views.py:983
+#: MoinMoin/apps/frontend/views.py:710 MoinMoin/apps/frontend/views.py:726
+#: MoinMoin/apps/frontend/views.py:849 MoinMoin/apps/frontend/views.py:916
+#: MoinMoin/apps/frontend/views.py:991 MoinMoin/apps/frontend/views.py:1124
 #: MoinMoin/templates/global_history.html:16 MoinMoin/templates/history.html:10
 msgid "Name"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:630 MoinMoin/apps/frontend/views.py:631
-#: MoinMoin/apps/frontend/views.py:852
+#: MoinMoin/apps/frontend/views.py:711 MoinMoin/apps/frontend/views.py:712
+#: MoinMoin/apps/frontend/views.py:727 MoinMoin/apps/frontend/views.py:728
+#: MoinMoin/apps/frontend/views.py:992
 msgid "Password"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:632 MoinMoin/apps/frontend/views.py:710
-#: MoinMoin/apps/frontend/views.py:949
+#: MoinMoin/apps/frontend/views.py:713 MoinMoin/apps/frontend/views.py:730
+#: MoinMoin/apps/frontend/views.py:850 MoinMoin/apps/frontend/views.py:1090
 msgid "E-Mail"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:633 MoinMoin/apps/frontend/views.py:853
-#: MoinMoin/apps/frontend/views.py:985
+#: MoinMoin/apps/frontend/views.py:714 MoinMoin/apps/frontend/views.py:731
+#: MoinMoin/apps/frontend/views.py:993 MoinMoin/apps/frontend/views.py:1126
 msgid "OpenID"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:634
+#: MoinMoin/apps/frontend/views.py:715
+#: MoinMoin/templates/openid_register.html:21
 msgid "Register"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:681
+#: MoinMoin/apps/frontend/views.py:794 MoinMoin/apps/frontend/views.py:821
 msgid "Account created, please log in now."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:694
+#: MoinMoin/apps/frontend/views.py:834
 msgid "Your user name or your email address is needed."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:711
+#: MoinMoin/apps/frontend/views.py:851
 msgid "Recover password"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:746
+#: MoinMoin/apps/frontend/views.py:886
 msgid "If this account exists, you will be notified."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:759 MoinMoin/apps/frontend/views.py:919
+#: MoinMoin/apps/frontend/views.py:899 MoinMoin/apps/frontend/views.py:1060
 msgid "New password is unacceptable, encoding trouble."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:777
+#: MoinMoin/apps/frontend/views.py:917
 msgid "Recovery token"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:778 MoinMoin/apps/frontend/views.py:941
+#: MoinMoin/apps/frontend/views.py:918 MoinMoin/apps/frontend/views.py:1082
 msgid "New password"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:779 MoinMoin/apps/frontend/views.py:942
+#: MoinMoin/apps/frontend/views.py:919 MoinMoin/apps/frontend/views.py:1083
 msgid "New password (repeat)"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:780 MoinMoin/apps/frontend/views.py:943
+#: MoinMoin/apps/frontend/views.py:920 MoinMoin/apps/frontend/views.py:1084
 #: MoinMoin/templates/usersettings.html:9
 msgid "Change password"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:807
+#: MoinMoin/apps/frontend/views.py:947
 msgid "Your password has been changed, you can log in now."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:809
+#: MoinMoin/apps/frontend/views.py:949
 msgid "Your token is invalid!"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:823
+#: MoinMoin/apps/frontend/views.py:963
 msgid "Either your username or password was invalid."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:824
+#: MoinMoin/apps/frontend/views.py:964
 msgid "Failed to authenticate with this OpenID."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:855
-msgid "Log in"
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:907
+#: MoinMoin/apps/frontend/views.py:1048
 msgid "You are now logged out."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:918
+#: MoinMoin/apps/frontend/views.py:1059
 msgid "The current password was wrong."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:940
+#: MoinMoin/apps/frontend/views.py:1081
 msgid "Current Password"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:950 MoinMoin/apps/frontend/views.py:956
-#: MoinMoin/apps/frontend/views.py:971 MoinMoin/apps/frontend/views.py:994
-#: MoinMoin/apps/frontend/views.py:1004 MoinMoin/templates/modify_binary.html:3
+#: MoinMoin/apps/frontend/views.py:1091 MoinMoin/apps/frontend/views.py:1097
+#: MoinMoin/apps/frontend/views.py:1112 MoinMoin/apps/frontend/views.py:1135
+#: MoinMoin/apps/frontend/views.py:1145 MoinMoin/templates/modify_binary.html:4
 msgid "Save"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:967
+#: MoinMoin/apps/frontend/views.py:1108
 msgid "Publish my email (not my wiki homepage) in author info"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:968
+#: MoinMoin/apps/frontend/views.py:1109
 msgid "Open editor on double click"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:969
+#: MoinMoin/apps/frontend/views.py:1110
 msgid "Show comment sections"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:970
+#: MoinMoin/apps/frontend/views.py:1111
 msgid "Disable this account forever"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:984
+#: MoinMoin/apps/frontend/views.py:1125
 msgid "Alias-Name"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:988
+#: MoinMoin/apps/frontend/views.py:1129
 msgid "Timezone"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:993
+#: MoinMoin/apps/frontend/views.py:1134
 msgid "Locale"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1001
+#: MoinMoin/apps/frontend/views.py:1142
 msgid "Theme name"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1002
+#: MoinMoin/apps/frontend/views.py:1143
 msgid "User CSS URL"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1003
+#: MoinMoin/apps/frontend/views.py:1144
 msgid "Editor size"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1040
+#: MoinMoin/apps/frontend/views.py:1179
 msgid "Your password has been changed."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1072
+#: MoinMoin/apps/frontend/views.py:1184
+msgid "This openid is already in use."
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:1188
+msgid "This username is already in use."
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:1194
+msgid "This email is already in use"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:1233
 msgid "You must log in to use bookmarks."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1230 MoinMoin/themes/__init__.py:348
+#: MoinMoin/apps/frontend/views.py:1387 MoinMoin/themes/__init__.py:302
 msgid "Items with similar names"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1430
+#: MoinMoin/apps/frontend/views.py:1588
 msgid "All tags in this wiki"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1442
+#: MoinMoin/apps/frontend/views.py:1600
 #, python-format
 msgid "Items tagged with %(tag)s"
 msgstr ""
 
-#: MoinMoin/auth/__init__.py:237 MoinMoin/auth/ldap_login.py:129
+#: MoinMoin/auth/__init__.py:242 MoinMoin/auth/ldap_login.py:129
 msgid "Missing password. Please enter user name and password."
 msgstr ""
 
-#: MoinMoin/auth/__init__.py:245 MoinMoin/auth/ldap_login.py:199
+#: MoinMoin/auth/__init__.py:250 MoinMoin/auth/ldap_login.py:199
 #: MoinMoin/auth/ldap_login.py:245
 msgid "Invalid username or password."
 msgstr ""
 
-#: MoinMoin/auth/__init__.py:249
+#: MoinMoin/auth/__init__.py:254
 #, python-format
 msgid ""
 "If you do not have an account, <a href=\"%(register_url)s\">you can "
 "create one now</a>. "
 msgstr ""
 
-#: MoinMoin/auth/__init__.py:251
+#: MoinMoin/auth/__init__.py:256
 #, python-format
 msgid "<a href=\"%(recover_url)s\">Forgot your password?</a>"
 msgstr ""
@@ -378,57 +440,53 @@
 msgid "LDAP server %(server)s failed."
 msgstr ""
 
-#: MoinMoin/auth/openidrp.py:60
+#: MoinMoin/auth/openidrp.py:63
 msgid "OpenID Error"
 msgstr ""
 
-#: MoinMoin/auth/openidrp.py:67
+#: MoinMoin/auth/openidrp.py:70
 msgid "OpenID verification cancelled."
 msgstr ""
 
-#: MoinMoin/auth/openidrp.py:93
-msgid "There is no user with this OpenID."
-msgstr ""
-
-#: MoinMoin/auth/openidrp.py:96
+#: MoinMoin/auth/openidrp.py:104
 msgid "This OpenID provider is not trusted."
 msgstr ""
 
-#: MoinMoin/auth/openidrp.py:101
+#: MoinMoin/auth/openidrp.py:109
 msgid "OpenID failure."
 msgstr ""
 
-#: MoinMoin/auth/openidrp.py:135
+#: MoinMoin/auth/openidrp.py:143
 msgid "Failed to resolve OpenID."
 msgstr ""
 
-#: MoinMoin/auth/openidrp.py:137
+#: MoinMoin/auth/openidrp.py:145
 msgid "OpenID discovery failure, not a valid OpenID."
 msgstr ""
 
-#: MoinMoin/auth/openidrp.py:141
+#: MoinMoin/auth/openidrp.py:149
 msgid "No OpenID service at this URL."
 msgstr ""
 
-#: MoinMoin/config/default.py:330
+#: MoinMoin/config/default.py:258
 msgid "Password is too short."
 msgstr ""
 
-#: MoinMoin/config/default.py:332
+#: MoinMoin/config/default.py:260
 msgid "Password has not enough different characters."
 msgstr ""
 
-#: MoinMoin/config/default.py:338
+#: MoinMoin/config/default.py:266
 msgid ""
 "Password is too easy to guess (password contains name or name contains "
 "password)."
 msgstr ""
 
-#: MoinMoin/config/default.py:347
+#: MoinMoin/config/default.py:275
 msgid "Password is too easy to guess (keyboard sequence)."
 msgstr ""
 
-#: MoinMoin/converter/html_out.py:599
+#: MoinMoin/converter/html_out.py:595
 msgid "Contents"
 msgstr ""
 
@@ -437,7 +495,15 @@
 msgid "<<%(macro_name)s: execution failed [%(error_msg)s] (see also the log)>>"
 msgstr ""
 
-#: MoinMoin/converter/nonexistent_in.py:29
+#: MoinMoin/converter/moinwiki_in.py:1005
+msgid "Error:"
+msgstr ""
+
+#: MoinMoin/converter/moinwiki_in.py:1006
+msgid "is invalid within"
+msgstr ""
+
+#: MoinMoin/converter/nonexistent_in.py:30
 #, python-format
 msgid "%(item_name)s does not exist. Create it?"
 msgstr ""
@@ -457,125 +523,49 @@
 msgid "Impossible to convert the data to the mimetype: %(mimetype)s"
 msgstr ""
 
-#: MoinMoin/items/__init__.py:1248 MoinMoin/items/__init__.py:1319
+#: MoinMoin/items/__init__.py:1265 MoinMoin/items/__init__.py:1341
 #, python-format
 msgid "Edit drawing %(filename)s (opens in new window)"
 msgstr ""
 
-#: MoinMoin/items/__init__.py:1263 MoinMoin/items/__init__.py:1336
+#: MoinMoin/items/__init__.py:1280 MoinMoin/items/__init__.py:1358
 #, python-format
 msgid "Clickable drawing: %(filename)s"
 msgstr ""
 
-#: MoinMoin/macro/GoTo.py:29
-msgid "Go To Item"
-msgstr ""
-
-#: MoinMoin/macro/HighlighterList.py:25
+#: MoinMoin/macro/HighlighterList.py:24
 msgid "Lexer description"
 msgstr ""
 
-#: MoinMoin/macro/HighlighterList.py:26
+#: MoinMoin/macro/HighlighterList.py:25
 msgid "Lexer names"
 msgstr ""
 
-#: MoinMoin/macro/HighlighterList.py:27
+#: MoinMoin/macro/HighlighterList.py:26
 msgid "File patterns"
 msgstr ""
 
-#: MoinMoin/macro/HighlighterList.py:28
+#: MoinMoin/macro/HighlighterList.py:27
 msgid "Mimetypes"
 msgstr ""
 
-#: MoinMoin/macro/WikiConfig.py:42
-msgid "Wiki configuration"
-msgstr ""
-
-#: MoinMoin/macro/WikiConfig.py:44
-msgid ""
-"This table shows all settings in this wiki that do not have default "
-"values. Settings that the configuration system doesn't know about are "
-"shown in italic, those may be due to third-party extensions needing "
-"configuration or settings that were removed from Moin."
-msgstr ""
-
-#: MoinMoin/macro/WikiConfig.py:58 MoinMoin/macro/WikiConfigHelp.py:42
-msgid "Variable name"
-msgstr ""
-
-#: MoinMoin/macro/WikiConfig.py:58
-msgid "Setting"
-msgstr ""
-
-#: MoinMoin/macro/WikiConfigHelp.py:42
-msgid "Default"
-msgstr ""
-
-#: MoinMoin/macro/WikiConfigHelp.py:42
-msgid "Description"
-msgstr ""
-
-#: MoinMoin/mail/sendmail.py:84
+#: MoinMoin/mail/sendmail.py:86
 msgid "No recipients, nothing to do"
 msgstr ""
 
-#: MoinMoin/mail/sendmail.py:160
+#: MoinMoin/mail/sendmail.py:162
 #, python-format
 msgid "Connection to mailserver '%(server)s' failed: %(reason)s"
 msgstr ""
 
-#: MoinMoin/mail/sendmail.py:174
+#: MoinMoin/mail/sendmail.py:176
 msgid "Mail not sent"
 msgstr ""
 
-#: MoinMoin/mail/sendmail.py:177
+#: MoinMoin/mail/sendmail.py:179
 msgid "Mail sent successfully"
 msgstr ""
 
-#: MoinMoin/search/results.py:286
-#, python-format
-msgid ""
-"Results %(bs)s%(hitsFrom)d - %(hitsTo)d%(be)s of %(aboutHits)s "
-"%(bs)s%(hits)d%(be)s results out of about %(items)d items."
-msgstr ""
-
-#: MoinMoin/search/results.py:299
-msgid "seconds"
-msgstr ""
-
-#: MoinMoin/search/results.py:694
-msgid "Previous"
-msgstr ""
-
-#: MoinMoin/search/results.py:715
-msgid "Next"
-msgstr ""
-
-#: MoinMoin/search/results.py:747 MoinMoin/search/results.py:749
-msgid "rev"
-msgstr ""
-
-#: MoinMoin/search/results.py:747
-msgid "current"
-msgstr ""
-
-#: MoinMoin/search/results.py:750
-#, python-format
-msgid "last modified: %s"
-msgstr ""
-
-#: MoinMoin/search/results.py:809
-msgid "match"
-msgstr ""
-
-#: MoinMoin/search/results.py:809
-msgid "matches"
-msgstr ""
-
-#: MoinMoin/search/Xapian/search.py:65
-msgid "about"
-msgstr ""
-
 #: MoinMoin/security/textcha.py:163
 msgid "The entered TextCha was incorrect."
 msgstr ""
@@ -588,25 +578,25 @@
 msgid "TextCha"
 msgstr ""
 
-#: MoinMoin/storage/error.py:41
+#: MoinMoin/storage/error.py:42
 msgid "Permission denied!"
 msgstr ""
 
-#: MoinMoin/storage/error.py:43
+#: MoinMoin/storage/error.py:44
 msgid "You"
 msgstr ""
 
-#: MoinMoin/storage/error.py:45
+#: MoinMoin/storage/error.py:46
 #, python-format
 msgid "%(username)s may not %(priv)s '%(item)s'."
 msgstr ""
 
-#: MoinMoin/templates/base.html:70 MoinMoin/templates/layout.html:24
+#: MoinMoin/templates/base.html:63 MoinMoin/templates/layout.html:24
 msgid "Search"
 msgstr ""
 
-#: MoinMoin/templates/base.html:76 MoinMoin/templates/editbar.html:87
-msgid "More Actions:"
+#: MoinMoin/templates/base.html:69
+msgid "More"
 msgstr ""
 
 #: MoinMoin/templates/copy.html:3
@@ -614,47 +604,47 @@
 msgid "Copy '%(item_name)s'"
 msgstr ""
 
-#: MoinMoin/templates/copy.html:6 MoinMoin/templates/rename.html:7
+#: MoinMoin/templates/copy.html:7 MoinMoin/templates/rename.html:8
 msgid "Target"
 msgstr ""
 
-#: MoinMoin/templates/copy.html:9 MoinMoin/templates/delete.html:7
-#: MoinMoin/templates/destroy.html:13 MoinMoin/templates/global_history.html:20
-#: MoinMoin/templates/history.html:17 MoinMoin/templates/rename.html:10
-#: MoinMoin/templates/revert.html:6
+#: MoinMoin/templates/copy.html:10 MoinMoin/templates/delete.html:8
+#: MoinMoin/templates/destroy.html:14 MoinMoin/templates/global_history.html:20
+#: MoinMoin/templates/history.html:17 MoinMoin/templates/rename.html:11
+#: MoinMoin/templates/revert.html:8
 msgid "Comment"
 msgstr ""
 
-#: MoinMoin/templates/copy.html:12
+#: MoinMoin/templates/copy.html:14
 msgid "Copy"
 msgstr ""
 
-#: MoinMoin/templates/copy.html:13 MoinMoin/templates/delete.html:11
-#: MoinMoin/templates/destroy.html:17 MoinMoin/templates/modify_binary.html:4
-#: MoinMoin/templates/rename.html:14 MoinMoin/templates/revert.html:10
+#: MoinMoin/templates/copy.html:15 MoinMoin/templates/delete.html:12
+#: MoinMoin/templates/destroy.html:20 MoinMoin/templates/modify_binary.html:5
+#: MoinMoin/templates/rename.html:15 MoinMoin/templates/revert.html:14
 msgid "Cancel"
 msgstr ""
 
-#: MoinMoin/templates/delete.html:4
+#: MoinMoin/templates/delete.html:5
 #, python-format
 msgid "Delete '%(item_name)s'"
 msgstr ""
 
-#: MoinMoin/templates/delete.html:10
+#: MoinMoin/templates/delete.html:11
 msgid "Delete"
 msgstr ""
 
-#: MoinMoin/templates/destroy.html:4
+#: MoinMoin/templates/destroy.html:5
 #, python-format
 msgid "DESTROY COMPLETE item '%(item_name)s'"
 msgstr ""
 
-#: MoinMoin/templates/destroy.html:8
+#: MoinMoin/templates/destroy.html:9
 #, python-format
 msgid "DESTROY REVISION '%(item_name)s' (rev %(rev_no)d)"
 msgstr ""
 
-#: MoinMoin/templates/destroy.html:16
+#: MoinMoin/templates/destroy.html:19
 msgid "DESTROY"
 msgstr ""
 
@@ -696,63 +686,67 @@
 msgid "Line"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:4
+#: MoinMoin/templates/editbar.html:5
 msgid "Show"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:9
+#: MoinMoin/templates/editbar.html:10
 msgid "Highlight"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:14
+#: MoinMoin/templates/editbar.html:15
 msgid "Meta"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:20 MoinMoin/templates/show.html:9
+#: MoinMoin/templates/editbar.html:21 MoinMoin/templates/show.html:9
 msgid "Modify"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:22
+#: MoinMoin/templates/editbar.html:23
 msgid "Immutable Item"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:28
+#: MoinMoin/templates/editbar.html:29
 msgid "Download"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:33
+#: MoinMoin/templates/editbar.html:34
 msgid "History"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:41
+#: MoinMoin/templates/editbar.html:42
 msgid "Remove Link"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:43
+#: MoinMoin/templates/editbar.html:44
 msgid "Add Link"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:53
+#: MoinMoin/templates/editbar.html:54
 msgid "Unsubscribe"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:55
+#: MoinMoin/templates/editbar.html:56
 msgid "Subscribe"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:62
+#: MoinMoin/templates/editbar.html:63
 msgid "Comments"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:67
+#: MoinMoin/templates/editbar.html:68
 msgid "Index"
 msgstr ""
 
-#: MoinMoin/templates/editbar.html:93
+#: MoinMoin/templates/editbar.html:88
+msgid "More Actions:"
+msgstr ""
+
+#: MoinMoin/templates/editbar.html:94
 msgid "Do"
 msgstr ""
 
-#: MoinMoin/templates/global_history.html:10 MoinMoin/themes/__init__.py:339
+#: MoinMoin/templates/global_history.html:10 MoinMoin/themes/__init__.py:293
 msgid "Global History"
 msgstr ""
 
@@ -839,12 +833,20 @@
 msgid "Logout"
 msgstr ""
 
-#: MoinMoin/templates/layout.html:56 MoinMoin/templates/login.html:5
+#: MoinMoin/templates/layout.html:58
 msgid "Login"
 msgstr ""
 
-#: MoinMoin/templates/login.html:17
-msgid "Login with your OpenID:"
+#: MoinMoin/templates/login.html:9
+msgid "Moin login"
+msgstr ""
+
+#: MoinMoin/templates/login.html:21 MoinMoin/templates/login.html:35
+msgid "Log in"
+msgstr ""
+
+#: MoinMoin/templates/login.html:28
+msgid "OpenID login"
 msgstr ""
 
 #: MoinMoin/templates/lostpass.html:5
@@ -879,11 +881,11 @@
 msgid "Modifying %(item_name)s"
 msgstr ""
 
-#: MoinMoin/templates/modify_applet.html:11
+#: MoinMoin/templates/modify_applet.html:12
 msgid "Upload file:"
 msgstr ""
 
-#: MoinMoin/templates/modify_binary.html:6
+#: MoinMoin/templates/modify_binary.html:8
 msgid "Comment:"
 msgstr ""
 
@@ -909,29 +911,29 @@
 "select the type of the item you want to create."
 msgstr ""
 
+#: MoinMoin/templates/openid_register.html:5 MoinMoin/templates/register.html:5
+msgid "Create Account"
+msgstr ""
+
 #: MoinMoin/templates/recoverpass.html:5
 msgid "Recover Password"
 msgstr ""
 
-#: MoinMoin/templates/register.html:5
-msgid "Create Account"
-msgstr ""
-
-#: MoinMoin/templates/rename.html:3
+#: MoinMoin/templates/rename.html:4
 #, python-format
 msgid "Rename '%(item_name)s'"
 msgstr ""
 
-#: MoinMoin/templates/rename.html:13
+#: MoinMoin/templates/rename.html:14
 msgid "Rename"
 msgstr ""
 
-#: MoinMoin/templates/revert.html:3
+#: MoinMoin/templates/revert.html:4
 #, python-format
-msgid "Revert '%(item_name)s'"
+msgid "Revert '%(item_name)s' (rev %(rev_no)d)"
 msgstr ""
 
-#: MoinMoin/templates/revert.html:9
+#: MoinMoin/templates/revert.html:13
 msgid "Revert"
 msgstr ""
 
@@ -1006,126 +1008,118 @@
 msgid "Total: %(total)s"
 msgstr ""
 
-#: MoinMoin/themes/__init__.py:46
+#: MoinMoin/themes/__init__.py:45
 msgid "Access denied"
 msgstr ""
 
-#: MoinMoin/themes/__init__.py:47
+#: MoinMoin/themes/__init__.py:46
 msgid "You are not allowed to access this resource."
 msgstr ""
 
-#: MoinMoin/themes/__init__.py:340
+#: MoinMoin/themes/__init__.py:294
 msgid "Global Items Index"
 msgstr ""
 
-#: MoinMoin/themes/__init__.py:341
+#: MoinMoin/themes/__init__.py:295
 msgid "Global Tags Index"
 msgstr ""
 
-#: MoinMoin/themes/__init__.py:342
-msgid "Wanted Items"
-msgstr ""
-
-#: MoinMoin/themes/__init__.py:343
-msgid "Orphaned Items"
-msgstr ""
-
-#: MoinMoin/themes/__init__.py:345 MoinMoin/themes/__init__.py:349
+#: MoinMoin/themes/__init__.py:299 MoinMoin/themes/__init__.py:303
 msgid "-----------------------------------"
 msgstr ""
 
-#: MoinMoin/themes/__init__.py:346
+#: MoinMoin/themes/__init__.py:300
 msgid "What refers here?"
 msgstr ""
 
-#: MoinMoin/themes/__init__.py:347
+#: MoinMoin/themes/__init__.py:301
 msgid "Local Site Map"
 msgstr ""
 
-#: MoinMoin/themes/__init__.py:350
+#: MoinMoin/themes/__init__.py:304
 msgid "Copy Item"
 msgstr ""
 
-#: MoinMoin/themes/__init__.py:351
+#: MoinMoin/themes/__init__.py:305
 msgid "Rename Item"
 msgstr ""
 
-#: MoinMoin/themes/__init__.py:352
+#: MoinMoin/themes/__init__.py:306
 msgid "Delete Item"
 msgstr ""
 
-#: MoinMoin/themes/__init__.py:353
+#: MoinMoin/themes/__init__.py:307
 msgid "Destroy Item"
 msgstr ""
 
-#: MoinMoin/themes/__init__.py:364
+#: MoinMoin/themes/__init__.py:318
 msgid "anonymous"
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:311
+#: MoinMoin/util/paramparser.py:316
 #, python-format
 msgid "Argument \"%(name)s\" must be a boolean value, not \"%(value)s\""
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:314
+#: MoinMoin/util/paramparser.py:319
 #, python-format
 msgid "Argument must be a boolean value, not \"%(value)s\""
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:340
+#: MoinMoin/util/paramparser.py:345
 #, python-format
 msgid "Argument \"%(name)s\" must be an integer value, not \"%(value)s\""
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:343
+#: MoinMoin/util/paramparser.py:348
 #, python-format
 msgid "Argument must be an integer value, not \"%(value)s\""
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:368
+#: MoinMoin/util/paramparser.py:373
 #, python-format
 msgid "Argument \"%(name)s\" must be a floating point value, not \"%(value)s\""
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:371
+#: MoinMoin/util/paramparser.py:376
 #, python-format
 msgid "Argument must be a floating point value, not \"%(value)s\""
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:398
+#: MoinMoin/util/paramparser.py:403
 #, python-format
 msgid "Argument \"%(name)s\" must be a complex value, not \"%(value)s\""
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:401
+#: MoinMoin/util/paramparser.py:406
 #, python-format
 msgid "Argument must be a complex value, not \"%(value)s\""
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:455
+#: MoinMoin/util/paramparser.py:460
 #, python-format
 msgid "Argument \"%(name)s\" must be one of \"%(choices)s\", not \"%(value)s\""
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:460
+#: MoinMoin/util/paramparser.py:465
 #, python-format
 msgid "Argument must be one of \"%(choices)s\", not \"%(value)s\""
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:685
+#: MoinMoin/util/paramparser.py:690
 msgid "Too many arguments"
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:690
+#: MoinMoin/util/paramparser.py:695
 msgid "Cannot have arguments without name following named arguments"
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:706
+#: MoinMoin/util/paramparser.py:711
 #, python-format
 msgid "Argument \"%(name)s\" is required"
 msgstr ""
 
-#: MoinMoin/util/paramparser.py:718
+#: MoinMoin/util/paramparser.py:723
 #, python-format
 msgid "No argument named \"%(name)s\""
 msgstr ""
--- a/MoinMoin/translations/de/LC_MESSAGES/messages.po	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/translations/de/LC_MESSAGES/messages.po	Sat Mar 12 14:14:22 2011 +0100
@@ -1,16 +1,15 @@
 # German translations for MoinMoin.
-# Copyright (C) 2010 ?
-# This file is distributed under the same license as the MoinMoin project
-# (GNU GPL).
-# Gerhard J. <gerhard.jungwirth3@gmail.com>, 2010.
-# Julian Brost <julian.brost@googlemail.com>, 2011.
+# Copyright: 2010 Gerhard J. <gerhard.jungwirth3@gmail.com>
+# Copyright: 2011 Julian Brost <julian.brost@googlemail.com>
+# Copyright: 2011 MoinMoin:ThomasWaldmann
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: MoinMoin 2.0\n"
-"Report-Msgid-Bugs-To: English <moin-user@lists.sourceforge.net>\n"
+"Report-Msgid-Bugs-To: German <moin-user@lists.sourceforge.net>\n"
 "POT-Creation-Date: 2010-12-28 14:16+0100\n"
-"PO-Revision-Date: 2011-01-25 21:31+0100\n"
+"PO-Revision-Date: 2011-03-06 17:49+0100\n"
 "Last-Translator: Julian Brost <julian.brost@googlemail.com>\n"
 "Language-Team: German <moin-user@lists.sourceforge.net>\n"
 "Plural-Forms: nplurals=2; plural=(n != 1)\n"
@@ -19,7 +18,7 @@
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Babel 0.9.5\n"
 
-#: MoinMoin/user.py:57
+#: MoinMoin/user.py:52
 #, python-format
 msgid ""
 "Invalid user name '%(name)s'.\n"
@@ -32,16 +31,16 @@
 "vorkommen dürfen. Der Benutzername darf auch kein existierender "
 "Gruppenname sein."
 
-#: MoinMoin/user.py:61
+#: MoinMoin/user.py:58
 msgid "This user name already belongs to somebody else."
 msgstr "Dieser Benutzername ist bereits vergeben."
 
-#: MoinMoin/user.py:67
+#: MoinMoin/user.py:64
 #, python-format
 msgid "Password not acceptable: %(msg)s"
 msgstr "Dieses Passwort ist nicht ausreichend: %(msg)s"
 
-#: MoinMoin/user.py:79
+#: MoinMoin/user.py:76
 msgid ""
 "Please provide your email address. If you lose your login information, "
 "you can get it by email."
@@ -49,19 +48,19 @@
 "Bitte geben Sie Ihre E-Mail-Adresse an. Wenn Sie Ihre Logindaten "
 "vergessen, können Sie Ihnen per E-Mail zugesendet werden."
 
-#: MoinMoin/user.py:85
+#: MoinMoin/user.py:82
 msgid "This email already belongs to somebody else."
 msgstr "Diese E-Mail-Adresse wird bereits verwendet."
 
-#: MoinMoin/user.py:91
+#: MoinMoin/user.py:87
 msgid "This OpenID already belongs to somebody else."
 msgstr "Diese OpenID gehört bereits zu jemand anderem."
 
-#: MoinMoin/user.py:805
+#: MoinMoin/user.py:800
 msgid "<unknown>"
 msgstr "<unbekannt>"
 
-#: MoinMoin/user.py:878
+#: MoinMoin/user.py:873
 #, python-format
 msgid ""
 "Somebody has requested to email you a password recovery link.\n"
@@ -85,24 +84,24 @@
 "ignorieren.\n"
 "\n"
 
-#: MoinMoin/user.py:881
+#: MoinMoin/user.py:876
 #, python-format
 msgid "[%(sitename)s] Your wiki password recovery link"
 msgstr "[%(sitename)s] Passwort vergessen"
 
-#: MoinMoin/apps/admin/views.py:60
+#: MoinMoin/apps/admin/views.py:59
 #, python-format
 msgid "User profile of %(username)s: %(email)r"
 msgstr "Profil von %(username)s: %(email)r"
 
-#: MoinMoin/apps/admin/views.py:109
+#: MoinMoin/apps/admin/views.py:108
 #, python-format
 msgid "System items upgrade failed due to the following error: %(error)s."
 msgstr ""
 "Das Upgrade der Systemitems ist aus folgendem Grund fehlgeschlagen: "
 "%(error)s."
 
-#: MoinMoin/apps/admin/views.py:111
+#: MoinMoin/apps/admin/views.py:110
 msgid "System items have been upgraded successfully!"
 msgstr "Das Upgrade der Systemitems war erfolgreich!"
 
@@ -119,6 +118,14 @@
 msgid "Upgrade system items"
 msgstr "Upgrade System-Items"
 
+#: MoinMoin/apps/admin/templates/index.html:7
+msgid "Show Wiki Configuration"
+msgstr "Wiki-Konfiguration anzeigen"
+
+#: MoinMoin/apps/admin/templates/index.html:8
+msgid "Show Wiki Configuration Help"
+msgstr "Wiki-Konfigurations-Hilfe anzeigen"
+
 #: MoinMoin/apps/admin/templates/sysitems_upgrade.html:3
 msgid "System items upgrade"
 msgstr "System-Items upgraden"
@@ -128,7 +135,8 @@
 "You can upgrade your system items by uploading an xml file with new items"
 " below."
 msgstr ""
-"Sie können Ihre System-Items upgraden, indem Sie eine XML-Datei mit neuen Items hochladen."
+"Sie können Ihre System-Items upgraden, indem Sie eine XML-Datei mit neuen"
+" Items hochladen."
 
 #: MoinMoin/apps/admin/templates/userbrowser.html:5
 msgid "User name"
@@ -163,234 +171,293 @@
 msgid "Mail account data"
 msgstr "Account-Daten zusenden"
 
-#: MoinMoin/apps/feed/views.py:66
+#: MoinMoin/apps/admin/templates/wikiconfig.html:3
+msgid "Wiki configuration"
+msgstr "Wiki-Konfiguration"
+
+#: MoinMoin/apps/admin/templates/wikiconfig.html:5
+msgid ""
+"This table shows all settings in this wiki that do not have default "
+"values. Settings that the configuration system doesn't know about are "
+"shown in italic, those may be due to third-party extensions needing "
+"configuration or settings that were removed from Moin."
+msgstr ""
+"Diese Tabelle zeigt alle Einstellungen des Wikis, die nicht dem "
+"Standardwert entsprechen. Dem Konfigurationssystem unbekannte "
+"Einstellungen sind kursiv dargestellt. Diese sind möglicherweise für "
+"Erweiterungen von Drittanbietern oder wurden aus Moin entfernt."
+
+#: MoinMoin/apps/admin/templates/wikiconfig.html:14
+#: MoinMoin/apps/admin/templates/wikiconfighelp.html:12
+msgid "Variable name"
+msgstr "Variablenname"
+
+#: MoinMoin/apps/admin/templates/wikiconfig.html:15
+msgid "Setting"
+msgstr "Einstellung"
+
+#: MoinMoin/apps/admin/templates/wikiconfighelp.html:3
+msgid "WikiConfig Help"
+msgstr "Hilfe zur Wiki-Konfiguration"
+
+#: MoinMoin/apps/admin/templates/wikiconfighelp.html:13
+msgid "Default"
+msgstr "Standardeinstellung"
+
+#: MoinMoin/apps/admin/templates/wikiconfighelp.html:14
+msgid "Description"
+msgstr "Beschreibung"
+
+#: MoinMoin/apps/feed/views.py:63
 msgid "MoinMoin feels unhappy."
 msgstr "MoinMoin ist unglücklich."
 
-#: MoinMoin/apps/frontend/views.py:569 MoinMoin/apps/frontend/views.py:588
+#: MoinMoin/apps/frontend/views.py:501
+msgid "Refers Here"
+msgstr "Hierauf linkt"
+
+#: MoinMoin/apps/frontend/views.py:567 MoinMoin/themes/__init__.py:296
+msgid "Wanted Items"
+msgstr "Fehlende Items"
+
+#: MoinMoin/apps/frontend/views.py:615 MoinMoin/themes/__init__.py:297
+msgid "Orphaned Items"
+msgstr "Verwaiste Items"
+
+#: MoinMoin/apps/frontend/views.py:651 MoinMoin/apps/frontend/views.py:670
 #, python-format
 msgid "You must login to use this action: %(action)s."
 msgstr "Sie müssen sich hierzu anmelden: %(action)s."
 
-#: MoinMoin/apps/frontend/views.py:572
+#: MoinMoin/apps/frontend/views.py:654
 msgid "A quicklink to this page could not be added for you."
 msgstr "Es konnte kein Quicklink zu dieser Item für Sie erstellt werden."
 
-#: MoinMoin/apps/frontend/views.py:575
+#: MoinMoin/apps/frontend/views.py:657
 msgid "Your quicklink to this page could not be removed."
 msgstr "Ihr Quicklink zu dieser Item konnte nicht entfernt werden."
 
-#: MoinMoin/apps/frontend/views.py:590
+#: MoinMoin/apps/frontend/views.py:672
 msgid "You are not allowed to subscribe to an item you may not read."
 msgstr "Sie dürfen kein Item abonnieren, für das Sie keine Leserechte haben."
 
-#: MoinMoin/apps/frontend/views.py:592
+#: MoinMoin/apps/frontend/views.py:674
 msgid "This wiki is not enabled for mail processing."
 msgstr "Die E-Mail-Funktion ist in diesem Wiki nicht aktiviert."
 
-#: MoinMoin/apps/frontend/views.py:594
+#: MoinMoin/apps/frontend/views.py:676
 msgid "Add your email address in your user settings to use subscriptions."
 msgstr ""
 "Setzen Sie Ihre E-Mail-Adresse in den Einstellungen, um Abonnements "
 "nutzen zu können."
 
-#: MoinMoin/apps/frontend/views.py:598
+#: MoinMoin/apps/frontend/views.py:680
 msgid "Can't remove regular expression subscription!"
 msgstr ""
 "Ein auf einem regulären Ausdruck basierendes Abonnement kann nicht "
 "entfernt werden."
 
-#: MoinMoin/apps/frontend/views.py:599
+#: MoinMoin/apps/frontend/views.py:681
 msgid "Edit the subscription regular expressions in your settings."
 msgstr ""
 "Bitte bearbeiten Sie die zu den Abonnements gehörenden regulären "
 "Ausdrücke in Ihren Einstellungen."
 
-#: MoinMoin/apps/frontend/views.py:603
+#: MoinMoin/apps/frontend/views.py:685
 msgid "You could not get subscribed to this item."
 msgstr "Sie konnten nicht zu den Abonnenten dieses Items hinzugefügt werden."
 
-#: MoinMoin/apps/frontend/views.py:612 MoinMoin/apps/frontend/views.py:758
-#: MoinMoin/apps/frontend/views.py:917
+#: MoinMoin/apps/frontend/views.py:694 MoinMoin/apps/frontend/views.py:898
+#: MoinMoin/apps/frontend/views.py:1058
 msgid "The passwords do not match."
 msgstr "Die Passwörter stimmen nicht überein."
 
-#: MoinMoin/apps/frontend/views.py:629 MoinMoin/apps/frontend/views.py:709
-#: MoinMoin/apps/frontend/views.py:776 MoinMoin/apps/frontend/views.py:851
-#: MoinMoin/apps/frontend/views.py:983
+#: MoinMoin/apps/frontend/views.py:710 MoinMoin/apps/frontend/views.py:726
+#: MoinMoin/apps/frontend/views.py:849 MoinMoin/apps/frontend/views.py:916
+#: MoinMoin/apps/frontend/views.py:991 MoinMoin/apps/frontend/views.py:1124
 #: MoinMoin/templates/global_history.html:16 MoinMoin/templates/history.html:10
 msgid "Name"
 msgstr "Name"
 
-#: MoinMoin/apps/frontend/views.py:630 MoinMoin/apps/frontend/views.py:631
-#: MoinMoin/apps/frontend/views.py:852
+#: MoinMoin/apps/frontend/views.py:711 MoinMoin/apps/frontend/views.py:712
+#: MoinMoin/apps/frontend/views.py:727 MoinMoin/apps/frontend/views.py:728
+#: MoinMoin/apps/frontend/views.py:992
 msgid "Password"
 msgstr "Passwort"
 
-#: MoinMoin/apps/frontend/views.py:632 MoinMoin/apps/frontend/views.py:710
-#: MoinMoin/apps/frontend/views.py:949
+#: MoinMoin/apps/frontend/views.py:713 MoinMoin/apps/frontend/views.py:730
+#: MoinMoin/apps/frontend/views.py:850 MoinMoin/apps/frontend/views.py:1090
 msgid "E-Mail"
 msgstr "E-Mail"
 
-#: MoinMoin/apps/frontend/views.py:633 MoinMoin/apps/frontend/views.py:853
-#: MoinMoin/apps/frontend/views.py:985
+#: MoinMoin/apps/frontend/views.py:714 MoinMoin/apps/frontend/views.py:731
+#: MoinMoin/apps/frontend/views.py:993 MoinMoin/apps/frontend/views.py:1126
 msgid "OpenID"
 msgstr "OpenID"
 
-#: MoinMoin/apps/frontend/views.py:634
+#: MoinMoin/apps/frontend/views.py:715
+#: MoinMoin/templates/openid_register.html:21
 msgid "Register"
 msgstr "Registrieren"
 
-#: MoinMoin/apps/frontend/views.py:681
+#: MoinMoin/apps/frontend/views.py:794 MoinMoin/apps/frontend/views.py:821
 msgid "Account created, please log in now."
 msgstr "Das Benutzerkonto wurde erstellt. Bitte melden Sie sich an."
 
-#: MoinMoin/apps/frontend/views.py:694
+#: MoinMoin/apps/frontend/views.py:834
 msgid "Your user name or your email address is needed."
 msgstr "Bitte geben Sie Ihren Benutzernamen oder Ihre E-Mail-Adresse an."
 
-#: MoinMoin/apps/frontend/views.py:711
+#: MoinMoin/apps/frontend/views.py:851
 msgid "Recover password"
 msgstr "Passwort zurücksetzen"
 
-#: MoinMoin/apps/frontend/views.py:746
+#: MoinMoin/apps/frontend/views.py:886
 msgid "If this account exists, you will be notified."
 msgstr "Wenn dieses Benutzerkonto existiert, werden Sie benachrichtigt."
 
-#: MoinMoin/apps/frontend/views.py:759 MoinMoin/apps/frontend/views.py:919
+#: MoinMoin/apps/frontend/views.py:899 MoinMoin/apps/frontend/views.py:1060
 msgid "New password is unacceptable, encoding trouble."
 msgstr ""
 "Das neue Passwort kann nicht verwendet werden, da es Probleme bei der "
 "Kodierung gab."
 
-#: MoinMoin/apps/frontend/views.py:777
+#: MoinMoin/apps/frontend/views.py:917
 msgid "Recovery token"
 msgstr "Wiederherstellungscode"
 
-#: MoinMoin/apps/frontend/views.py:778 MoinMoin/apps/frontend/views.py:941
+#: MoinMoin/apps/frontend/views.py:918 MoinMoin/apps/frontend/views.py:1082
 msgid "New password"
 msgstr "Neues Passwort"
 
-#: MoinMoin/apps/frontend/views.py:779 MoinMoin/apps/frontend/views.py:942
+#: MoinMoin/apps/frontend/views.py:919 MoinMoin/apps/frontend/views.py:1083
 msgid "New password (repeat)"
 msgstr "Neues Passwort (wiederholen)"
 
-#: MoinMoin/apps/frontend/views.py:780 MoinMoin/apps/frontend/views.py:943
+#: MoinMoin/apps/frontend/views.py:920 MoinMoin/apps/frontend/views.py:1084
 #: MoinMoin/templates/usersettings.html:9
 msgid "Change password"
 msgstr "Passwort ändern"
 
-#: MoinMoin/apps/frontend/views.py:807
+#: MoinMoin/apps/frontend/views.py:947
 msgid "Your password has been changed, you can log in now."
 msgstr "Ihr Passwort wurde geändert. Sie können sich jetzt anmelden."
 
-#: MoinMoin/apps/frontend/views.py:809
+#: MoinMoin/apps/frontend/views.py:949
 msgid "Your token is invalid!"
 msgstr "Ihr Code ist ungültig!"
 
-#: MoinMoin/apps/frontend/views.py:823
+#: MoinMoin/apps/frontend/views.py:963
 msgid "Either your username or password was invalid."
 msgstr "Ihr Benutzername oder Passwort war ungültig."
 
-#: MoinMoin/apps/frontend/views.py:824
+#: MoinMoin/apps/frontend/views.py:964
 msgid "Failed to authenticate with this OpenID."
 msgstr "Mit dieser OpenID konnte nicht authentifiziert werden."
 
-#: MoinMoin/apps/frontend/views.py:855
-msgid "Log in"
-msgstr "Anmelden"
-
-#: MoinMoin/apps/frontend/views.py:907
+#: MoinMoin/apps/frontend/views.py:1048
 msgid "You are now logged out."
 msgstr "Sie sind jetzt abgemeldet."
 
-#: MoinMoin/apps/frontend/views.py:918
+#: MoinMoin/apps/frontend/views.py:1059
 msgid "The current password was wrong."
 msgstr "Das aktuelle Passwort war nicht korrekt."
 
-#: MoinMoin/apps/frontend/views.py:940
+#: MoinMoin/apps/frontend/views.py:1081
 msgid "Current Password"
 msgstr "Aktuelles Passwort"
 
-#: MoinMoin/apps/frontend/views.py:950 MoinMoin/apps/frontend/views.py:956
-#: MoinMoin/apps/frontend/views.py:971 MoinMoin/apps/frontend/views.py:994
-#: MoinMoin/apps/frontend/views.py:1004 MoinMoin/templates/modify_binary.html:3
+#: MoinMoin/apps/frontend/views.py:1091 MoinMoin/apps/frontend/views.py:1097
+#: MoinMoin/apps/frontend/views.py:1112 MoinMoin/apps/frontend/views.py:1135
+#: MoinMoin/apps/frontend/views.py:1145 MoinMoin/templates/modify_binary.html:4
 msgid "Save"
 msgstr "Speichern"
 
-#: MoinMoin/apps/frontend/views.py:967
+#: MoinMoin/apps/frontend/views.py:1108
 msgid "Publish my email (not my wiki homepage) in author info"
 msgstr ""
 "Veröffentliche meine E-Mail-Adresse (statt meiner Wiki-Homepage) in den "
 "Autoreninformationen"
 
-#: MoinMoin/apps/frontend/views.py:968
+#: MoinMoin/apps/frontend/views.py:1109
 msgid "Open editor on double click"
 msgstr "Editor durch Doppelklick öffnen"
 
-#: MoinMoin/apps/frontend/views.py:969
+#: MoinMoin/apps/frontend/views.py:1110
 msgid "Show comment sections"
 msgstr "Kommentarbereiche anzeigen"
 
-#: MoinMoin/apps/frontend/views.py:970
+#: MoinMoin/apps/frontend/views.py:1111
 msgid "Disable this account forever"
 msgstr "Benutzerkonto dauerhaft deaktivieren"
 
-#: MoinMoin/apps/frontend/views.py:984
+#: MoinMoin/apps/frontend/views.py:1125
 msgid "Alias-Name"
 msgstr "Alias-Name"
 
-#: MoinMoin/apps/frontend/views.py:988
+#: MoinMoin/apps/frontend/views.py:1129
 msgid "Timezone"
 msgstr "Zeitzone"
 
-#: MoinMoin/apps/frontend/views.py:993
+#: MoinMoin/apps/frontend/views.py:1134
 msgid "Locale"
 msgstr "Lokalisierung"
 
-#: MoinMoin/apps/frontend/views.py:1001
+#: MoinMoin/apps/frontend/views.py:1142
 msgid "Theme name"
 msgstr "Theme-Name"
 
-#: MoinMoin/apps/frontend/views.py:1002
+#: MoinMoin/apps/frontend/views.py:1143
 msgid "User CSS URL"
 msgstr "Benutzerdefiniertes CSS"
 
-#: MoinMoin/apps/frontend/views.py:1003
+#: MoinMoin/apps/frontend/views.py:1144
 msgid "Editor size"
 msgstr "Größe des Editors"
 
-#: MoinMoin/apps/frontend/views.py:1040
+#: MoinMoin/apps/frontend/views.py:1179
 msgid "Your password has been changed."
 msgstr "Ihr Passwort wurde geändert."
 
-#: MoinMoin/apps/frontend/views.py:1072
+#: MoinMoin/apps/frontend/views.py:1184
+msgid "This openid is already in use."
+msgstr "Diese OpenID wird bereits verwendet."
+
+#: MoinMoin/apps/frontend/views.py:1188
+msgid "This username is already in use."
+msgstr "Dieser Benutzername wird bereits verwendet."
+
+#: MoinMoin/apps/frontend/views.py:1194
+msgid "This email is already in use"
+msgstr "Diese E-Mail-Adresse wird bereits verwendet."
+
+#: MoinMoin/apps/frontend/views.py:1233
 msgid "You must log in to use bookmarks."
 msgstr "Sie müssen sich anmelden, um Lesezeichen verwenden zu können."
 
-#: MoinMoin/apps/frontend/views.py:1230 MoinMoin/themes/__init__.py:348
+#: MoinMoin/apps/frontend/views.py:1387 MoinMoin/themes/__init__.py:302
 msgid "Items with similar names"
 msgstr "Items mit ähnlichen Namen"
 
-#: MoinMoin/apps/frontend/views.py:1430
+#: MoinMoin/apps/frontend/views.py:1588
 msgid "All tags in this wiki"
 msgstr "Alle Schlagworte in diesem Wiki"
 
-#: MoinMoin/apps/frontend/views.py:1442
+#: MoinMoin/apps/frontend/views.py:1600
 #, python-format
 msgid "Items tagged with %(tag)s"
 msgstr "Items, die mit dem Schlagwort %(tag)s markiert wurden"
 
-#: MoinMoin/auth/__init__.py:237 MoinMoin/auth/ldap_login.py:129
+#: MoinMoin/auth/__init__.py:242 MoinMoin/auth/ldap_login.py:129
 msgid "Missing password. Please enter user name and password."
 msgstr "Fehlendes Passwort. Bitte geben Sie Benutzername und Passwort ein."
 
-#: MoinMoin/auth/__init__.py:245 MoinMoin/auth/ldap_login.py:199
+#: MoinMoin/auth/__init__.py:250 MoinMoin/auth/ldap_login.py:199
 #: MoinMoin/auth/ldap_login.py:245
 msgid "Invalid username or password."
 msgstr "Benutzername oder Passwort ungültig."
 
-#: MoinMoin/auth/__init__.py:249
+#: MoinMoin/auth/__init__.py:254
 #, python-format
 msgid ""
 "If you do not have an account, <a href=\"%(register_url)s\">you can "
@@ -399,7 +466,7 @@
 "Wenn Sie kein Benutzerkonto haben, <a href=\"%(register_url)s\">können "
 "Sie jetzt eines anlegen</a>. "
 
-#: MoinMoin/auth/__init__.py:251
+#: MoinMoin/auth/__init__.py:256
 #, python-format
 msgid "<a href=\"%(recover_url)s\">Forgot your password?</a>"
 msgstr "<a href=\"%(recover_url)s\">Passwort vergessen?</a>"
@@ -413,47 +480,43 @@
 msgid "LDAP server %(server)s failed."
 msgstr "Verbindung zum LDAP-Server %(server)s fehlgeschlagen."
 
-#: MoinMoin/auth/openidrp.py:60
+#: MoinMoin/auth/openidrp.py:63
 msgid "OpenID Error"
 msgstr "OpenID-Fehler"
 
-#: MoinMoin/auth/openidrp.py:67
+#: MoinMoin/auth/openidrp.py:70
 msgid "OpenID verification cancelled."
 msgstr "OpenID-Überprüfung abgebrochen."
 
-#: MoinMoin/auth/openidrp.py:93
-msgid "There is no user with this OpenID."
-msgstr "Es gibt keinen Benutzer mit dieser OpenID."
-
-#: MoinMoin/auth/openidrp.py:96
+#: MoinMoin/auth/openidrp.py:104
 msgid "This OpenID provider is not trusted."
 msgstr "Diesem OpenID-Provider wird nicht vertraut."
 
-#: MoinMoin/auth/openidrp.py:101
+#: MoinMoin/auth/openidrp.py:109
 msgid "OpenID failure."
 msgstr "OpenID-Fehlschlag."
 
-#: MoinMoin/auth/openidrp.py:135
+#: MoinMoin/auth/openidrp.py:143
 msgid "Failed to resolve OpenID."
 msgstr "Auflösen der OpenID fehlgeschlagen."
 
-#: MoinMoin/auth/openidrp.py:137
+#: MoinMoin/auth/openidrp.py:145
 msgid "OpenID discovery failure, not a valid OpenID."
 msgstr "OpenID-Discovery fehlgeschlagen, dies ist keine gültige OpenID."
 
-#: MoinMoin/auth/openidrp.py:141
+#: MoinMoin/auth/openidrp.py:149
 msgid "No OpenID service at this URL."
 msgstr "Kein OpenID-Service unter dieser URL."
 
-#: MoinMoin/config/default.py:330
+#: MoinMoin/config/default.py:258
 msgid "Password is too short."
 msgstr "Das Passwort ist zu kurz."
 
-#: MoinMoin/config/default.py:332
+#: MoinMoin/config/default.py:260
 msgid "Password has not enough different characters."
 msgstr "Das Passwort besteht nicht aus genug verschiedenen Zeichen."
 
-#: MoinMoin/config/default.py:338
+#: MoinMoin/config/default.py:266
 msgid ""
 "Password is too easy to guess (password contains name or name contains "
 "password)."
@@ -461,11 +524,11 @@
 "Das Passwort ist zu leicht zu erraten (Passwort ist Teil des "
 "Benutzernamens oder umgekehrt)."
 
-#: MoinMoin/config/default.py:347
+#: MoinMoin/config/default.py:275
 msgid "Password is too easy to guess (keyboard sequence)."
 msgstr "Das Passwort ist zu leicht zu erraten (Zeichenfolge auf der Tastatur)."
 
-#: MoinMoin/converter/html_out.py:599
+#: MoinMoin/converter/html_out.py:595
 msgid "Contents"
 msgstr "Inhaltsverzeichnis"
 
@@ -476,7 +539,15 @@
 "<<%(macro_name)s: Ausführung fehlgeschlagen [%(error_msg)s] (Sie finden "
 "weitere Informationen im Log)>>"
 
-#: MoinMoin/converter/nonexistent_in.py:29
+#: MoinMoin/converter/moinwiki_in.py:1005
+msgid "Error:"
+msgstr "Fehler:"
+
+#: MoinMoin/converter/moinwiki_in.py:1006
+msgid "is invalid within"
+msgstr "ist ungültig innerhalb von"
+
+#: MoinMoin/converter/nonexistent_in.py:30
 #, python-format
 msgid "%(item_name)s does not exist. Create it?"
 msgstr "%(item_name)s existiert nicht, möchten Sie es jetzt erstellen?"
@@ -500,131 +571,49 @@
 "Es ist nicht möglich, die Daten zu diesem MIME-Typen umzuwandeln: "
 "%(mimetype)s"
 
-#: MoinMoin/items/__init__.py:1248 MoinMoin/items/__init__.py:1319
+#: MoinMoin/items/__init__.py:1265 MoinMoin/items/__init__.py:1341
 #, python-format
 msgid "Edit drawing %(filename)s (opens in new window)"
 msgstr "Zeichnung %(filename)s bearbeiten (öffnet in neuem Fenster)"
 
-#: MoinMoin/items/__init__.py:1263 MoinMoin/items/__init__.py:1336
+#: MoinMoin/items/__init__.py:1280 MoinMoin/items/__init__.py:1358
 #, python-format
 msgid "Clickable drawing: %(filename)s"
 msgstr "Anklickbare Zeichnung: %(filename)s"
 
-#: MoinMoin/macro/GoTo.py:29
-msgid "Go To Item"
-msgstr "Gehe zu Item"
-
-#: MoinMoin/macro/HighlighterList.py:25
+#: MoinMoin/macro/HighlighterList.py:24
 msgid "Lexer description"
 msgstr "Lexer-Beschreibung"
 
-#: MoinMoin/macro/HighlighterList.py:26
+#: MoinMoin/macro/HighlighterList.py:25
 msgid "Lexer names"
 msgstr "Lexer-Namen"
 
-#: MoinMoin/macro/HighlighterList.py:27
+#: MoinMoin/macro/HighlighterList.py:26
 msgid "File patterns"
 msgstr "Dateitypen"
 
-#: MoinMoin/macro/HighlighterList.py:28
+#: MoinMoin/macro/HighlighterList.py:27
 msgid "Mimetypes"
 msgstr "MIME-Typen"
 
-#: MoinMoin/macro/WikiConfig.py:42
-msgid "Wiki configuration"
-msgstr "Wiki-Konfiguration"
-
-#: MoinMoin/macro/WikiConfig.py:44
-msgid ""
-"This table shows all settings in this wiki that do not have default "
-"values. Settings that the configuration system doesn't know about are "
-"shown in italic, those may be due to third-party extensions needing "
-"configuration or settings that were removed from Moin."
-msgstr ""
-"Diese Tabelle zeigt alle Einstellungen des Wikis, die nicht dem "
-"Standardwert entsprechen. Dem Konfigurationssystem unbekannte "
-"Einstellungen sind kursiv dargestellt. Diese sind möglicherweise für "
-"Erweiterungen von Drittanbietern oder wurden aus Moin entfernt."
-
-#: MoinMoin/macro/WikiConfig.py:58 MoinMoin/macro/WikiConfigHelp.py:42
-msgid "Variable name"
-msgstr "Variablenname"
-
-#: MoinMoin/macro/WikiConfig.py:58
-msgid "Setting"
-msgstr "Einstellung"
-
-#: MoinMoin/macro/WikiConfigHelp.py:42
-msgid "Default"
-msgstr "Standardeinstellung"
-
-#: MoinMoin/macro/WikiConfigHelp.py:42
-msgid "Description"
-msgstr "Beschreibung"
-
-#: MoinMoin/mail/sendmail.py:84
+#: MoinMoin/mail/sendmail.py:86
 msgid "No recipients, nothing to do"
 msgstr "Keine Empfänger, nichts zu tun"
 
-#: MoinMoin/mail/sendmail.py:160
+#: MoinMoin/mail/sendmail.py:162
 #, python-format
 msgid "Connection to mailserver '%(server)s' failed: %(reason)s"
 msgstr "Verbindung zum Mailserver '%(server)s' fehlgeschlagen: %(reason)s"
 
-#: MoinMoin/mail/sendmail.py:174
+#: MoinMoin/mail/sendmail.py:176
 msgid "Mail not sent"
 msgstr "E-Mail wurde nicht versandt"
 
-#: MoinMoin/mail/sendmail.py:177
+#: MoinMoin/mail/sendmail.py:179
 msgid "Mail sent successfully"
 msgstr "E-Mail wurde erfolgreich versandt"
 
-#: MoinMoin/search/results.py:286
-#, python-format
-msgid ""
-"Results %(bs)s%(hitsFrom)d - %(hitsTo)d%(be)s of %(aboutHits)s "
-"%(bs)s%(hits)d%(be)s results out of about %(items)d items."
-msgstr ""
-"Ergebnisse %(bs)s%(hitsFrom)d - %(hitsTo)d%(be)s von %(aboutHits)s "
-"%(bs)s%(hits)d%(be)s Ergebnissen aus ungefähr %(items)d Items."
-
-#: MoinMoin/search/results.py:299
-msgid "seconds"
-msgstr "Sekunden"
-
-#: MoinMoin/search/results.py:694
-msgid "Previous"
-msgstr "Vorherige"
-
-#: MoinMoin/search/results.py:715
-msgid "Next"
-msgstr "Nächste"
-
-#: MoinMoin/search/results.py:747 MoinMoin/search/results.py:749
-msgid "rev"
-msgstr "Rev"
-
-#: MoinMoin/search/results.py:747
-msgid "current"
-msgstr "aktuelle"
-
-#: MoinMoin/search/results.py:750
-#, python-format
-msgid "last modified: %s"
-msgstr "zuletzt geändert: %s"
-
-#: MoinMoin/search/results.py:809
-msgid "match"
-msgstr "Treffer"
-
-#: MoinMoin/search/results.py:809
-msgid "matches"
-msgstr "Treffer"
-
-#: MoinMoin/search/Xapian/search.py:65
-msgid "about"
-msgstr "ungefähr"
-
 #: MoinMoin/security/textcha.py:163
 msgid "The entered TextCha was incorrect."
 msgstr "Das eingegebene TextCha war falsch."
@@ -639,73 +628,73 @@
 msgid "TextCha"
 msgstr "TextCha"
 
-#: MoinMoin/storage/error.py:41
+#: MoinMoin/storage/error.py:42
 msgid "Permission denied!"
 msgstr "Zugriff verweigert!"
 
-#: MoinMoin/storage/error.py:43
+#: MoinMoin/storage/error.py:44
 msgid "You"
 msgstr "Sie"
 
-#: MoinMoin/storage/error.py:45
+#: MoinMoin/storage/error.py:46
 #, python-format
 msgid "%(username)s may not %(priv)s '%(item)s'."
 msgstr "%(username)s darf '%(item)s' nicht %(priv)s."
 
-#: MoinMoin/templates/base.html:70 MoinMoin/templates/layout.html:24
+#: MoinMoin/templates/base.html:63 MoinMoin/templates/layout.html:24
 msgid "Search"
 msgstr "Suche"
 
-#: MoinMoin/templates/base.html:76 MoinMoin/templates/editbar.html:87
-msgid "More Actions:"
-msgstr "Weitere Aktionen:"
+#: MoinMoin/templates/base.html:69
+msgid "More"
+msgstr "Mehr"
 
 #: MoinMoin/templates/copy.html:3
 #, python-format
 msgid "Copy '%(item_name)s'"
 msgstr "'%(item_name)s' kopieren"
 
-#: MoinMoin/templates/copy.html:6 MoinMoin/templates/rename.html:7
+#: MoinMoin/templates/copy.html:7 MoinMoin/templates/rename.html:8
 msgid "Target"
 msgstr "Ziel"
 
-#: MoinMoin/templates/copy.html:9 MoinMoin/templates/delete.html:7
-#: MoinMoin/templates/destroy.html:13 MoinMoin/templates/global_history.html:20
-#: MoinMoin/templates/history.html:17 MoinMoin/templates/rename.html:10
-#: MoinMoin/templates/revert.html:6
+#: MoinMoin/templates/copy.html:10 MoinMoin/templates/delete.html:8
+#: MoinMoin/templates/destroy.html:14 MoinMoin/templates/global_history.html:20
+#: MoinMoin/templates/history.html:17 MoinMoin/templates/rename.html:11
+#: MoinMoin/templates/revert.html:8
 msgid "Comment"
 msgstr "Kommentar"
 
-#: MoinMoin/templates/copy.html:12
+#: MoinMoin/templates/copy.html:14
 msgid "Copy"
 msgstr "Kopieren"
 
-#: MoinMoin/templates/copy.html:13 MoinMoin/templates/delete.html:11
-#: MoinMoin/templates/destroy.html:17 MoinMoin/templates/modify_binary.html:4
-#: MoinMoin/templates/rename.html:14 MoinMoin/templates/revert.html:10
+#: MoinMoin/templates/copy.html:15 MoinMoin/templates/delete.html:12
+#: MoinMoin/templates/destroy.html:20 MoinMoin/templates/modify_binary.html:5
+#: MoinMoin/templates/rename.html:15 MoinMoin/templates/revert.html:14
 msgid "Cancel"
 msgstr "Abbrechen"
 
-#: MoinMoin/templates/delete.html:4
+#: MoinMoin/templates/delete.html:5
 #, python-format
 msgid "Delete '%(item_name)s'"
 msgstr "'%(item_name)s' löschen"
 
-#: MoinMoin/templates/delete.html:10
+#: MoinMoin/templates/delete.html:11
 msgid "Delete"
 msgstr "Löschen"
 
-#: MoinMoin/templates/destroy.html:4
+#: MoinMoin/templates/destroy.html:5
 #, python-format
 msgid "DESTROY COMPLETE item '%(item_name)s'"
 msgstr "Item '%(item_name)s' VOLLSTÄNDIG VERNICHTEN"
 
-#: MoinMoin/templates/destroy.html:8
+#: MoinMoin/templates/destroy.html:9
 #, python-format
 msgid "DESTROY REVISION '%(item_name)s' (rev %(rev_no)d)"
 msgstr "Revision '%(item_name)s' (Rev %(rev_no)d) VERNICHTEN"
 
-#: MoinMoin/templates/destroy.html:16
+#: MoinMoin/templates/destroy.html:19
 msgid "DESTROY"
 msgstr "VERNICHTEN"
 
@@ -747,63 +736,67 @@
 msgid "Line"
 msgstr "Zeile"
 
-#: MoinMoin/templates/editbar.html:4
+#: MoinMoin/templates/editbar.html:5
 msgid "Show"
 msgstr "Anzeigen"
 
-#: MoinMoin/templates/editbar.html:9
+#: MoinMoin/templates/editbar.html:10
 msgid "Highlight"
 msgstr "Hervorheben"
 
-#: MoinMoin/templates/editbar.html:14
+#: MoinMoin/templates/editbar.html:15
 msgid "Meta"
 msgstr "Metadaten"
 
-#: MoinMoin/templates/editbar.html:20 MoinMoin/templates/show.html:9
+#: MoinMoin/templates/editbar.html:21 MoinMoin/templates/show.html:9
 msgid "Modify"
 msgstr "Bearbeiten"
 
-#: MoinMoin/templates/editbar.html:22
+#: MoinMoin/templates/editbar.html:23
 msgid "Immutable Item"
 msgstr "Geschützte Item"
 
-#: MoinMoin/templates/editbar.html:28
+#: MoinMoin/templates/editbar.html:29
 msgid "Download"
 msgstr "Herunterladen"
 
-#: MoinMoin/templates/editbar.html:33
+#: MoinMoin/templates/editbar.html:34
 msgid "History"
 msgstr "Historie"
 
-#: MoinMoin/templates/editbar.html:41
+#: MoinMoin/templates/editbar.html:42
 msgid "Remove Link"
 msgstr "Link entfernen"
 
-#: MoinMoin/templates/editbar.html:43
+#: MoinMoin/templates/editbar.html:44
 msgid "Add Link"
 msgstr "Link hinzufügen"
 
-#: MoinMoin/templates/editbar.html:53
+#: MoinMoin/templates/editbar.html:54
 msgid "Unsubscribe"
 msgstr "Nicht abonnieren"
 
-#: MoinMoin/templates/editbar.html:55
+#: MoinMoin/templates/editbar.html:56
 msgid "Subscribe"
 msgstr "Abonnieren"
 
-#: MoinMoin/templates/editbar.html:62
+#: MoinMoin/templates/editbar.html:63
 msgid "Comments"
 msgstr "Kommentare"
 
-#: MoinMoin/templates/editbar.html:67
+#: MoinMoin/templates/editbar.html:68
 msgid "Index"
 msgstr "Index"
 
-#: MoinMoin/templates/editbar.html:93
+#: MoinMoin/templates/editbar.html:88
+msgid "More Actions:"
+msgstr "Weitere Aktionen:"
+
+#: MoinMoin/templates/editbar.html:94
 msgid "Do"
 msgstr "Los!"
 
-#: MoinMoin/templates/global_history.html:10 MoinMoin/themes/__init__.py:339
+#: MoinMoin/templates/global_history.html:10 MoinMoin/themes/__init__.py:293
 msgid "Global History"
 msgstr "Globale Historie"
 
@@ -890,13 +883,21 @@
 msgid "Logout"
 msgstr "Abmelden"
 
-#: MoinMoin/templates/layout.html:56 MoinMoin/templates/login.html:5
+#: MoinMoin/templates/layout.html:58
 msgid "Login"
 msgstr "Anmelden"
 
-#: MoinMoin/templates/login.html:17
-msgid "Login with your OpenID:"
-msgstr "Melden Sie sich mit Ihrer OpenID an:"
+#: MoinMoin/templates/login.html:9
+msgid "Moin login"
+msgstr "Moin-Login"
+
+#: MoinMoin/templates/login.html:21 MoinMoin/templates/login.html:35
+msgid "Log in"
+msgstr "Anmelden"
+
+#: MoinMoin/templates/login.html:28
+msgid "OpenID login"
+msgstr "OpenID-Login"
 
 #: MoinMoin/templates/lostpass.html:5
 msgid "Lost Password"
@@ -930,11 +931,11 @@
 msgid "Modifying %(item_name)s"
 msgstr "%(item_name)s bearbeiten"
 
-#: MoinMoin/templates/modify_applet.html:11
+#: MoinMoin/templates/modify_applet.html:12
 msgid "Upload file:"
 msgstr "Datei hochladen:"
 
-#: MoinMoin/templates/modify_binary.html:6
+#: MoinMoin/templates/modify_binary.html:8
 msgid "Comment:"
 msgstr "Kommentar:"
 
@@ -964,29 +965,29 @@
 "Dieses Item existiert (noch) nicht, aber Sie können es jetzt erstellen. "
 "Bitte wählen Sie die Art das Items, das Sie erstellen wollen, aus."
 
+#: MoinMoin/templates/openid_register.html:5 MoinMoin/templates/register.html:5
+msgid "Create Account"
+msgstr "Benutzerkonto erstellen"
+
 #: MoinMoin/templates/recoverpass.html:5
 msgid "Recover Password"
 msgstr "Passwort wiederherstellen"
 
-#: MoinMoin/templates/register.html:5
-msgid "Create Account"
-msgstr "Benutzerkonto erstellen"
-
-#: MoinMoin/templates/rename.html:3
+#: MoinMoin/templates/rename.html:4
 #, python-format
 msgid "Rename '%(item_name)s'"
 msgstr "'%(item_name)s' umbenennen"
 
-#: MoinMoin/templates/rename.html:13
+#: MoinMoin/templates/rename.html:14
 msgid "Rename"
 msgstr "Umbenennen"
 
-#: MoinMoin/templates/revert.html:3
+#: MoinMoin/templates/revert.html:4
 #, python-format
-msgid "Revert '%(item_name)s'"
-msgstr "'%(item_name)s' wiederherstellen"
+msgid "Revert '%(item_name)s' (rev %(rev_no)d)"
+msgstr "Wiederherstellen von '%(item_name)s' (Rev %(rev_no)d)"
 
-#: MoinMoin/templates/revert.html:9
+#: MoinMoin/templates/revert.html:13
 msgid "Revert"
 msgstr "Wiederherstellen"
 
@@ -1061,128 +1062,120 @@
 msgid "Total: %(total)s"
 msgstr "Insgesamt: %(total)s"
 
-#: MoinMoin/themes/__init__.py:46
+#: MoinMoin/themes/__init__.py:45
 msgid "Access denied"
 msgstr "Zugriff verweigert"
 
-#: MoinMoin/themes/__init__.py:47
+#: MoinMoin/themes/__init__.py:46
 msgid "You are not allowed to access this resource."
 msgstr "Sie haben nicht die notwendigen Rechte, um auf diese Ressource zuzugreifen"
 
-#: MoinMoin/themes/__init__.py:340
+#: MoinMoin/themes/__init__.py:294
 msgid "Global Items Index"
 msgstr "Globaler Item-Index"
 
-#: MoinMoin/themes/__init__.py:341
+#: MoinMoin/themes/__init__.py:295
 msgid "Global Tags Index"
 msgstr "Globaler Schlagwort-Index"
 
-#: MoinMoin/themes/__init__.py:342
-msgid "Wanted Items"
-msgstr "Fehlende Items"
-
-#: MoinMoin/themes/__init__.py:343
-msgid "Orphaned Items"
-msgstr "Verwaiste Items"
-
-#: MoinMoin/themes/__init__.py:345 MoinMoin/themes/__init__.py:349
+#: MoinMoin/themes/__init__.py:299 MoinMoin/themes/__init__.py:303
 msgid "-----------------------------------"
 msgstr "----------------------------------------"
 
-#: MoinMoin/themes/__init__.py:346
+#: MoinMoin/themes/__init__.py:300
 msgid "What refers here?"
 msgstr "Was linkt hierauf?"
 
-#: MoinMoin/themes/__init__.py:347
+#: MoinMoin/themes/__init__.py:301
 msgid "Local Site Map"
 msgstr "Lokale Sitemap"
 
-#: MoinMoin/themes/__init__.py:350
+#: MoinMoin/themes/__init__.py:304
 msgid "Copy Item"
 msgstr "Item kopieren"
 
-#: MoinMoin/themes/__init__.py:351
+#: MoinMoin/themes/__init__.py:305
 msgid "Rename Item"
 msgstr "Item umbenennen"
 
-#: MoinMoin/themes/__init__.py:352
+#: MoinMoin/themes/__init__.py:306
 msgid "Delete Item"
 msgstr "Item löschen"
 
-#: MoinMoin/themes/__init__.py:353
+#: MoinMoin/themes/__init__.py:307
 msgid "Destroy Item"
 msgstr "Item vernichten"
 
-#: MoinMoin/themes/__init__.py:364
+#: MoinMoin/themes/__init__.py:318
 msgid "anonymous"
 msgstr "anonym"
 
-#: MoinMoin/util/paramparser.py:311
+#: MoinMoin/util/paramparser.py:316
 #, python-format
 msgid "Argument \"%(name)s\" must be a boolean value, not \"%(value)s\""
 msgstr "Argument \"%(name)s\" muss ein boolescher Wert sein, nicht \"%(value)s\""
 
-#: MoinMoin/util/paramparser.py:314
+#: MoinMoin/util/paramparser.py:319
 #, python-format
 msgid "Argument must be a boolean value, not \"%(value)s\""
 msgstr "Argument muss ein boolescher Wert sein, nicht \"%(value)s\""
 
-#: MoinMoin/util/paramparser.py:340
+#: MoinMoin/util/paramparser.py:345
 #, python-format
 msgid "Argument \"%(name)s\" must be an integer value, not \"%(value)s\""
 msgstr "Argument \"%(name)s\" muss ein ganzzahliger Wert sein, nicht \"%(value)s\""
 
-#: MoinMoin/util/paramparser.py:343
+#: MoinMoin/util/paramparser.py:348
 #, python-format
 msgid "Argument must be an integer value, not \"%(value)s\""
 msgstr "Argument muss ein ganzzahliger Wert sein, nicht \"%(value)s\""
 
-#: MoinMoin/util/paramparser.py:368
+#: MoinMoin/util/paramparser.py:373
 #, python-format
 msgid "Argument \"%(name)s\" must be a floating point value, not \"%(value)s\""
 msgstr "Argument \"%(name)s\" muss eine Fließkommazahl sein, nicht \"%(value)s\""
 
-#: MoinMoin/util/paramparser.py:371
+#: MoinMoin/util/paramparser.py:376
 #, python-format
 msgid "Argument must be a floating point value, not \"%(value)s\""
 msgstr "Argument muss eine Fließkommazahl sein, nicht \"%(value)s\""
 
-#: MoinMoin/util/paramparser.py:398
+#: MoinMoin/util/paramparser.py:403
 #, python-format
 msgid "Argument \"%(name)s\" must be a complex value, not \"%(value)s\""
 msgstr "Argument \"%(name)s\" muss ein komplexer Wert sein, nicht \"%(value)s\""
 
-#: MoinMoin/util/paramparser.py:401
+#: MoinMoin/util/paramparser.py:406
 #, python-format
 msgid "Argument must be a complex value, not \"%(value)s\""
 msgstr "Argument muss ein komplexer Wert sein, nicht \"%(value)s\""
 
-#: MoinMoin/util/paramparser.py:455
+#: MoinMoin/util/paramparser.py:460
 #, python-format
 msgid "Argument \"%(name)s\" must be one of \"%(choices)s\", not \"%(value)s\""
 msgstr ""
 "Argument \"%(name)s\" muss eines von \"%(choices)s\" sein, nicht "
 "\"%(value)s\""
 
-#: MoinMoin/util/paramparser.py:460
+#: MoinMoin/util/paramparser.py:465
 #, python-format
 msgid "Argument must be one of \"%(choices)s\", not \"%(value)s\""
 msgstr "Argument muss eines von \"%(choices)s\" sein, nicht \"%(value)s\""
 
-#: MoinMoin/util/paramparser.py:685
+#: MoinMoin/util/paramparser.py:690
 msgid "Too many arguments"
 msgstr "Zu viele Argumente"
 
-#: MoinMoin/util/paramparser.py:690
+#: MoinMoin/util/paramparser.py:695
 msgid "Cannot have arguments without name following named arguments"
 msgstr "Unbenannte Argumente können nicht auf benannte Argumente folgen"
 
-#: MoinMoin/util/paramparser.py:706
+#: MoinMoin/util/paramparser.py:711
 #, python-format
 msgid "Argument \"%(name)s\" is required"
 msgstr "Argument \"%(name)s\" wird benötigt"
 
-#: MoinMoin/util/paramparser.py:718
+#: MoinMoin/util/paramparser.py:723
 #, python-format
 msgid "No argument named \"%(name)s\""
 msgstr "Kein Argument mit dem Namen \"%(name)s\""
--- a/MoinMoin/user.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/user.py	Sat Mar 12 14:14:22 2011 +0100
@@ -111,9 +111,8 @@
 
 def get_by_filter(key, value):
     """ Searches for an user with a given filter """
-    from MoinMoin.search import term
-    filter = term.ItemMetaDataMatch(key, value)
-    items = get_user_backend().search_items(filter)
+    from MoinMoin.storage.terms import ItemMetaDataMatch
+    items = get_user_backend().search_items(ItemMetaDataMatch(key, value))
     users = [User(item.name) for item in items]
     return users
 
@@ -144,10 +143,10 @@
     :rtype: string
     :returns: the corresponding user ID or None
     """
-    from MoinMoin.search import term
+    from MoinMoin.storage.terms import ItemMetaDataMatch
     try:
         backend = get_user_backend()
-        for user in backend.search_items(term.ItemMetaDataMatch('name', searchName)):
+        for user in backend.search_items(ItemMetaDataMatch('name', searchName)):
             return user.name
         return None
     except IndexError:
--- a/MoinMoin/util/monkeypatch.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/util/monkeypatch.py	Sat Mar 12 14:14:22 2011 +0100
@@ -25,6 +25,11 @@
 werkzeug.serving.BaseRequestHandler = BaseRequestHandler
 werkzeug.serving.WSGIRequestHandler = BaseRequestHandler
 
+# werkzeug 0.6.2 has a missing import sys in posixemulation
+from werkzeug import posixemulation
+import sys
+posixemulation.sys = sys
+
 
 # flask patching -------------------------------------------------------------
 
--- a/MoinMoin/util/plugins.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/util/plugins.py	Sat Mar 12 14:14:22 2011 +0100
@@ -194,14 +194,13 @@
     Since each configured plugin path has unique plugins, we load the
     plugin packages as "moin_plugin_<sha1(path)>.plugin".
     """
-    plugin_dirs = [cfg.plugin_dir] + cfg.plugin_dirs
     cfg._plugin_modules = []
 
     try:
         # Lock other threads while we check and import
         imp.acquire_lock()
         try:
-            for pdir in plugin_dirs:
+            for pdir in cfg.plugin_dirs:
                 csum = 'p_%s' % hashlib.new('sha1', pdir).hexdigest()
                 modname = '%s.%s' % (cfg.siteid, csum)
                 # If the module is not loaded, try to load it
--- a/MoinMoin/wikiutil.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/MoinMoin/wikiutil.py	Sat Mar 12 14:14:22 2011 +0100
@@ -24,6 +24,8 @@
 from flask import request
 
 from MoinMoin import config
+from MoinMoin.config import IS_SYSITEM
+
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.util import pysupport, lock
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError
@@ -134,7 +136,6 @@
     :rtype: bool
     :returns: True if page is a system item
     """
-    from MoinMoin.items import IS_SYSITEM
     try:
         item = flaskg.storage.get_item(itemname)
         return item.get_revision(-1)[IS_SYSITEM]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/wsgi/print_hotshot_profile.py	Sat Mar 12 14:14:22 2011 +0100
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# Copyright: 2005 by MoinMoin:ThomasWaldmann
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+    MoinMoin - Print statistics gathered by hotshot profiler
+
+    Usage:
+        print_stats.py statsfile
+
+    Typical usage:
+     1. Edit moin.py and activate the hotshot profiler, set profile file name
+     2. Run moin.py
+     3. Do some request, with a browser, script or ab
+     4. Stop moin.py
+     5. Run this tool: print_stats.py moin.prof
+
+    Currently CGI and twisted also have a hotshot profiler integration.
+"""
+
+
+def run():
+    import sys
+    from hotshot import stats
+
+    if len(sys.argv) != 2:
+        print __doc__
+        sys.exit()
+
+    # Load and print stats
+    s = stats.load(sys.argv[1])
+    s.strip_dirs()
+    s.sort_stats('cumulative', 'time', 'calls')
+    s.print_stats(40)
+    s.print_callers(40)
+
+if __name__ == "__main__":
+    run()
+
--- a/docs/admin/install.rst	Fri Mar 11 23:08:35 2011 -0600
+++ b/docs/admin/install.rst	Sat Mar 12 14:14:22 2011 +0100
@@ -37,15 +37,11 @@
 a while.
 It will also compile the translations (`*.po` files) to binary `*.mo` files.
 
-Note: in this special mode, it won't copy moin to the env/ directory, it will
-run everything from your work dir, so you can modify code and directly try it
-out (you only need to do this installation procedure once).
-
-After successfully installing, you can enter the virtual env and start moin by::
+Further, it will create a "moin" script for your platform which you can use
+for starting moin (the builtin server) or invoke moin script commands. It will
+be in the PATH, so just type "moin" on the shell / cmd.
 
- source env/bin/activate
- ./moin
+Note: in this special mode, it won't copy the MoinMoin code to the env/
+directory, it will run everything from your work dir, so you can modify code
+and directly try it out (you only need to do this installation procedure once).
 
-We activate that virtual environment, so moin will find all its dependencies
-and finally start the builtin server using the `moin` script.
-
--- a/docs/admin/serve.rst	Fri Mar 11 23:08:35 2011 -0600
+++ b/docs/admin/serve.rst	Sat Mar 12 14:14:22 2011 +0100
@@ -9,22 +9,41 @@
 
 It is not made for serving bigger loads, but it is easy to use.
 
-To start moin using the builtin web server, just run "moin".
-
-If you'ld like to see all subcommands and options of the moin command, use::
-
- $ ./moin help
- $ ./moin moin --help
+Entering the virtual env
+------------------------
+If you installed to a virtualenv, you need to activate it first, so it will
+find the moin script, the moin code and all its library dependencies::
 
-**Example**::
-
- $ ./moin moin --config /srv/wiki/wikiconfig.py --host 1.2.3.4 --port 7777
+ source env/bin/activate  # for linux (or other posix OSes)
+ # or
+ call env\bin\activate  # for windows
 
-Use an absolute path for the wikiconfig.py!
+Running the builtin server
+--------------------------
+Then you can run the moin builtin server by::
 
-.. todo::
+ moin
+ # or, if you need another ip/port:
+ moin moin --config /path/to/wikiconfig.py --host 1.2.3.4 --port 7777
 
-   add stuff above to man page and reference man page from here
+Now moin starts the builtin server and tries to locate the wiki configuration
+from (please use an absolute path):
+
+- commandline argument `--config /path/to/wikiconfig.py`
+- environment variable `MOINCFG=/path/to/wikiconfig.py`
+- current directory, file `wikiconfig_local.py`
+- current directory, file `wikiconfig.py`
+
+While the moin server is starting up, you will see some log output like::
+
+ 2011-03-06 23:35:11,445 INFO werkzeug:116  * Running on http://127.0.0.1:8080/
+
+Now point your browser at that URL - your moin wiki is running!
+
+Stopping the builtin server
+---------------------------
+To stop the wiki server, either use `Ctrl-C` or close the window.
+
 
 External Web Server (advanced)
 ==============================
--- a/docs/devel/api/MoinMoin.macro.rst	Fri Mar 11 23:08:35 2011 -0600
+++ b/docs/devel/api/MoinMoin.macro.rst	Sat Mar 12 14:14:22 2011 +0100
@@ -57,14 +57,6 @@
     :undoc-members:
     :show-inheritance:
 
-:mod:`GoTo` Module
-------------------
-
-.. automodule:: MoinMoin.macro.GoTo
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
 :mod:`HighlighterList` Module
 -----------------------------
 
@@ -89,14 +81,6 @@
     :undoc-members:
     :show-inheritance:
 
-:mod:`PageCount` Module
------------------------
-
-.. automodule:: MoinMoin.macro.PageCount
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
 :mod:`PageSize` Module
 ----------------------
 
@@ -121,14 +105,6 @@
     :undoc-members:
     :show-inheritance:
 
-:mod:`TemplateList` Module
---------------------------
-
-.. automodule:: MoinMoin.macro.TemplateList
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
 :mod:`Verbatim` Module
 ----------------------
 
@@ -137,22 +113,6 @@
     :undoc-members:
     :show-inheritance:
 
-:mod:`WikiConfig` Module
-------------------------
-
-.. automodule:: MoinMoin.macro.WikiConfig
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
-:mod:`WikiConfigHelp` Module
-----------------------------
-
-.. automodule:: MoinMoin.macro.WikiConfigHelp
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
 :mod:`_base` Module
 -------------------
 
--- a/docs/devel/api/MoinMoin.rst	Fri Mar 11 23:08:35 2011 -0600
+++ b/docs/devel/api/MoinMoin.rst	Sat Mar 12 14:14:22 2011 +0100
@@ -72,7 +72,6 @@
     MoinMoin.macro
     MoinMoin.mail
     MoinMoin.script
-    MoinMoin.search
     MoinMoin.security
     MoinMoin.signalling
     MoinMoin.storage
--- a/docs/devel/api/MoinMoin.script.maint.rst	Fri Mar 11 23:08:35 2011 -0600
+++ b/docs/devel/api/MoinMoin.script.maint.rst	Sat Mar 12 14:14:22 2011 +0100
@@ -9,6 +9,14 @@
     :undoc-members:
     :show-inheritance:
 
+:mod:`create_item` Module
+-------------------------
+
+.. automodule:: MoinMoin.script.maint.create_item
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
 :mod:`reduce_revisions` Module
 ------------------------------
 
--- a/docs/devel/api/MoinMoin.script.rst	Fri Mar 11 23:08:35 2011 -0600
+++ b/docs/devel/api/MoinMoin.script.rst	Sat Mar 12 14:14:22 2011 +0100
@@ -9,21 +9,11 @@
     :undoc-members:
     :show-inheritance:
 
-:mod:`moin` Module
-------------------
-
-.. automodule:: MoinMoin.script.moin
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
 Subpackages
 -----------
 
 .. toctree::
 
     MoinMoin.script.account
-    MoinMoin.script.index
     MoinMoin.script.maint
-    MoinMoin.script.migration
 
--- a/docs/devel/api/MoinMoin.storage.rst	Fri Mar 11 23:08:35 2011 -0600
+++ b/docs/devel/api/MoinMoin.storage.rst	Sat Mar 12 14:14:22 2011 +0100
@@ -25,6 +25,14 @@
     :undoc-members:
     :show-inheritance:
 
+:mod:`terms` Module
+-------------------
+
+.. automodule:: MoinMoin.storage.terms
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
 Subpackages
 -----------
 
--- a/docs/devel/development.rst	Fri Mar 11 23:08:35 2011 -0600
+++ b/docs/devel/development.rst	Sat Mar 12 14:14:22 2011 +0100
@@ -38,7 +38,7 @@
 * flask as framework
 
   - flask-script for commandline scripts
-  - flask-babel / babel / pytz / parsedatetime for i18n/l10n
+  - flask-babel / babel / pytz for i18n/l10n
   - flask-themes for theme switching
   - flask-cache as cache storage abstraction
 * werkzeug for lowlevel web/http stuff, debugging, builtin server, etc.
--- a/docs/examples/config/snippets/xapian_wikiconfig_snippet	Fri Mar 11 23:08:35 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-    # This is a sample configuration snippet that shows how to configure
-    # Xapian indexing search. For further help see HelpOnXapian.
-
-    # You need Xapian and its Python bindings (xapian-core and xapian-bindings). 
-    # On Windows, you additionally need pywin32 (http://sourceforge.net/projects/pywin32/). 
-
-    # Setting this to True, enables Xapian indexed search.
-    #xapian_search = False
-
-    # IMPORTANT: After enabling (and also after changing any xapian related setting), you
-    # need to completely (re-)build the index.
-    # There are 2 ways:
-    # A. Unsafe 1-stage xapian index rebuilding:
-    #    moin --config-dir=/where/your/configdir/is --wiki-url=wiki-url/ index build --mode=rebuild
-    #    Use this if your wiki is not running at that time or if it is unlikely that
-    #    someone uses the search index or if you can live with a few failing searches when
-    #    it is switching from old to new index.
-    #    "rebuild" is the same as "buildnewindex" immediately followed by "usenewindex".
-    # B. Safe 2-stage xapian index rebuilding:
-    #    moin ... index build --mode=buildnewindex
-    #    <stop wiki>
-    #    moin ... index build --mode=usenewindex
-    #    <start wiki>
-    #    buildnewindex will build a SEPARATE new index while the wiki is running and it
-    #    will NOT interfere with wiki operations at all (except that it consumes some
-    #    server resources like cpu, disk, ram) - the wiki will NOT use the new index.
-    #    This phase can take some minutes up to many hours, depending on the size of your wiki.
-    #    usenewindex is the switching command that will switch from the current to the
-    #    new index. If you like to avoid trouble with a few failing searches (but rather want
-    #    the wiki offline for a short moment), you can stop the wiki, switch index, start
-    #    the wiki. usenewindex will just take some milliseconds.
-
-    # If set to True means that ALL page revisions will be indexed (not only
-    # the latest revision).
-    # Thus, they optionally will be searchable [see FindPage search options]).
-    #xapian_index_history = False
-
-    # If set to True, words will be indexed in their original AND their stemmed
-    # forms. This means that searching for "testing" will also find "tested",
-    # "tester", "testings", etc.
-    #xapian_stemming = False
-
-    # This option lets you specify a non-default directory for storing the index.
-    # If set to None (default), it gets stored into <data_dir>/cache/xapian/. 
-    #xapian_index_dir = None
-
--- a/docs/examples/config/wikiconfig.py	Fri Mar 11 23:08:35 2011 -0600
+++ b/docs/examples/config/wikiconfig.py	Sat Mar 12 14:14:22 2011 +0100
@@ -54,13 +54,8 @@
     # Site name, used by default for wiki name-logo [Unicode]
     sitename = u'Untitled Wiki'
 
-    # name of entry page / front page [Unicode], choose one of those:
-
-    # a) if most wiki content is in a single language
-    #item_root = u"MyStartingPage"
-
-    # b) if wiki content is maintained in many languages
-    #item_root = u"FrontPage"
+    # name of entry page / front page [Unicode]
+    #item_root = u"Home"
 
     # The interwiki name used in interwiki links (set it and never change!)
     #interwikiname = u'UntitledWiki'
@@ -73,7 +68,7 @@
 
     # This is checked by some rather critical and potentially harmful actions,
     # like despam or PackageInstaller action:
-    #superuser = [u"YourName", ]
+    #superusers = [u"YourName", ]
 
     # The default (ENABLED) password_checker will keep users from choosing too
     # short or too easy passwords. If you don't like this and your site has
@@ -106,7 +101,6 @@
     navi_bar = [
         # If you want to show your item_root here:
         #u'%(item_root)s',
-        u'HelpContents',
     ]
 
     # The default theme anonymous or new users get
@@ -123,13 +117,11 @@
 
     # the following regexes should match the complete name when used in free text
     # the group 'all' shall match all, while the group 'key' shall match the key only<