Commits

Thomas Waldmann  committed f022028 Merge

pulled from pkumar's repo and merged into pytest2 branch here

  • Participants
  • Parent commits 82a41c9, 66fee7b
  • Branches pytest2

Comments (0)

Files changed (19)

File MoinMoin/apps/admin/views.py

 from MoinMoin.apps.admin import admin
 from MoinMoin import user
 from MoinMoin.storage.error import NoSuchRevisionError
-from MoinMoin.config import SIZE
+from MoinMoin.config import NAME, SIZE
 from MoinMoin.config import SUPERUSER
 from MoinMoin.security import require_permission
 
     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 = [(doc[SIZE], doc[NAME])
+            for doc in flaskg.storage.documents(all_revs=False, wikiname=app.cfg.interwikiname)]
     rows = sorted(rows, reverse=True)
     return render_template('admin/itemsize.html',
                            item_name="+admin/itemsize",

File MoinMoin/apps/frontend/views.py

 Disallow: /+diffsince/
 Disallow: /+diff/
 Disallow: /+diffraw/
+Disallow: /+search
 Disallow: /+dispatch/
 Disallow: /+admin/
 Allow: /
         return True
 
 class SearchForm(Form):
-    q = String.using(optional=False).with_properties(autofocus=True, placeholder=L_("Search Query"))
+    q = String.using(optional=False, default=u'').with_properties(autofocus=True, placeholder=L_("Search Query"))
+    history = Boolean.using(label=L_('search also in non-current revisions'), optional=True)
     submit = String.using(default=L_('Search'), optional=True)
-    pagelen = String.using(optional=False)
-    search_in_all = Boolean.using(label=L_('search also in non-current revisions'), optional=True)
 
     validators = [ValidSearch()]
 
 
-def _search(search_form, item_name):
+@frontend.route('/+search', methods=['GET', 'POST'])
+def search():
+    search_form = SearchForm.from_flat(request.values)
+    valid = search_form.validate()
+    search_form['submit'].set_default() # XXX from_flat() kills all values
     query = search_form['q'].value
-    pagenum = 1  # We start from first page
-    pagelen = int(search_form['pagelen'].value)
-    all_revs = bool(request.values.get('search_in_all'))
-    qp = flaskg.storage.query_parser(["name_exact", "name", "content"], all_revs=all_revs)
-    q = qp.parse(query)
-    with flaskg.storage.searcher(all_revs) as searcher:
-        results = searcher.search_page(q, pagenum, pagelen)
-        return render_template('search_results.html',
-                               results=results,
+    if valid:
+        history = bool(request.values.get('history'))
+        qp = flaskg.storage.query_parser(["name_exact", "name", "content"], all_revs=history)
+        q = qp.parse(query)
+        with flaskg.storage.searcher(all_revs=history) as searcher:
+            results = searcher.search(q, limit=100)
+            return render_template('search.html',
+                                   results=results,
+                                   name_suggestions=u', '.join([word for word, score in results.key_terms('name', docs=20, numterms=10)]),
+                                   content_suggestions=u', '.join([word for word, score in results.key_terms('content', docs=20, numterms=10)]),
+                                   query=query,
+                                   medium_search_form=search_form,
+                                   item_name='+search', # XXX
+                                  )
+    else:
+        return render_template('search.html',
                                query=query,
                                medium_search_form=search_form,
-                               item_name=item_name,
+                               item_name='+search', # XXX
                               )
 
 
-@frontend.route('/<itemname:item_name>', defaults=dict(rev=-1), methods=['GET', 'POST'])
-@frontend.route('/+show/<int:rev>/<itemname:item_name>', methods=['GET', 'POST'])
+
+@frontend.route('/<itemname:item_name>', defaults=dict(rev=-1), methods=['GET'])
+@frontend.route('/+show/<int:rev>/<itemname:item_name>', methods=['GET'])
 def show_item(item_name, rev):
-    # first check whether we have a valid search query:
-    search_form = SearchForm.from_flat(request.values)
-    if search_form.validate():
-        return _search(search_form, item_name)
-    search_form['submit'].set_default() # XXX from_flat() kills all values
-
     flaskg.user.addTrail(item_name)
     item_displayed.send(app._get_current_object(),
                         item_name=item_name)
                               data_rendered=Markup(item._render_data()),
                               show_revision=show_revision,
                               show_navigation=show_navigation,
-                              search_form=search_form,
+                              search_form=SearchForm.from_defaults(),
                              )
     return Response(content, status)
 
     :type item_name: unicode
     :returns: a page with all the items which link or transclude item_name
     """
-    refs_here = _backrefs(flaskg.storage.iteritems(), item_name)
+    refs_here = _backrefs(item_name)
     return render_template('item_link_list.html',
                            item_name=item_name,
                            headline=_(u'Refers Here'),
                           )
 
 
-def _backrefs(items, item_name):
+def _backrefs(item_name):
     """
     Returns a list with all names of items which ref item_name
 
-    :param items: all the items
-    :type items: iteratable sequence
     :param item_name: the name of the item transcluded or linked
     :type item_name: unicode
     :returns: the list of all items which ref item_name
     """
-    from MoinMoin.search.indexing import WhooshIndex
-    from whoosh.query import Term, Or
-    index_object = WhooshIndex()
-    ix = index_object.latest_revisions_index
-    with ix.searcher() as searcher:
-        q = Or([Term("itemtransclusions", item_name), Term("itemlinks", item_name)])
-        results = searcher.search(q)
-        return [result["name"] for result in results]
+    q = And([Term("wikiname", app.cfg.interwikiname),
+             Or([Term("itemtransclusions", item_name), Term("itemlinks", item_name)])])
+    docs = flaskg.storage.search(q, all_revs=False)
+    return [doc["name"] for doc in docs]
+
 
 @frontend.route('/+history/<itemname:item_name>')
 def history(item_name):
     :rtype: tuple
     :returns: start word, end word, matches dict
     """
-    item_names = [item.name for item in flaskg.storage.iteritems()]
+    item_names = [doc[NAME] for doc in flaskg.storage.documents(all_revs=False, wikiname=app.cfg.interwikiname)]
     if item_name in item_names:
         item_names.remove(item_name)
     # Get matches using wiki way, start and end of word

File MoinMoin/apps/misc/views.py

 """
 
 
-import time
-
 from flask import Response
 from flask import current_app as app
 from flask import g as flaskg
 
 from MoinMoin.apps.misc import misc
 
+from MoinMoin.config import NAME, MTIME
 from MoinMoin.themes import render_template
 from MoinMoin import wikiutil
-from MoinMoin.storage.error import NoSuchRevisionError, NoSuchItemError
 
 SITEMAP_HAS_SYSTEM_ITEMS = True
 
     """
     Google (and others) XML sitemap
     """
-    def format_timestamp(ts):
-        return time.strftime("%Y-%m-%dT%H:%M:%S+00:00", time.gmtime(ts))
+    def format_timestamp(dt):
+        return dt.strftime("%Y-%m-%dT%H:%M:%S+00:00")
 
     sitemap = []
-    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
-        if wikiutil.isSystemItem(item.name):
+    for doc in flaskg.storage.documents(all_revs=False, wikiname=app.cfg.interwikiname):
+        name = doc[NAME]
+        mtime = doc[MTIME]
+        if False: # was: wikiutil.isSystemItem(name)   XXX add back later, when we have that in the index
             if not SITEMAP_HAS_SYSTEM_ITEMS:
                 continue
             # system items are rather boring
             # these are the content items:
             changefreq = "daily"
             priority = "0.5"
-        sitemap.append((item.name, format_timestamp(rev.timestamp), changefreq, priority))
+        sitemap.append((name, format_timestamp(mtime), changefreq, priority))
     # add an entry for root url
-    try:
-        item = flaskg.storage.get_item(app.cfg.item_root)
-        rev = item.get_revision(-1)
-        sitemap.append((u'', format_timestamp(rev.timestamp), "hourly", "1.0"))
-    except NoSuchItemError:
-        pass
+    root_item = app.cfg.item_root
+    docs = list(flaskg.storage.documents(all_revs=False, wikiname=app.cfg.interwikiname, name=root_item))
+    if docs:
+        mtime = docs[0][MTIME]
+        sitemap.append((u'', format_timestamp(mtime), "hourly", "1.0"))
     sitemap.sort()
     content = render_template('misc/sitemap.xml', sitemap=sitemap)
     return Response(content, mimetype='text/xml')
     can implement SisterWiki functionality easily.
     See: http://usemod.com/cgi-bin/mb.pl?SisterSitesImplementationGuide
     """
-    # XXX we currently also get user items, fix this
-    item_names = sorted([item.name for item in flaskg.storage.iteritems()])
+    # XXX we currently also get trash items, fix this
+    item_names = sorted([doc[NAME] for doc in flaskg.storage.documents(all_revs=False, wikiname=app.cfg.interwikiname)])
     content = render_template('misc/urls_names.txt', item_names=item_names)
     return Response(content, mimetype='text/plain')
 

File MoinMoin/converter/__init__.py

         return self._register(self.Entry(factory, type_input, type_output, priority))
 
 
-from ..util.mime import Type, type_moin_document
-
-from MoinMoin.config import NAME, CONTENTTYPE
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-
-def convert_to_indexable(rev):
-    """
-    convert a revision to an indexable document
-
-    :param rev: item revision - please make sure that the content file is
-                ready to read all indexable content from it. if you have just
-                written that content or already read from it, you need to call
-                rev.seek(0) before calling convert_to_indexable(rev).
-    """
-    try:
-        # TODO use different converter mode?
-        # Maybe we want some special mode for the input converters so they emit
-        # different output than for normal rendering), esp. for the non-markup
-        # content types (images, etc.).
-        input_contenttype = rev[CONTENTTYPE]
-        output_contenttype = 'text/plain'
-        type_input_contenttype = Type(input_contenttype)
-        type_output_contenttype = Type(output_contenttype)
-        reg = default_registry
-        # first try a direct conversion (this could be useful for extraction
-        # of (meta)data from binary types, like from images or audio):
-        conv = reg.get(type_input_contenttype, type_output_contenttype)
-        if conv:
-            doc = conv(rev, input_contenttype)
-            return doc
-        # otherwise try via DOM as intermediate format (this is useful if
-        # input type is markup, to get rid of the markup):
-        input_conv = reg.get(type_input_contenttype, type_moin_document)
-        output_conv = reg.get(type_moin_document, type_output_contenttype)
-        if input_conv and output_conv:
-            doc = input_conv(rev, input_contenttype)
-            # We do not convert smileys, includes, macros, links, because
-            # it does not improve search results or even makes results worse.
-            doc = output_conv(doc)
-            return doc
-        # no way
-        raise TypeError("No converter for %s --> %s" % (input_contenttype, output_contenttype))
-    except Exception as e: # catch all exceptions, we don't want to break an indexing run
-        logging.exception("Exception happened in conversion of item %r rev %d contenttype %s:" % (rev[NAME], rev.revno, rev[CONTENTTYPE]))
-        doc = u'ERROR [%s]' % str(e)
-        return doc
-
-
 default_registry = RegistryConverter()
 load_package_modules(__name__, __path__)
 

File MoinMoin/items/__init__.py

             newrev[CONTENTTYPE] = unicode(contenttype_current or contenttype_guessed or 'application/octet-stream')
 
         newrev[ACTION] = unicode(action)
-        self.before_revision_commit(newrev, data)
         storage_item.commit()
         item_modified.send(app._get_current_object(), item_name=name)
         return new_rev_no, size
 
-    def before_revision_commit(self, newrev, data):
-        """
-        hook that can be used to add more meta data to a revision before
-        it is committed.
-
-        :param newrev: new (still uncommitted) revision - modify as wanted
-        :param data: either str or open file (we can avoid having to read/seek
-                     rev's data with this)
-        """
-        remote_addr = request.remote_addr
-        if remote_addr:
-            if app.cfg.log_remote_addr:
-                newrev[ADDRESS] = unicode(remote_addr)
-                hostname = wikiutil.get_hostname(remote_addr)
-                if hostname:
-                    newrev[HOSTNAME] = hostname
-        if flaskg.user.valid:
-            newrev[USERID] = unicode(flaskg.user.id)
-
     def get_index(self):
         """ create an index of sub items of this item """
         if self.name:
     some kind of item with markup
     (internal links and transcluded items)
     """
-    def before_revision_commit(self, newrev, data):
-        """
-        add ITEMLINKS and ITEMTRANSCLUSIONS metadata
-        """
-        super(MarkupItem, self).before_revision_commit(newrev, data)
-
-        if hasattr(data, "read"):
-            data.seek(0)
-            data = data.read()
-        elif isinstance(data, str):
-            pass
-        else:
-            raise StorageError("unsupported content object: %r" % data)
-
-        from MoinMoin.converter import default_registry as reg
-
-        input_conv = reg.get(Type(self.contenttype), type_moin_document)
-        item_conv = reg.get(type_moin_document, type_moin_document, items='refs')
-
-        i = Iri(scheme='wiki', authority='', path='/' + self.name)
-
-        doc = input_conv(self.rev, self.contenttype)
-        doc.set(moin_page.page_href, unicode(i))
-        doc = item_conv(doc)
-
-        newrev[ITEMLINKS] = item_conv.get_links()
-        newrev[ITEMTRANSCLUSIONS] = item_conv.get_transclusions()
 
 
 class MoinWiki(MarkupItem):

File MoinMoin/items/_tests/test_Item.py

         assert u'<pre class="highlight">test_data\n' in result
         assert item2.data == ''
 
-class TestMarkupItem(object):
-    """ Test for the items with markup """
-
-    def test_before_revision_commit(self):
-        item_name = u'Markup_Item'
-        item = MarkupItem.create(item_name)
-        contenttype = u'text/x.moin.wiki;charset=utf-8'
-        meta = {CONTENTTYPE: contenttype}
-        item._save(meta)
-        item1 = MarkupItem.create(item_name)
-        MarkupItem.before_revision_commit(item1, item.rev, 'test_data')
-        assert item.rev['itemlinks'] == []
-        assert item.rev['itemtransclusions'] == []
-
 coverage_modules = ['MoinMoin.items']
 

File MoinMoin/script/maint/index.py

 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError
 from MoinMoin.util.mime import Type
 from MoinMoin.search.indexing import backend_to_index
-from MoinMoin.converter import convert_to_indexable
+from MoinMoin.storage.backends.indexing import convert_to_indexable
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
             Building in app.cfg.index_dir_tmp
             """
             indexnames = [indexname for indexname, schema in indexnames_schemas]
-            with MultiSegmentWriter(all_rev_index, procs, limitmb) as all_rev_writer:
-                with MultiSegmentWriter(latest_rev_index, procs, limitmb) as latest_rev_writer:
+            if procs == 1:
+                # MultiSegmentWriter sometimes has issues and is pointless for procs == 1,
+                # so use the simple writer when --procs 1 is given:
+                _all_rev_writer = all_rev_index.writer()
+                _latest_rev_writer = latest_rev_index.writer()
+            else:
+                _all_rev_writer = MultiSegmentWriter(all_rev_index, procs, limitmb)
+                _latest_rev_writer = MultiSegmentWriter(latest_rev_index, procs, limitmb)
+            with _all_rev_writer as all_rev_writer:
+                with _latest_rev_writer as latest_rev_writer:
                     for item in backend.iter_items_noindex():
                         try:
                             rev_no = None
                         except NoSuchRevisionError: # item has no such revision
                             continue
                         # revision is now the latest revision of this item
-                        if "latest_revisions_index" in indexnames and rev_no:
+                        if "latest_revisions_index" in indexnames and rev_no is not None:
                             metadata = backend_to_index(revision, rev_no, latest_rev_schema, rev_content, interwikiname)
                             latest_rev_writer.add_document(**metadata)
 

File MoinMoin/search/analyzers.py

         Tokenizer behaviour:
 
         Input: u"text/x.moin.wiki;charset=utf-8"
-        Output: u"text", u"x.moin.wiki", u"charset=utf-8"
+        Output: u"text/x.moin.wiki;charset=utf-8", u"text", u"x.moin.wiki", u"charset=utf-8"
 
         Input: u"application/pdf"
-        Output: u"application", u"pdf"
+        Output: u"application/pdf", u"application", u"pdf"
 
         :param value: String for tokenization
         :param start_pos: The position number of the first token. For example,

File MoinMoin/search/indexing.py

         self._index_dir = index_dir or self._cfg.index_dir
 
         common_fields = dict(
+            # wikiname so we can have a shared index in a wiki farm, always check this!
+            # taken from app.cfg.interwikiname
             wikiname=ID(stored=True),
+            # tokenized NAME from metadata - use this for manual searching from UI
             name=TEXT(stored=True, multitoken_query="and", analyzer=item_name_analyzer(), field_boost=2.0),
+            # unmodified NAME from metadata - use this for precise lookup by the code.
+            # also needed for wildcard search, so the original string as well as the query
+            # (with the wildcard) is not cut into pieces.
             name_exact=ID(field_boost=3.0),
+            # revision number, integer 0..n
             rev_no=NUMERIC(stored=True),
+            # MTIME from revision metadata (converted to UTC datetime)
             mtime=DATETIME(stored=True),
+            # tokenized CONTENTTYPE from metadata
             contenttype=TEXT(stored=True, multitoken_query="and", analyzer=MimeTokenizer()),
+            # unmodified list of TAGS from metadata
             tags=ID(stored=True),
+            # LANGUAGE from metadata
             language=ID(stored=True),
+            # USERID from metadata
             userid=ID(stored=True),
+            # ADDRESS from metadata
             address=ID(stored=True),
+            # HOSTNAME from metadata
             hostname=ID(stored=True),
+            # SIZE from metadata
             size=NUMERIC(stored=True),
+            # ACTION from metadata
             action=ID(stored=True),
+            # tokenized COMMENT from metadata
             comment=TEXT(stored=True, multitoken_query="and"),
+            # data (content), converted to text/plain and tokenized
             content=TEXT(stored=True, multitoken_query="and"),
         )
+        latest_revs_fields = dict(
+            # UUID from metadata - as there is only latest rev of same item here, it is unique
+            uuid=ID(unique=True, stored=True),
+            # unmodified list of ITEMLINKS from metadata
+            itemlinks=ID(stored=True),
+            # unmodified list of ITEMTRANSCLUSIONS from metadata
+            itemtransclusions=ID(stored=True),
+            # tokenized ACL from metadata
+            acl=TEXT(analyzer=AclTokenizer(self._cfg), multitoken_query="and", stored=True),
+            **common_fields
+        )
 
-        self.latest_revisions_schema = Schema(uuid=ID(unique=True, stored=True),
-                                              itemlinks=ID(stored=True),
-                                              itemtransclusions=ID(stored=True),
-                                              acl=TEXT(analyzer=AclTokenizer(self._cfg), multitoken_query="and", stored=True),
-                                              **common_fields)
+        all_revs_fields = dict(
+            # UUID from metadata
+            uuid=ID(stored=True),
+            **common_fields
+        )
 
-        self.all_revisions_schema = Schema(uuid=ID(stored=True),
-                                           **common_fields)
+        self.latest_revisions_schema = Schema(**latest_revs_fields)
+        self.all_revisions_schema = Schema(**all_revs_fields)
 
         # Define dynamic fields
-        dynamic_fields = [("*_id", ID),
-                          ("*_text", TEXT),
-                          ("*_keyword", KEYWORD),
-                          ("*_numeric", NUMERIC),
-                          ("*_datetime", DATETIME),
-                          ("*_boolean", BOOLEAN)
+        dynamic_fields = [("*_id", ID(stored=True)),
+                          ("*_text", TEXT(stored=True)),
+                          ("*_keyword", KEYWORD(stored=True)),
+                          ("*_numeric", NUMERIC(stored=True)),
+                          ("*_datetime", DATETIME(stored=True)),
+                          ("*_boolean", BOOLEAN(stored=True)),
                          ]
 
         # Adding dynamic fields to schemas

File MoinMoin/storage/backends/fileserver.py

-# Copyright: 2008-2010 MoinMoin:ThomasWaldmann
+# Copyright: 2008-2011 MoinMoin:ThomasWaldmann
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
 """
-    MoinMoin - file server backend
+MoinMoin - file server backend
 
-    You can use this backend to directly get read-only access to your
-    wiki server's filesystem.
+You can use this backend to directly get read-only access to your
+wiki server's filesystem.
 
-    TODO: nearly working, but needs more work at other places,
-          e.g. in the router backend, to be useful.
+TODO: nearly working, but needs more work at other places,
+      e.g. in the router backend, to be useful.
 """
 
 
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError
 from MoinMoin.util.mimetype import MimeType
 
-from MoinMoin.config import ACL, CONTENTTYPE, ACTION, COMMENT, MTIME, SIZE
+from MoinMoin.config import NAME, ACL, CONTENTTYPE, ACTION, COMMENT, MTIME, SIZE, HASH_ALGORITHM
 
 class FSError(Exception):
     """ file serving backend error """
 
     def iter_items_noindex(self):
         for dirpath, dirnames, filenames in os.walk(self.root_dir):
-            yield DirItem(self, self._path2item(dirpath))
+            name = self._path2item(dirpath)
+            if name:
+                # XXX currently there is an issue with whoosh indexing if fileserver
+                # backend is mounted at / and the item name is empty, resulting in a
+                # completely empty item name - avoid this for now.
+                yield DirItem(self, name)
             for filename in filenames:
                 try:
                     item = FileItem(self, self._path2item(os.path.join(dirpath, filename)))
         filepath = item._fs_filepath
         st = item._fs_stat
         meta = { # make something up
+            NAME: item.name,
             MTIME: int(st.st_mtime),
-            ACTION: 'SAVE',
+            ACTION: u'SAVE',
             SIZE: st.st_size,
+            HASH_ALGORITHM: u'' # XXX fake it, send_file needs it for etag and crashes ithout the hash
         }
         self._fs_meta = meta
         self._fs_data_fname = filepath
     def __init__(self, item, revno):
         FileDirRevision.__init__(self, item, revno)
         self._fs_meta.update({
-            CONTENTTYPE: 'text/x.moin.wiki',
+            CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8',
         })
         # create a directory "page" in wiki markup:
         try:
         FileDirRevision.__init__(self, item, revno)
         contenttype = MimeType(filename=self._fs_data_fname).content_type()
         self._fs_meta.update({
-            CONTENTTYPE: contenttype,
+            CONTENTTYPE: unicode(contenttype),
         })
 

File MoinMoin/storage/backends/indexing.py

 from uuid import uuid4
 make_uuid = lambda: unicode(uuid4().hex)
 
+from flask import current_app as app
+from flask import g as flaskg
+from flask import request
+
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, \
                                    AccessDeniedError
-from MoinMoin.config import ACL, CONTENTTYPE, UUID, NAME, NAME_OLD, MTIME, TAGS
+from MoinMoin.config import ACL, CONTENTTYPE, UUID, NAME, NAME_OLD, MTIME, TAGS, \
+                            ADDRESS, HOSTNAME, USERID, ITEMLINKS, ITEMTRANSCLUSIONS
 from MoinMoin.search.indexing import backend_to_index
-from MoinMoin.converter import convert_to_indexable
+from MoinMoin.converter import default_registry
+from MoinMoin.util.iri import Iri
+from MoinMoin.util.mime import Type, type_moin_document
+from MoinMoin.util.tree import moin_page
+from MoinMoin import wikiutil
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
+
+def convert_to_indexable(rev, new_rev=False):
+    """
+    convert a revision to an indexable document
+
+    :param rev: item revision - please make sure that the content file is
+                ready to read all indexable content from it. if you have just
+                written that content or already read from it, you need to call
+                rev.seek(0) before calling convert_to_indexable(rev).
+    """
+    try:
+        # TODO use different converter mode?
+        # Maybe we want some special mode for the input converters so they emit
+        # different output than for normal rendering), esp. for the non-markup
+        # content types (images, etc.).
+        input_contenttype = rev[CONTENTTYPE]
+        output_contenttype = 'text/plain'
+        type_input_contenttype = Type(input_contenttype)
+        type_output_contenttype = Type(output_contenttype)
+        reg = default_registry
+        # first try a direct conversion (this could be useful for extraction
+        # of (meta)data from binary types, like from images or audio):
+        conv = reg.get(type_input_contenttype, type_output_contenttype)
+        if conv:
+            doc = conv(rev, input_contenttype)
+            return doc
+        # otherwise try via DOM as intermediate format (this is useful if
+        # input type is markup, to get rid of the markup):
+        input_conv = reg.get(type_input_contenttype, type_moin_document)
+        refs_conv = reg.get(type_moin_document, type_moin_document, items='refs')
+        output_conv = reg.get(type_moin_document, type_output_contenttype)
+        if input_conv and output_conv:
+            doc = input_conv(rev, input_contenttype)
+            # We do not convert smileys, includes, macros, links, because
+            # it does not improve search results or even makes results worse.
+            # We do run the referenced converter, though, to extract links and
+            # transclusions.
+            if new_rev:
+                # we only can modify new, uncommitted revisions, not stored revs
+                i = Iri(scheme='wiki', authority='', path='/' + rev[NAME])
+                doc.set(moin_page.page_href, unicode(i))
+                refs_conv(doc)
+                # side effect: we update some metadata:
+                rev[ITEMLINKS] = refs_conv.get_links()
+                rev[ITEMTRANSCLUSIONS] = refs_conv.get_transclusions()
+            doc = output_conv(doc)
+            return doc
+        # no way
+        raise TypeError("No converter for %s --> %s" % (input_contenttype, output_contenttype))
+    except Exception as e: # catch all exceptions, we don't want to break an indexing run
+        logging.exception("Exception happened in conversion of item %r rev %d contenttype %s:" % (rev[NAME], rev.revno, rev[CONTENTTYPE]))
+        doc = u'ERROR [%s]' % str(e)
+        return doc
+
+
 class IndexingBackendMixin(object):
     """
     Backend indexing support / functionality using the index.
         """
         update the index, removing everything related to this item
         """
-        logging.debug("item %r remove index!" % (self.name, ))
-        self._index.remove_item(metas=self)
+        uuid = self[UUID]
+        logging.debug("item %r %r remove index!" % (self.name, uuid))
+        self._index.remove_item(uuid)
 
 
 class IndexingRevisionMixin(object):
         name = self.item.name
         uuid = self.item[UUID]
         revno = self.revno
+        logging.debug("Processing: name %s revno %s" % (name, revno))
         if MTIME not in self:
             self[MTIME] = int(time.time())
         if NAME not in self:
             self[UUID] = uuid # do we want the item's uuid in the rev's metadata?
         if CONTENTTYPE not in self:
             self[CONTENTTYPE] = u'application/octet-stream'
-        metas = self
+
+        if app.cfg.log_remote_addr:
+            remote_addr = request.remote_addr
+            if remote_addr:
+                self[ADDRESS] = unicode(remote_addr)
+                hostname = wikiutil.get_hostname(remote_addr)
+                if hostname:
+                    self[HOSTNAME] = hostname
+        try:
+            if flaskg.user.valid:
+                self[USERID] = unicode(flaskg.user.id)
+        except:
+            # when loading xml via script, we have no flaskg.user
+            pass
+
+        self.seek(0) # for a new revision, file pointer points to EOF, rewind first
+        rev_content = convert_to_indexable(self, new_rev=True)
+
         logging.debug("item %r revno %d update index:" % (name, revno))
-        for k, v in metas.items():
+        for k, v in self.items():
             logging.debug(" * rev meta %r: %r" % (k, v))
-        self._index.add_rev(uuid, revno, metas)
+        logging.debug("Indexable content: %r" % (rev_content[:250], ))
+        self._index.add_rev(uuid, revno, self, rev_content)
 
     def remove_index(self):
         """
         """
         # XXX we do not have an index for item metadata yet!
 
-    def remove_item(self, metas):
+    def remove_item(self, uuid):
         """
         remove all data related to this item and all its revisions from the index
         """
         with self.index_object.latest_revisions_index.searcher() as latest_revs_searcher:
-            doc_number = latest_revs_searcher.document_number(uuid=metas[UUID],
-                                                              name_exact=metas[NAME],
+            doc_number = latest_revs_searcher.document_number(uuid=uuid,
                                                               wikiname=self.wikiname
                                                              )
         if doc_number is not None:
                 async_writer.delete_document(doc_number)
 
         with self.index_object.all_revisions_index.searcher() as all_revs_searcher:
-            doc_numbers = list(all_revs_searcher.document_numbers(uuid=metas[UUID],
-                                                                  name_exact=metas[NAME],
+            doc_numbers = list(all_revs_searcher.document_numbers(uuid=uuid,
                                                                   wikiname=self.wikiname
                                                                  ))
         if doc_numbers:
                 for doc_number in doc_numbers:
                     async_writer.delete_document(doc_number)
 
-    def add_rev(self, uuid, revno, rev):
+    def add_rev(self, uuid, revno, rev, rev_content):
         """
         add a new revision <revno> for item <uuid> with metadata <metas>
         """
             latest_found_document = latest_revs_searcher.document(uuid=rev[UUID],
                                                                   wikiname=self.wikiname
                                                                  )
-        logging.debug("Processing: name %s revno %s" % (rev[NAME], revno))
-        rev.seek(0) # for a new revision, file pointer points to EOF, rewind first
-        rev_content = convert_to_indexable(rev)
-        logging.debug("Indexable content: %r" % (rev_content[:250], ))
         if not all_found_document:
             schema = self.index_object.all_revisions_index.schema
             with AsyncWriter(self.index_object.all_revisions_index) as async_writer:
                                                                      rev_no=revno,
                                                                      wikiname=self.wikiname
                                                                     )
+        if latest_doc_number is not None:
+            with AsyncWriter(self.index_object.latest_revisions_index) as async_writer:
+                logging.debug("Latest revisions: removing %d", latest_doc_number)
+                async_writer.delete_document(latest_doc_number)
+
         with self.index_object.all_revisions_index.searcher() as all_revs_searcher:
             doc_number = all_revs_searcher.document_number(uuid=uuid,
                                                            rev_no=revno,
             with AsyncWriter(self.index_object.all_revisions_index) as async_writer:
                 logging.debug("All revisions: removing %d", doc_number)
                 async_writer.delete_document(doc_number)
-        if latest_doc_number is not None:
-            with AsyncWriter(self.index_object.latest_revisions_index) as async_writer:
-                logging.debug("Latest revisions: removing %d", latest_doc_number)
-                async_writer.delete_document(latest_doc_number)
 
     def query_parser(self, default_fields, all_revs=False):
         if all_revs:

File MoinMoin/templates/layout.html

 <div id="moin-header">
 {% block header %}
     {% if search_form %} 
-    {{ gen.form.open(search_form, id='moin-searchform', method='get', action=url_for('frontend.show_item', item_name=item_name)) }}
+    {{ gen.form.open(search_form, id='moin-searchform', method='get', action=url_for('frontend.search')) }}
         <div>
-            {{ gen.input(search_form['q'], type='search', id='moin-search-query', size='30') }}
-            {{ gen.input(search_form['submit'], type='submit') }}
-            {{ gen.input(search_form['pagelen'], type='hidden', value='25') }}
+            {{ gen.input(search_form['q'], type='search', id='moin-search-query', size='40') }}
+            {{ gen.input(search_form['submit'], type='submit', id='moin-search-submit') }}
             {{ forms.render_errors(search_form) }}
         </div>
     {{ gen.form.close() }}

File MoinMoin/templates/search.html

+{% extends theme("layout.html") %}
+{% import "utils.html" as utils %}
+{% block content %}
+    {% if results is defined %}
+    <p class="searchstats">
+        {% if results %}
+        {{ _("%(result_len)d results found (%(runtime).3f secs).",
+              result_len=results|length, runtime=results.runtime
+            )
+        }}
+        {% else %}
+        {{ _("No results found (%(runtime).3f secs).", runtime=results.runtime) }}
+        {% endif %}
+    </p>
+    {% endif %}
+    {{ gen.form.open(medium_search_form, id='moin-long-searchform', method='get', action=url_for('frontend.search')) }}
+        <div>
+            {{ gen.input(medium_search_form['q'], type='search', id='moin-search-query') }}
+            {{ gen.input(medium_search_form['submit'], type='submit') }}
+            {{ forms.render_field_without_markup(gen, medium_search_form['history'], 'checkbox') }}
+            {{ forms.render_errors(medium_search_form) }}
+        </div>
+    {{ gen.form.close() }}
+    {% if results is defined %}
+    <div>
+        <p>{{ _("name term suggestions: %(termlist)s", termlist=name_suggestions) }}</p>
+        <p>{{ _("content term suggestions: %(termlist)s", termlist=content_suggestions) }}</p>
+    </div>
+    <div class="searchresults">
+        <table>
+            {% for result in results %}
+                {% if result['wikiname'] == cfg.interwikiname %}
+                    <tr>
+                        <td class="moin-wordbreak">{{ result.pos + 1 }}
+                        <a href="{{ url_for_item(item_name=result['name'], wiki_name='Self', rev=result['rev_no']) }}"><b>{{ result['name'] }}</b></a>
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>
+                            <p class="info searchhitinfobar">{{ _("Revision: %(rev_no)d Last Change: %(mtime)s", rev_no=result['rev_no'], mtime=result['mtime']|datetimeformat) }}</p>
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>
+                            {% if user.may.read(result['name']) %}
+                                <p class="info foundtext">{{ result.highlights('content')|safe }}</p>
+                            {% else %}
+                                <p class="info foundtext">{{ _("You don't have read permission for this item.") }}</p>
+                            {% endif %}
+                        </td>
+                    </tr>
+                {% else %}
+                    <tr>
+                        <td class="moin-wordbreak">{{ result.pos + 1 }}
+                        <a class="moin-interwiki" href="{{ url_for_item(item_name=result['name'], wiki_name=result['wikiname'], rev=result['rev_no']) }}"><b>{{ "%s:%s" % (result['wikiname'], result['name']) }}</b></a>
+                        </td>
+                    </tr>
+                {% endif %}
+            {% endfor %}
+        </table>
+    </div>
+    {% endif %}
+{% endblock %}

File MoinMoin/templates/search_results.html

-{% extends theme("layout.html") %}
-{% import "utils.html" as utils %}
-{% block content %}
-    {% if results %}
-    <p class="searchstats">
-        {{ _("Result: Page %(start_page)d of %(end_page)d.
-              Showing results %(start_result)d - %(end_result)d of %(result_len)d (%(runtime).3f secs)",
-              start_page=results.pagenum, end_page=results.pagecount,
-              start_result=results.offset + 1, end_result=results.offset + results.pagelen,
-              result_len=results|length, runtime=results.results.runtime
-            )
-        }}
-    {% endif %}
-    {{ gen.form.open(medium_search_form, id='moin-long-searchform', method='get', action=url_for('frontend.show_item', item_name=item_name)) }}
-        <div>
-            {{ gen.input(medium_search_form['q'], type='search', id='moin-search-query') }}
-            {{ gen.input(medium_search_form['submit'], type='submit') }}
-            {{ gen.input(medium_search_form['pagelen'], type='hidden', value='25') }}
-            {{ forms.render_field_without_markup(gen, medium_search_form['search_in_all'], 'checkbox') }}
-            {{ forms.render_errors(medium_search_form) }}
-        </div>
-    {{ gen.form.close() }}
-    </p>
-        {% if results %}
-        <div class="searchresults">
-        <table>
-            {% for result in results %}
-                {% if result['wikiname'] == cfg.interwikiname %}
-                    <tr>
-                        <td class="moin-wordbreak">{{ result.pos + 1 }}
-                        <a href="{{ url_for_item(item_name=result['name'], wiki_name='Self', rev=result['rev_no']) }}"><b>{{ result['name'] }}</b></a>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td>
-                            <p class="info searchhitinfobar">{{ _("Revision: %(rev_no)d Last Change: %(mtime)s", rev_no=result['rev_no'], mtime=result['mtime']|datetimeformat) }}</p>
-                        </td>
-                    </tr>
-                    <tr>
-                        <td>
-                            {% if user.may.read(result['name']) %}
-                                <p class="info foundtext">{{ result.highlights('content')|safe }}</p>
-                            {% else %}
-                                <p class="info foundtext">{{ _("You don't have read permission for this item.") }}</p>
-                            {% endif %}
-                        </td>
-                    </tr>
-                {% else %}
-                    <tr>
-                        <td class="moin-wordbreak">{{ result.pos + 1 }}
-                        <a class="moin-interwiki" href="{{ url_for_item(item_name=result['name'], wiki_name=result['wikiname'], rev=result['rev_no']) }}"><b>{{ "%s:%s" % (result['wikiname'], result['name']) }}</b></a>
-                        </td>
-                    </tr>
-                {% endif %}
-            {% endfor %}
-        </table>
-        </div>
-    {% else %}
-        <h1>{{ _("No results for '%(query)s'", query=query) }}</h1>
-    {% endif %}
-{% endblock %}

File MoinMoin/themes/modernized/static/css/common.css

             line-height: 1.12em; }
 
 /* moin-header searchform */
-#moin-searchform { margin: 8px .5em; padding: 0; font-size: 0.82em; float: right;  text-align: left; }
+#moin-searchform { margin: 8px .5em; padding: 0; font-size: 0.82em; float: right;  text-align: right; }
 #moin-searchform input { font-size: 100%; vertical-align: middle;
             background-color: #F3F7FD; /* same as body bg col */
             border: 1px solid #A4B9DF; }
+#moin-searchform #moin-search-submit { display: none; }
 #moin-searchform div { margin: 0; }
 #moin-searchform ul { border: 1px solid #A4B9DF; margin: 0; padding: 0; background-color: #F3F7FD; }
 #moin-searchform li { list-style:none; }

File docs/admin/index.rst

-====================
-Working with indexes
-====================
+=======
+Indexes
+=======
+
+General
+=======
+moin strongly relies on indexes that accelerate access to item metadata and data.
+Indexes are used internally for many operations like item lookup, history,
+iterating over items, search (also for interactive searches), etc..
+
+So, you need to configure indexing correctly first.
+
+moin will automatically update the index when items are created, updated, deleted,
+destroyed or renamed (via the storage api of moin):
+* when users change content via the user interface
+* when xml is unserialized to load items into the backend
+
+In case you have items changing without usage of the backend api or in case
+your index gets damaged or lost, you need to manually do an index build -
+or moin won't be able to work correctly.
+
 Configuration
 =============
-For correct script working you need ``index_dir`` and ``index_dir_tmp`` in
-your wiki config. They have default values and most likely you don't want to change
+Your wiki config needs ``index_dir`` and ``index_dir_tmp`` to point to different
+directories. They have default values and most likely you don't need to change
 them.
 
 But if you want, try something like::
 
-      index_dir = "/path/to/moin-2.0/wiki/index/"
-      index_dir_tmp = "/path/to/moin-2.0/wiki/tmp_build/"
+      index_dir = "/path/to/moin-2.0/wiki/index"
+      index_dir_tmp = "/path/to/moin-2.0/wiki/tmp_build"
 
 **Note:** Paths MUST BE absolute.
 
-For using one index by multiple wikis (wiki farm) you must set up ``interwikiname``
-parameter in your wiki config:
 
-Example::
-
-        interwikiname = u'MyWiki'
-
-**Note:** For correct working interwikiname must be unique for each wiki.
-
-Offline index manipulation
-==========================
-The main goal of offline index manipulation is to let wiki admin build, update, clean,
-move and monitor state of indexes.
+moin index script reference
+===========================
+You can use the ``moin index`` script to build, update, clean, move and monitor
+indexes.
 
 MoinMoin uses 2 indexes: ``latest-revs`` (index stores only current revisions)
 and ``all-revs`` (index stores all revisions).
 
 Build
 -----
-Just build fresh indexes using moin backend.
+Index all revisions of all items to the index located in ``index_dir_tmp`` (we
+use this separate location so that index building does not affect the index
+your wiki engine is currently using).
+
+If there is no index at that location yet, a new index will be built there.
+If there is already an index at that location, that index will be extended.
 
 Example::
 
     moin index --for <indexname> --action build
 
-Indexes will be built under ``index_dir_tmp`` so index building happens without
-affecting the index your wiki engine uses currently.
+**Note:** moin won't use this index until you have moved it to ``index_dir``.
+
+Move
+----
+Move indexes from ``index_dir_tmp`` to ``index_dir``.
+
+Example::
+
+    moin index --for <indexname> --action move
 
 Update
 ------
-Update indexes to reflect the current backend contents. Add new stuff, remove
-outdated stuff.
+Update the index located in ``index_dir`` to reflect the current backend
+contents. Add new stuff, remove outdated stuff.
 
 Example::
 
     moin index --for <indexname> --action update
 
-Move
-----
-Moving indexes from ``index_dir_tmp`` to ``index_dir``.
-
-Example::
-
-    moin index --for <indexname> --action move
-
 Clean
 -----
 Create empty index in ``index_dir`` for given index (previous will be erased).
 
 Show
 ----
-Showing content of index files in human readable form.
+Show contents of the index located in ``index_dir`` in human readable form.
+This is mostly used for debugging.
+
+Example::
+
+    moin index --for indexname --action show
 
 **Note:** field length limited to 40 chars.
 
 **Note:** fields without attribute ``stored=True`` are not displayed.
 
-Example::
 
-    moin index --for indexname --action show
+Building an index for a single wiki
+===================================
+Build index at separate place, move it at right place:
 
-Building wiki farm
-==================
-Wiki farm allows admins create several wikis which share one index. So users
-will be able to search in one wiki and also see results from others.
+     moin index --for both --action build
+     moin index --for both --action move
 
-Before start you must prepair your wiki config.
 
-For example, you have 3 wikis: ``Advertising``, ``Sales``, ``Engineering``
+Building an index for a wiki farm
+=================================
+If you run a wiki farm (multiple, but related wikis), you may share the index
+between the farm wikis, so farm wiki users will be able to search in one wiki
+and also see results from the others.
+
+Before start you must prepare your wiki configs.
+
+For example, imagine some company uses 2 farm wikis: ``Sales``, ``Engineering``
 
 So, wiki configs will be looking like 
 
-wikiconfig.py for ``Advertising``::
+wiki config for ``Sales``::
 
-      index_dir = "/path/to/wiki/index/"
-      index_dir_tmp = "/path/to/wiki/tmp_build/"
-      interwikiname = u"Adverising"
+      interwikiname = u"Sales"
+      index_dir = "/path/to/wiki/index"
+      index_dir_tmp = "/path/to/wiki/index_tmp"
 
-wikiconfig.py for ``Sales``::
+wiki config for ``Engineering``::
 
-      index_dir = "/path/to/wiki/index/"
-      index_dir_tmp = "/path/to/wiki/tmp_build/"
-      interwikiname = u"Sales"
+      interwikiname = u"Engineering"
+      index_dir = "/path/to/wiki/index"
+      index_dir_tmp = "/path/to/wiki/index_tmp"
 
-wikiconfig.py for ``Engineering``::
+Now do the initial index building:
 
-      index_dir = "/path/to/wiki/index/"
-      index_dir_tmp = "/path/to/wiki/tmp_build/"
-      interwikiname = u"Engineering"
-
-So, after you defined configs you may start building indexes.
-
-**Note:** Do not build indexes for two or more wikis in parallel, you'll damage
-it or get traceback.
-
-You must successively build index for each wiki in appropriate virtual env and then
-move indexes from ``index_dir_tmp`` to ``index_dir``::
-
-     moin index --for both --action build # in Advertising virtual env
      moin index --for both --action build # in Sales virtual env
      moin index --for both --action build # in Engineering virtual env
      moin index --for both --action move # you can run it from any virtual env
 
-So, after that just run moin and try to search for something.
+Now you should have a shared index for all these wikis.
+
+**Note:** Do not build indexes for multiple wikis in parallel, this is not
+supported.
+
+Building indexes while your wiki is running
+===========================================
+If you want to build an index while your wiki is running, you have to be
+careful not to miss any changes that happen while you build the index.
+
+``moin index --action build`` is made to not interfere with your running wiki.
+So you can run this in parallel without taking your wiki offline.
+Depending on the size of your wiki, index build can take rather long - but it
+doesn't matter as you don't have to take your wiki offline for this.
+
+But: if indexing takes rather long, it can easily happen that content that was
+already put into the index is updated afterwards in the online wiki. So we need
+to do a quick index update while the wiki is offline:
+
+Offline your wiki (or at least make it read-only, so no data in it changes).
+
+``moin index --action move`` to move indexes into place.
+
+``moin index --action update`` to add anything we might have missed otherwise.
+As this is not as much as doing a full index build, this should be rather quick
+(but still: it has to look at every item in your wiki, whether it has been
+updated after the initial index build).
+
+Put your wiki back online again.
+
+**Note:** Indexing puts load onto your server, so if you like to do regular
+index rebuilds, schedule them at some time when your server is not too busy
+otherwise.
+

File docs/index.rst

 
    user/searching
    user/markups
+   user/search
 
 Administrating MoinMoin
 =======================

File docs/intro/features.rst

 * dump backend contents to xml
 * load backend contents from xml
 
+Search / Indexing
+=================
+* important metadata is put into index
+* content data is converted and put into index
+* fast indexed search, fast internal operations
+* flexible and powerful search queries
+* search current and historical contents
+* using a shared index, find stuff in any farm wiki
+
 User Interface
 ==============
 OO user interface
 * html5, css, javascript with jquery, svg
 * python
 * flask, flask-cache, flask-babel, flask-themes, flask-script
-* werkzeug, pygments, flatland, blinker, babel, emeraldtree, sqlalchemy, sqlite
+* whoosh, werkzeug, pygments, flatland, blinker, babel, emeraldtree, sqlalchemy, sqlite
 * optional: mercurial, postgresql, mysql
 
 

File docs/user/search.rst

+=====================
+Searching and Finding
+=====================
+
+Entering search queries
+=======================
+
+Usually there is a simple and rather short search query input field offered by
+the theme - if you submit a query from there, it will search in item names and
+content (but only in the current stuff, not in non-current revisions) and display
+the search results to you.
+
+On that search results view, you will get a bigger search query input field
+(e.g. for refining your query) and you may also choose to additionally search
+in non-current revision item revisions (selecting that will search in all
+revisions).
+
+Simple search queries
+=====================
+Just enter one or few simple words into the query input field and hit ``Enter``.
+
+If you give multiple words, it will only find documents containing ALL those
+words ("AND" is the default).
+
+You can use AND (default), OR, NOT to refine your search.
+
+Examples
+--------
+Search for "wiki"::
+
+  wiki
+
+Search for documents containing "wiki" AND "moin"::
+
+  wiki moin
+
+  or (does the same):
+
+  wiki AND moin
+
+Search for documents containing "wiki" OR "moin"::
+
+  wiki OR moin
+
+Search for documents containing "wiki" and NOT "bad"::
+
+  wiki NOT bad
+
+Using wildcards
+===============
+
+If you want to enter word fragments or if you are not sure about spelling or
+word form, use wildcards for the parts you do not know:
+
++----------------+-----------------------------------+
+| Wildcard       | Matches                           |
++----------------+-----------------------------------+
+| ``?``          | one arbitrary character           |
++----------------+-----------------------------------+
+| ``*``          | any count of arbitrary characters |
++----------------+-----------------------------------+
+
+Examples
+--------
+Search for something like wiki, wika, wikb, ...::
+
+  wik?
+
+Search for something like wiki, willi, wi, ...::
+
+  w*i
+
+You can also use it for poor man's language independant word stemming.
+
+Matches on clean, cleaner, cleanest, cleaning, ...::
+
+  clean*
+
+Searching in specific fields
+============================
+
+As long as you do not specify otherwise, moin will search in ``name``,
+``name_exact`` and ``content`` fields.
+
+To specify the field to search in, just use the `fieldname:searchterm` syntax.
+
++-----------------------+-------------------------------------------------------+
+| Field name            | Field value                                           |
++-----------------------+-------------------------------------------------------+
+| ``wikiname``          | wiki name, e.g. ITWiki, EngineeringWiki, SalesWiki    |
++-----------------------+-------------------------------------------------------+
+| ``name``              | document name, e.g. Home, MyWikiPage                  |
++-----------------------+-------------------------------------------------------+
+| ``name_exact``        | same as ``name``, but is not tokenized                |
++-----------------------+-------------------------------------------------------+
+| ``content``           | document contents, e.g. This is some example content. |
++-----------------------+-------------------------------------------------------+
+| ``contenttype``       | document type, e.g. text/plain;charset=utf-8          |
++-----------------------+-------------------------------------------------------+
+| ``tags``              | tags of the document, e.g. important, hard, todo      |
++-----------------------+-------------------------------------------------------+
+| ``language``          | (main) language of the document contents, e.g. en     |
++-----------------------+-------------------------------------------------------+
+| ``mtime``             | document modification (submission) time, 201112312359 |
++-----------------------+-------------------------------------------------------+
+| ``address``           | submitter IP address, e.g. 127.0.0.1                  |
++-----------------------+-------------------------------------------------------+
+| ``hostname``          | submitter DNS name, e.g. foo.example.org              |
++-----------------------+-------------------------------------------------------+
+| ``acl``               | access control list (see below)                       |
++-----------------------+-------------------------------------------------------+
+| ``itemlinks``         | link targets of the document, e.g. OtherItem          |
++-----------------------+-------------------------------------------------------+
+| ``itemtransclusions`` | transclusion targets of the document, e.g. OtherItem  |
++-----------------------+-------------------------------------------------------+
+
+Examples
+--------
+Search in metadata fields::
+
+  contenttype:text
+  contenttype:image/jpeg
+  tags:todo
+  mtime:201108
+  address:127.0.0.1
+  language:en
+  hostname:localhost
+
+Search items with an item ACL that explicitly gives Joe read rights::
+
+  acl:Joe:+read
+
+Limiting search to a specific wiki (in a wiki farm's shared index)::
+
+  wikiname:SomeWiki
+
+Notes
+=====
+moin uses indexed search - keep in mind that this has some special properties:
+
+ * as it is using an index, it is rather fast usually
+ * because it is only using the index, it can only find what was put there
+ * if you use wildcards, it will still use the index, but in a different, slower way
+
+E.g.:
+
+ * "foobar" is put into the index somehow
+ * you search for "ooba" - you will not find it, because only "foobar" was put into the index
+ * solution: search for "foobar" - fast and will find it
+ * solution: search for "*ooba*" - slow, but will find it
+
+More infos
+==========
+
+See the `Whoosh query language docs <http://packages.python.org/Whoosh/querylang.html>`_.
+