Commits

Thomas Waldmann committed 5281fec Merge

merged default branch into gae branch

Comments (0)

Files changed (91)

 ^.settings
 ^MANIFEST$
 .DS_Store
+^\.cache/
+^\.idea/
 .sqlite$
 .orig$
 .rej$

MoinMoin/_tests/__init__.py

         meta[NAME] = name
     if CONTENTTYPE not in meta:
         meta[CONTENTTYPE] = u'application/octet-stream'
-    rev = item.store_revision(meta, StringIO(data))
+    rev = item.store_revision(meta, StringIO(data), return_rev=True)
     return rev
 
 def create_random_string_list(length=14, count=10):

MoinMoin/_tests/test_error.py

 
     def testCreateWithObject(self):
         """ error: create with any object """
-        class Foo:
+
+        class Foo(object):
             def __unicode__(self):
                 return u'טעות'
+
             def __str__(self):
                 return 'טעות'
 
         """ error: access error like a dict """
         test = 'value'
         err = error.Error(test)
-        assert '%(message)s' % err == test
+        assert '%(message)s' % dict(message=err) == test
 
 class TestCompositeError(object):
 

MoinMoin/_tests/test_user.py

 # -*- coding: utf-8 -*-
 # Copyright: 2003-2004 by Juergen Hermann <jh@web.de>
 # Copyright: 2009 by ReimarBauer
+# Copyright: 2013 by ThomasWaldmann
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
 """
         assert u.exists()
 
 
-class TestLoginWithPassword(object):
-    """user: login tests"""
-
+class TestUser(object):
     def setup_method(self, method):
         # Save original user
         self.saved_user = flaskg.user
         # Restore original user
         flaskg.user = self.saved_user
 
+    # Passwords / Login -----------------------------------------------
+
     def testAsciiPassword(self):
         """ user: login with ascii password """
         # Create test user
         theUser = user.User(name=name, password=password)
         assert theUser.valid
 
-    def test_auth_with_ssha_stored_password(self):
+    def testInvalidatePassword(self):
+        """ user: test invalidation of password """
+        # Create test user
+        name = u'__Non Existent User Name__'
+        password = name
+        self.createUser(name, password)
+
+        # Try to "login"
+        theUser = user.User(name=name, password=password)
+        assert theUser.valid
+
+        # invalidate the stored password (hash)
+        theUser.set_password("") # emptry str or None means "invalidate"
+        theUser.save()
+
+        # Try to "login" with previous password
+        theUser = user.User(name=name, password=password)
+        assert not theUser.valid
+
+        # Try to "login" with empty password
+        theUser = user.User(name=name, password="")
+        assert not theUser.valid
+
+    def testPasswordHash(self):
         """
-        Create user with {SSHA} password and check that user can login.
+        Create user, set a specific pw hash and check that user can login
+        with the correct password and can not log in with a wrong password.
         """
         # Create test user
         name = u'Test User'
-        # pass = 12345
-        # salt = salt
-        password = '{SSHA}x4YEGdfI4i0qROaY3NTHCmwSJY5zYWx0'
-        self.createUser(name, password, True)
+        # sha512_crypt passlib hash for '12345':
+        pw_hash = '$6$rounds=1001$y9ObPHKb8cvRCs5G$39IW1i5w6LqXPRi4xqAu3OKv1UOpVKNkwk7zPnidsKZWqi1CrQBpl2wuq36J/s6yTxjCnmaGzv/2.dAmM8fDY/'
+        self.createUser(name, pw_hash, True)
 
-        # Try to "login"
+        # Try to "login" with correct password
         theuser = user.User(name=name, password='12345')
         assert theuser.valid
 
-    def test_auth_with_apr1_stored_password(self):
-        """
-        Create user with {APR1} password and check that user can login.
-        """
-        # Create test user
-        name = u'Test User'
-        # generated with "htpasswd -nbm blaze 12345"
-        password = '{APR1}$apr1$NG3VoiU5$PSpHT6tV0ZMKkSZ71E3qg.' # 12345
-        self.createUser(name, password, True)
+        # Try to "login" with a wrong password
+        theuser = user.User(name=name, password='wrong')
+        assert not theuser.valid
 
-        # Try to "login"
-        theuser = user.User(name=name, password='12345')
-        assert theuser.valid
-
-    def test_auth_with_md5_stored_password(self):
-        """
-        Create user with {MD5} password and check that user can login.
-        """
-        # Create test user
-        name = u'Test User'
-        password = '{MD5}$1$salt$etVYf53ma13QCiRbQOuRk/' # 12345
-        self.createUser(name, password, True)
-
-        # Try to "login"
-        theuser = user.User(name=name, password='12345')
-        assert theuser.valid
-
-    def test_auth_with_des_stored_password(self):
-        """
-        Create user with {DES} password and check that user can login.
-        """
-        # Create test user
-        name = u'Test User'
-        # generated with "htpasswd -nbd blaze 12345"
-        password = '{DES}gArsfn7O5Yqfo' # 12345
-        self.createUser(name, password, True)
-
-        try:
-            import crypt
-            # Try to "login"
-            theuser = user.User(name=name, password='12345')
-            assert theuser.valid
-        except ImportError:
-            pytest.skip("Platform does not provide crypt module!")
-
-    def test_auth_with_ssha256_stored_password(self):
-        """
-        Create user with {SSHA256} password and check that user can login.
-        """
-        # Create test user
-        name = u'Test User'
-        # generated with online sha256 tool
-        # pass: 12345
-        # salt: salt
-        # base64 encoded
-        password = '{SSHA256}r4ONZUfEyn9MUkcyDQkQ5MBNpdIerM24MasxFpuQBaFzYWx0'
-
-        self.createUser(name, password, True)
-
-        # Try to "login"
-        theuser = user.User(name=name, password='12345')
-        assert theuser.valid
-
-    def test_regression_user_password_started_with_sha(self):
-        # This is regression test for bug in function 'user.create_user'.
-        #
-        # This function does not encode passwords which start with '{SHA}'
-        # It treats them as already encoded SHA hashes.
-        #
-        # If user during registration specifies password starting with '{SHA}'
-        # this password will not get encoded and user object will get saved with empty enc_password
-        # field.
-        #
-        # Such situation leads to "KeyError: 'enc_password'" during
-        # user authentication.
-
-        # Any Password begins with the {SHA} symbols led to
-        # "KeyError: 'enc_password'" error during user authentication.
-        user_name = u'moin'
-        user_password = u'{SHA}LKM56'
-        user.create_user(user_name, user_password, u'moin@moinmo.in', u'')
-
-        # Try to "login"
-        theuser = user.User(name=user_name, password=user_password)
-        assert theuser.valid
+    # Subscriptions ---------------------------------------------------
 
     def testSubscriptionSubscribedPage(self):
         """ user: tests is_subscribed_to  """
         theUser.subscribe(pagename)
         assert not theUser.is_subscribed_to([testPagename]) # list(!) of pages to check
 
-    def test_upgrade_password_from_ssha_to_ssha256(self):
-        """
-        Create user with {SSHA} password and check that logging in
-        upgrades to {SSHA256}.
-        """
-        name = u'/no such user/'
-        # pass = 'MoinMoin', salt = '12345'
-        password = '{SSHA}xkDIIx1I7A4gC98Vt/+UelIkTDYxMjM0NQ=='
-        self.createUser(name, password, True)
-
-        theuser = user.User(name=name, password='MoinMoin')
-        assert theuser.enc_password[:9] == '{SSHA256}'
-
-    def test_upgrade_password_from_sha_to_ssha256(self):
-        """
-        Create user with {SHA} password and check that logging in
-        upgrades to {SSHA256}.
-        """
-        name = u'/no such user/'
-        password = '{SHA}jLIjfQZ5yojbZGTqxg2pY0VROWQ=' # 12345
-        self.createUser(name, password, True)
-
-        theuser = user.User(name=name, password='12345')
-        assert theuser.enc_password[:9] == '{SSHA256}'
-
-    def test_upgrade_password_from_apr1_to_ssha256(self):
-        """
-        Create user with {APR1} password and check that logging in
-        upgrades to {SSHA256}.
-        """
-        # Create test user
-        name = u'Test User'
-        # generated with "htpasswd -nbm blaze 12345"
-        password = '{APR1}$apr1$NG3VoiU5$PSpHT6tV0ZMKkSZ71E3qg.' # 12345
-        self.createUser(name, password, True)
-
-        theuser = user.User(name=name, password='12345')
-        assert theuser.enc_password[:9] == '{SSHA256}'
-
-    def test_upgrade_password_from_md5_to_ssha256(self):
-        """
-        Create user with {MD5} password and check that logging in
-        upgrades to {SSHA}.
-        """
-        # Create test user
-        name = u'Test User'
-        password = '{MD5}$1$salt$etVYf53ma13QCiRbQOuRk/' # 12345
-        self.createUser(name, password, True)
-
-        theuser = user.User(name=name, password='12345')
-        assert theuser.enc_password[:9] == '{SSHA256}'
-
-    def test_upgrade_password_from_des_to_ssha256(self):
-        """
-        Create user with {DES} password and check that logging in
-        upgrades to {SSHA}.
-        """
-        # Create test user
-        name = u'Test User'
-        # generated with "htpasswd -nbd blaze 12345"
-        password = '{DES}gArsfn7O5Yqfo' # 12345
-        self.createUser(name, password, True)
-
-        theuser = user.User(name=name, password='12345')
-        assert theuser.enc_password[:9] == '{SSHA256}'
-
     # Bookmarks -------------------------------------------------------
 
     def test_bookmark(self):

MoinMoin/_tests/wikiconfig.py

     interwikiname = u'MoinTest'
     interwiki_map = dict(Self='http://localhost:8080/', MoinMoin='http://moinmo.in/')
     interwiki_map[interwikiname] = 'http://localhost:8080/'
+
+    passlib_crypt_context = dict(
+        schemes=["sha512_crypt", ],
+        # for the tests, we don't want to have varying rounds
+        sha512_crypt__vary_rounds=0,
+        # for the tests, we want to have a rather low rounds count,
+        # so the tests run quickly (do NOT use low counts in production!)
+        sha512_crypt__default_rounds=1001,
+    )
     clock.stop('create_app load config')
     clock.start('create_app register')
     # register converters
-    from werkzeug.routing import PathConverter
-    app.url_map.converters['itemname'] = PathConverter
+    from werkzeug.routing import BaseConverter
+
+    class ItemNameConverter(BaseConverter):
+        """Like the default :class:`UnicodeConverter`, but it also matches
+        slashes (except at the beginning AND end).
+        This is useful for wikis and similar applications::
+
+            Rule('/<itemname:wikipage>')
+            Rule('/<itemname:wikipage>/edit')
+
+        :param map: the :class:`Map`.
+        """
+        regex = '[^/]+?(/[^/]+?)*'
+        weight = 200
+
+    app.url_map.converters['itemname'] = ItemNameConverter
     # register modules, before/after request functions
     from MoinMoin.apps.frontend import frontend
     frontend.before_request(before_wiki)

MoinMoin/apps/admin/templates/admin/index.html

 {% block content %}
 <h1>{{ _("Admin Menu") }}</h1>
 <ul>
-    <li><a href="{{ url_for('admin.userbrowser') }}">{{ _("User Browser") }}</a></li>
-    <li><a href="{{ url_for('admin.sysitems_upgrade') }}">{{ _("Upgrade system items") }}</a></li>
+    <li><a href="{{ url_for('admin.userbrowser') }}">{{ _("Users") }}</a></li>
+    <li><a href="{{ url_for('admin.sysitems_upgrade') }}">{{ _("Upgrade System Items") }}</a></li>
     <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>
+    <li><a href="{{ url_for('admin.wikiconfighelp') }}">{{ _("Wiki Configuration Help") }}</a></li>
 </ul>
 {% endblock %}

MoinMoin/apps/admin/templates/admin/sysitems_upgrade.html

 {% extends theme("layout.html") %}
 {% block content %}
-<h1>{{ _("System items upgrade") }}</h1>
+<h1>{{ _("Upgrade System Items") }}</h1>
 <p>
 {{ _("You can upgrade your system items by uploading an xml file with new items below.") }}
 </p>

MoinMoin/apps/admin/templates/admin/userbrowser.html

 {% extends theme("layout.html") %}
 {% block content %}
+    <h1>{{ _("Users") }}</h1>
     <table class="zebra">
     <tr>
         <th>{{ _("User name") }}</th>

MoinMoin/apps/admin/templates/admin/wikiconfig.html

 {% extends theme("layout.html") %}
 {% block content %}
-<h1>{{ _("Wiki configuration") }}</h1>
+<h1>{{ _("Show Wiki Configuration") }}</h1>
 <p>
 {{ _("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, "
 <table class="zebra">
 <thead>
 <tr>
-<th>{{ _('Variable name') }}</th>
+<th>{{ _('Variable Name') }}</th>
 <th>{{ _('Setting') }}</th>
 </tr>
 </thead>

MoinMoin/apps/admin/templates/admin/wikiconfighelp.html

 {% extends theme("layout.html") %}
 {% block content %}
-<h1>{{ _("WikiConfig Help") }}</h1>
+<h1>{{ _("Wiki Configuration Help") }}</h1>
 {% for heading, desc, opts in groups %}
     <h2>{{ heading }}</h2>
     {% if desc %}
     <table class="zebra">
     <thead>
     <tr>
-        <th>{{ _('Variable name') }}</th>
+        <th>{{ _('Variable Name') }}</th>
         <th>{{ _('Default') }}</th>
         <th>{{ _('Description') }}</th>
     </tr>

MoinMoin/apps/admin/templates/user/highlighterhelp.html

 {% import "utils.html" as utils %}
 {% extends theme("layout.html") %}
 {% block content %}
-<h1>{{ _("Available Highlighters") }}</h1>
+<h1>{{ _("Highlighters") }}</h1>
 {{ utils.table(headings, rows) }}
 {% endblock %}

MoinMoin/apps/admin/templates/user/index_user.html

 </ul>
 <h1>{{ _("User Menu") }}</h1>
 <ul>
+    <li><a href="{{ url_for('frontend.mychanges') }}">{{ _("My Changes") }}</a></li>
     <li><a href="{{ url_for('frontend.wanted_items') }}">{{ _("Wanted Items") }}</a></li>
     <li><a href="{{ url_for('frontend.orphaned_items') }}">{{ _("Orphaned Items") }}</a></li>
-    <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>
+    <li><a href="{{ url_for('admin.itemsize') }}">{{ _("Item Sizes (last revision)") }}</a></li>
+    <li><a href="{{ url_for('admin.interwikihelp') }}">{{ _("InterWiki Names") }}</a></li>
+    <li><a href="{{ url_for('admin.highlighterhelp') }}">{{ _("Highlighters") }}</a></li>
 </ul>
 {% endblock %}

MoinMoin/apps/admin/templates/user/interwikihelp.html

 {% import "utils.html" as utils %}
 {% extends theme("layout.html") %}
 {% block content %}
-<h1>{{ _("Known InterWiki names") }}</h1>
-{{ utils.table(headings, rows) }}
+<h1>{{ _("InterWiki Names") }}</h1>
+{{ utils.table(headings, rows, url_cols=[2]) }}
 {% endblock %}

MoinMoin/apps/admin/templates/user/itemsize.html

 {% import "utils.html" as utils %}
 {% extends theme("layout.html") %}
 {% block content %}
-<h1>{{ _("Item sizes (latest revision)") }}</h1>
-{{ utils.table(headings, rows) }}
+<h1>{{ _("Item Sizes (last revision)") }}</h1>
+{{ utils.table(headings, rows, itemname_cols=[2]) }}
 {% endblock %}

MoinMoin/apps/admin/views.py

                           groups=[groupname for groupname in groups if rev.meta[NAME] in groups[groupname]],
                      )
                      for rev in revs]
-    return render_template('admin/userbrowser.html', user_accounts=user_accounts, title_name=_(u"User Browser"))
+    return render_template('admin/userbrowser.html', user_accounts=user_accounts, title_name=_(u"Users"))
 
 
 @admin.route('/userprofile/<user_name>', methods=['GET', 'POST', ])
 
     found.sort()
     return render_template('admin/wikiconfig.html',
-                           title_name=_(u"Wiki Configuration"),
+                           title_name=_(u"Show Wiki Configuration"),
                            found=found, settings=settings)
 
 
     rows = sorted([[desc, ' '.join(names), ' '.join(patterns), ' '.join(mimetypes), ]
                    for desc, names, patterns, mimetypes in lexers])
     return render_template('user/highlighterhelp.html',
-                           title_name=_(u"Highlighter Help"),
+                           title_name=_(u"Highlighters"),
                            headings=headings,
                            rows=rows)
 
                ]
     rows = sorted(app.cfg.interwiki_map.items())
     return render_template('user/interwikihelp.html',
-                           title_name=_(u"Interwiki Help"),
+                           title_name=_(u"Interwiki Names"),
                            headings=headings,
                            rows=rows)
 
             for rev in flaskg.storage.documents(wikiname=app.cfg.interwikiname)]
     rows = sorted(rows, reverse=True)
     return render_template('user/itemsize.html',
-                           title_name=_(u"Item Size"),
+                           title_name=_(u"Item Sizes"),
                            headings=headings,
                            rows=rows)

MoinMoin/apps/frontend/views.py

 # Copyright: 2012 MoinMoin:CheerXiao
-# Copyright: 2003-2010 MoinMoin:ThomasWaldmann
+# Copyright: 2003-2013 MoinMoin:ThomasWaldmann
 # Copyright: 2011 MoinMoin:AkashSinha
 # Copyright: 2011 MoinMoin:ReimarBauer
 # Copyright: 2008 MoinMoin:FlorianKrupicka
     return item.do_show(rev)
 
 
+@frontend.route('/<itemname:item_name>/')  # note: unwanted trailing slash
 @frontend.route('/+show/<itemname:item_name>')
 def redirect_show_item(item_name):
     return redirect(url_for_item(item_name))
     :returns: a page with all the items which link or transclude item_name
     """
     my_changes = _mychanges(flaskg.user.itemid)
-    return render_template('item_link_list.html',
+    return render_template('link_list_no_item_panel.html',
                            title_name=_(u'My Changes'),
                            headline=_(u'My Changes'),
                            item_names=my_changes
     :returns: a page with all the items which link or transclude item_name
     """
     refs_here = _backrefs(item_name)
-    return render_template('item_link_list.html',
+    return render_template('link_list_item_panel.html',
                            item_name=item_name,
                            headline=_(u"Items which refer to '%(item_name)s'", item_name=item_name),
                            item_names=refs_here
     referred = linked | transcluded
     wanteds = referred - existing
     title_name = _(u'Wanted Items')
-    return render_template('item_link_list.html',
+    return render_template('link_list_no_item_panel.html',
                            headline=_(u'Wanted Items'),
                            title_name=title_name,
                            item_names=wanteds)
     referred = linked | transcluded
     orphans = existing - referred
     title_name = _('Orphaned Items')
-    return render_template('item_link_list.html',
+    return render_template('link_list_no_item_panel.html',
                            title_name=title_name,
                            headline=_(u'Orphaned Items'),
                            item_names=orphans)
     """Validator for a valid password recovery form
     """
     passwords_mismatch_msg = L_('The passwords do not match.')
-    password_encoding_problem_msg = L_('New password is unacceptable, encoding trouble.')
+    password_problem_msg = L_('New password is unacceptable, could not get processed.')
 
     def validate(self, element, state):
         if element['password1'].value != element['password2'].value:
             return self.note_error(element, state, 'passwords_mismatch_msg')
 
+        password = element['password1'].value
         try:
-            crypto.crypt_password(element['password1'].value)
-        except UnicodeError:
-            return self.note_error(element, state, 'password_encoding_problem_msg')
+            app.cfg.cache.pwd_context.encrypt(password)
+        except (ValueError, TypeError) as err:
+            return self.note_error(element, state, 'password_problem_msg')
 
         return True
 
     """
     passwords_mismatch_msg = L_('The passwords do not match.')
     current_password_wrong_msg = L_('The current password was wrong.')
-    password_encoding_problem_msg = L_('New password is unacceptable, encoding trouble.')
+    password_problem_msg = L_('New password is unacceptable, could not get processed.')
 
     def validate(self, element, state):
         if not (element['password_current'].valid and element['password1'].valid and element['password2'].valid):
         if element['password1'].value != element['password2'].value:
             return self.note_error(element, state, 'passwords_mismatch_msg')
 
+        password = element['password1'].value
         try:
-            crypto.crypt_password(element['password1'].value)
-        except UnicodeError:
-            return self.note_error(element, state, 'password_encoding_problem_msg')
+            app.cfg.cache.pwd_context.encrypt(password)
+        except (ValueError, TypeError) as err:
+            return self.note_error(element, state, 'password_problem_msg')
         return True
 
 
             rank = matches[name]
             if rank == wanted_rank:
                 item_names.append(name)
-    return render_template("item_link_list.html",
+    return render_template("link_list_item_panel.html",
                            headline=_("Items with similar names to '%(item_name)s'", item_name=item_name),
                            item_name=item_name, # XXX no item
                            item_names=item_names)
     query = And([Term(WIKINAME, app.cfg.interwikiname), Term(TAGS, tag), ])
     revs = flaskg.storage.search(query, sortedby=NAME_EXACT, limit=None)
     item_names = [rev.meta[NAME] for rev in revs]
-    return render_template("item_link_list.html",
+    return render_template("link_list_no_item_panel.html",
                            headline=_("Items tagged with %(tag)s", tag=tag),
                            item_name=tag,
                            item_names=item_names)

MoinMoin/auth/_tests/test_http.py

 
 class TestHTTPAuthMoin(object):
     """ Test: HTTPAuthMoin """
-    class Auth:
+
+    class Auth(object):
         def __init__(self):
             self.username = 'ValidUser'
             self.password = 'test_pass'

MoinMoin/config/default.py

 # -*- coding: utf-8 -*-
 # Copyright: 2000-2004 Juergen Hermann <jh@web.de>
-# Copyright: 2005-2011 MoinMoin:ThomasWaldmann
+# Copyright: 2005-2013 MoinMoin:ThomasWaldmann
 # Copyright: 2008      MoinMoin:JohannesBerg
 # Copyright: 2010      MoinMoin:DiogenesAugusto
 # Copyright: 2011      MoinMoin:AkashSinha
                 raise error.ConfigurationError("You must set a (at least {0} chars long) secret string for secrets['{1}']!".format(
                     secret_min_length, secret_key_name))
 
+        from passlib.context import CryptContext
+        try:
+            self.cache.pwd_context = CryptContext(**self.passlib_crypt_context)
+        except ValueError as err:
+            raise error.ConfigurationError("passlib_crypt_context configuration is invalid [{0}].".format(err))
+
     def _config_check(self):
         """ Check namespace and warn about unknown names
 
 #
 options_no_group_name = {
   # ==========================================================================
-  'datastruct': ('Datastruct settings', None, (
+  'datastruct': ('Datastruct', None, (
     #('dicts', lambda cfg: datastruct.ConfigDicts({}),
     ('dicts', lambda cfg: datastruct.WikiDicts(),
      "function f(cfg) that returns a backend which is used to access dicts definitions."),
      "function f(cfg) that returns a backend which is used to access groups definitions."),
   )),
   # ==========================================================================
-  'auth': ('Authentication / Authorization / Security settings', None, (
+  'auth': ('Authentication / Authorization / Security', None, (
     ('auth', DefaultExpression('[MoinAuth()]'),
      "list of auth objects, to be called in this order (see HelpOnAuthentication)"),
     ('secrets', None, """Either a long shared secret string used for multiple purposes or a dict {"purpose": "longsecretstring", ...} for setting up different shared secrets for different purposes."""),
 
     ('password_checker', DefaultExpression('_default_password_checker'),
      'checks whether a password is acceptable (default check is length >= 6, at least 4 different chars, no keyboard sequence, not username used somehow (you can switch this off by using `None`)'),
+
+    ('passlib_crypt_context', dict(
+        # schemes we want to support (or deprecated schemes for which we still have
+        # hashes in our storage).
+        # note about bcrypt: it needs additional code (that is not pure python and
+        # thus either needs compiling or installing platform-specific binaries)
+        schemes=["sha512_crypt", ],
+        # default scheme for creating new pw hashes (if not given, passlib uses first from schemes)
+        #default="sha512_crypt",
+        # deprecated schemes get auto-upgraded to the default scheme at login
+        # time or when setting a password (including doing a moin account pwreset).
+        #deprecated=["auto"],
+        # vary rounds parameter randomly when creating new hashes...
+        #all__vary_rounds=0.1,
+     ),
+     "passlib CryptContext arguments, see passlib docs"),
   )),
   # ==========================================================================
-  'spam_leech_dos': ('Anti-Spam/Leech/DOS',
+  'spam_leech_dos': ('Anti-Spam / Leech / DOS',
   'These settings help limiting ressource usage and avoiding abuse.',
   (
     ('textchas', None,
      "Time [s] for a !TextCha to expire."),
   )),
   # ==========================================================================
-  'style': ('Style / Theme / UI related',
+  'style': ('Style / Theme / UI',
   'These settings control how the wiki user interface will look like.',
   (
     ('sitename', u'Untitled Wiki',
     ('template_dirs', [], "list of directories with templates that will override theme and base templates."),
   )),
   # ==========================================================================
-  'editor': ('Editor related', None, (
+  'editor': ('Editor', None, (
     ('item_license', u'', 'if set, show the license item within the editor. [Unicode]'),
     #('edit_locking', 'warn 10', "Editor locking policy: `None`, `'warn <timeout in minutes>'`, or `'lock <timeout in minutes>'`"),
     ('edit_ticketing', True, None),
   )),
   # ==========================================================================
-  'paging': ('Paging related', None, (
+  'paging': ('Paging', None, (
     ('results_per_page', 50, "Number of results to be shown on a single page in pagination"),
   )),
   # ==========================================================================
-  'data': ('Data storage', None, (
+  'data': ('Data Storage', None, (
     ('data_dir', './data/', "Path to the data directory."),
     ('plugin_dirs', [], "Plugin directories."),
 
     ('destroy_index', False, "Destroy (empty) the index after using it."),
   )),
   # ==========================================================================
-  'items': ('Special item names', None, (
+  'items': ('Special Item Names', None, (
     ('item_root', u'Home', "Name of the root item (aka 'front page'). [Unicode]"),
 
     # the following regexes should match the complete name when used in free text
      'Item names exactly matching this regex are regarded as items containing group definitions [Unicode]'),
   )),
   # ==========================================================================
-  'user': ('User Preferences related', None, (
+  'user': ('User Preferences', None, (
     ('user_defaults',
       dict(
         name=u'anonymous',
         scroll_page_after_edit=True,
         show_comments=False,
         want_trivial=False,
+        enc_password=u'',  # empty value == invalid hash
         disabled=False,
         bookmarks={},
         quicklinks=[],
 #
 #
 options = {
-    'acl': ('Access control lists',
+    'acl': ('Access Control Lists',
     'ACLs control who may do what.',
     (
       ('functions', u'',
       ('user_homepage', 'User/', 'All user homepages are stored below this namespace.'),
     )),
 
-    'user': ('Users / User settings', None, (
+    'user': ('User', None, (
       ('email_unique', True,
        "if True, check email addresses for uniqueness and don't accept duplicates."),
       ('email_verification', False,
       ('use_gravatar', False, "if True, gravatar.com will be used to find User's avatar")
     )),
 
-    'mail': ('Mail settings',
+    'mail': ('Mail',
         'These settings control outgoing and incoming email from and to the wiki.',
     (
       ('from', None, "Used as From: address for generated mail. [Unicode]"),

MoinMoin/constants/contenttypes.py

 
 CONTENTTYPE_USER = u'application/x.moin.userprofile'
 CONTENTTYPE_DEFAULT = u'application/octet-stream'
+CONTENTTYPE_NONEXISTENT = u'application/x-nonexistent'
 
 
 GROUP_MARKUP_TEXT = 'markup text items'

MoinMoin/converter/_table.py

 from __future__ import absolute_import, division
 
 from MoinMoin.util.tree import moin_page
+from emeraldtree import ElementTree as ET
+
+WORDBREAK_LEN = 30
 
 class TableMixin(object):
     """
     Mixin to support building a DOM table.
     """
+    def add_numeric_class(self, cell, table_cell):
+        """
+        Add numeric class attribute if cell is numeric.
+        """
+        try:
+            float(cell)
+            table_cell.attrib[moin_page('class')] = 'moin-integer'
+        except:
+            pass
+
     def build_dom_table(self, rows, head=None, cls=None):
         """
         Build a DOM table with data from <rows>.
         if head is not None:
             table_head = moin_page.table_header()
             table_row = moin_page.table_row()
-            for cell in head:
-                table_cell = moin_page.table_cell(children=[cell, ])
+            for idx, cell in enumerate(head):
+                table_cell = moin_page.table_cell(children=[cell, ],)
+                if rows:
+                    # add "align: right" to heading cell if cell in first data row is numeric
+                    self.add_numeric_class(rows[0][idx], table_cell)
                 table_row.append(table_cell)
             table_head.append(table_row)
             table.append(table_head)
         for row in rows:
             table_row = moin_page.table_row()
             for cell in row:
-                table_cell = moin_page.table_cell(children=[cell, ])
+                if isinstance(cell, ET.Node) and isinstance(cell[0], unicode) and \
+                    len(cell[0].split()) == 1 and len(cell[0]) > WORDBREAK_LEN:
+                    # avoid destroying table layout by applying special styling to cells with long file name hyperlinks
+                    table_cell = moin_page.table_cell(children=[cell, ], attrib={moin_page.class_: 'moin-wordbreak'})
+                else:
+                    table_cell = moin_page.table_cell(children=[cell, ],)
+                    self.add_numeric_class(cell, table_cell)
                 table_row.append(table_cell)
             table_body.append(table_row)
         table.append(table_body)

MoinMoin/converter/_tests/test_include.py

         assert e.data == 'a(b)'
 
     def test_IncludeHandlesCircularRecursion(self):
-        # issue #80
-        # we use text/x.moin.wiki markup to make tests simple
+        # detect circular recursion and create error message
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page2}}')
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page3}}')
         update_item(u'page3', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page4}}')
         update_item(u'page4', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page2}}')
-
         page1 = Item.create(u'page1')
         rendered = page1.content._render_data()
         # an error message will follow strong tag
         assert '<strong class="moin-error">' in rendered
 
     def test_ExternalInclude(self):
+        # external include
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{http://moinmo.in}}')
         rendered = Item.create(u'page1').content._render_data()
         assert '<object class="moin-http moin-transclusion" data="http://moinmo.in" data-href="http://moinmo.in">http://moinmo.in</object>' in rendered
+        # external include embedded within text (object is an inline tag)
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'before {{http://moinmo.in}} after')
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>before <object class="moin-http moin-transclusion" data="http://moinmo.in" data-href="http://moinmo.in">http://moinmo.in</object> after</p>' in rendered
+        # external include embedded within text italic and bold markup (object is an inline tag)
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u"before ''italic '''bold {{http://moinmo.in}} bold''' italic'' normal")
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>before <em>italic <strong>bold <object class="moin-http moin-transclusion" data="http://moinmo.in" data-href="http://moinmo.in">http://moinmo.in</object> bold</strong> italic</em> normal</p>' in rendered
 
     def test_InlineInclude(self):
-        # issue #28
-        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Content of page2 is "{{page2}}".')
 
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'before {{page2}} after')
+        # transclude single paragraph as inline
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Single line')
         rendered = Item.create(u'page1').content._render_data()
-        assert '<p>Content of page2 is "<span class="moin-transclusion" data-href="/page2">Single line</span>".</p>' in rendered
-
+        assert '<p>before <span class="moin-transclusion" data-href="/page2">Single line</span> after</p>' in rendered
+        # transclude multiple paragraphs as block
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Two\n\nParagraphs')
         rendered = Item.create(u'page1').content._render_data()
-        assert '<p>Content of page2 is "</p><div class="moin-transclusion" data-href="/page2"><p>Two</p><p>Paragraphs</p></div><p>".</p></div>' in rendered
-
+        assert '<p>before </p><div class="moin-transclusion" data-href="/page2"><p>Two</p><p>Paragraphs</p></div><p> after</p></div>' in rendered
+        # transclude single paragraph with internal markup as inline
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"this text contains ''italic'' string")
         rendered = Item.create(u'page1').content._render_data()
-        assert 'Content of page2 is "<span class="moin-transclusion" data-href="/page2">this text contains <em>italic</em>' in rendered
-
+        assert 'before <span class="moin-transclusion" data-href="/page2">this text contains <em>italic</em>' in rendered
+        # transclude single paragraph as only content within a paragraph
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Content of page2 is\n\n{{page2}}')
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"Single Line")
         rendered = Item.create(u'page1').content._render_data()
         assert '<p>Content of page2 is</p><p><span class="moin-transclusion" data-href="/page2">Single Line</span></p>' in rendered
-
-        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Content of page2 is "{{page2}}"')
+        # transclude single row table within a paragraph, block element forces paragraph to be split into 2 parts
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'before {{page2}} after')
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"|| table || cell ||")
         rendered = Item.create(u'page1').content._render_data()
-        assert 'Content of page2 is "</p>' in rendered
-        assert '<table>' in rendered
+        assert '<p>before </p><div class="moin-transclusion" data-href="/page2"><table' in rendered
+        assert '</table></div><p> after</p>' in rendered
         assert rendered.count('<table>') == 1
-
-        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Content of page2 is "{{page2}}"')
+        # transclude two row table within a paragraph, block element forces paragraph to be split into 2 parts
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'before {{page2}} after')
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"|| this || has ||\n|| two || rows ||")
         rendered = Item.create(u'page1').content._render_data()
-        assert 'Content of page2 is "</p>' in rendered
-        assert '<table>' in rendered
+        # inclusion of block item within a paragraph results in a before and after p
+        assert '<p>before </p><div class="moin-transclusion" data-href="/page2"><table' in rendered
+        assert '</table></div><p> after</p>' in rendered
         assert rendered.count('<table>') == 1
+        # transclude nonexistent item
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'before {{nonexistent}} after')
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>before <span class="moin-transclusion" data-href="/nonexistent"><a href="/+modify/nonexistent">' in rendered
+        assert '</a></span> after</p>' in rendered
+        # transclude empty item
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'text {{page2}} text')
+        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"")
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>text <span class="moin-transclusion" data-href="/page2"></span> text</p>' in rendered
+    def test_InlineIncludeCreole(self):
+        # transclude single paragraph as inline using creole parser
+        update_item(u'creole', {CONTENTTYPE: u'text/x.moin.creole;charset=utf-8'}, u'creole item')
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.creole;charset=utf-8'}, u'before {{creole}} after')
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>before <span class="moin-transclusion" data-href="/creole">creole item</span> after</p>' in rendered
+    def test_InlineIncludeWithinMarkup(self):
+        # transclude single line item within italic and bold markup
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u"Normal ''italic '''bold {{page2}} bold''' italic'' normal")
+        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"Single Line")
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>Normal <em>italic <strong>bold <span class="moin-transclusion" data-href="/page2">Single Line</span> bold</strong> italic</em> normal</p>' in rendered
+        # transclude double line item within italic and bold markup
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u"Normal ''italic '''bold {{page2}} bold''' italic'' normal")
+        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"Double\n\nLine")
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>Normal <em>italic <strong>bold </strong></em></p><div class="moin-transclusion" data-href="/page2"><p>Double</p><p>Line</p></div><p><em><strong> bold</strong> italic</em> normal</p>' in rendered
+        # transclude single line item within comment
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u"comment /* before {{page2}} after */")
+        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"Single Line")
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>comment <span class="comment">before <span class="moin-transclusion" data-href="/page2">Single Line</span> after</span></p>' in rendered
+        # transclude double line item within comment
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u"comment /* before {{page2}} after */")
+        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"Double\n\nLine")
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>comment <span class="comment">before </span></p><div class="comment moin-transclusion" data-href="/page2"><p>Double</p><p>Line</p></div><p><span class="comment"> after</span></p>' in rendered
 
-    def test_InlineIncludeLogo(self):
+    def test_InlineIncludeImage(self):
         # the 3rd parameter, u'',  should be a binary string defining a png image, but it is not needed for this simple test
-        update_item(u'logo', {CONTENTTYPE: u'image/png'}, u'')
-
-        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{logo}}')
+        update_item(u'logo.png', {CONTENTTYPE: u'image/png'}, u'')
+        # simple transclusion
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{logo.png}}')
         rendered = Item.create(u'page1').content._render_data()
-        assert '<div class="moin-transclusion" data-href="/logo"><img alt="logo"' in rendered
-
-        # <p /> is not valid html5; should be <p></p>. to be valid.  Even better, there should be no empty p's.
-        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{logo}}{{logo}}')
+        assert '<p><span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src=' in rendered
+        assert '/logo.png" /></span></p>' in rendered
+        # within paragraph
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'text {{logo.png}} text')
         rendered = Item.create(u'page1').content._render_data()
+        assert '<p>text <span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src=' in rendered
+        assert '/logo.png" /></span> text</p>' in rendered
+        # within markup
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u"Normal ''italic '''bold {{logo.png}} bold''' italic'' normal")
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>Normal <em>italic <strong>bold <span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src=' in rendered
+        assert '/logo.png" /></span> bold</strong> italic</em> normal</p>' in rendered
+        # multiple transclusions
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{logo.png}}{{logo.png}}')
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p><span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src=' in rendered
+        assert '/logo.png" /></span><span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src=' in rendered
+        # check for old bug
         assert '<p />' not in rendered
         assert '<p></p>' not in rendered
+
+    def test_IncludeAsLinkAlternate(self):
+        # image as link alternate
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u"text [[page2|{{logo.png}}]] text")
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>text <a href="/page2"><span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src="' in rendered
+        assert '/logo.png" /></span></a> text</p>' in rendered
+        # link alternate with image embedded in markup
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u"text [[page2|plain '''bold {{logo.png}} bold''' plain]] text")
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>text <a href="/page2">plain <strong>bold <span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src="' in rendered
+        assert '/logo.png" /></span> bold</strong> plain</a> text</p>' in rendered
+        # nonexistent image used in link alternate
+        # XXX html validation errora: A inside A - the image alternate turns into an A-tag to create the non-existant image.  Error is easily seen.
+        # IE9, Firefox, Chrome, Safari, and Opera display this OK;  the only usable hyperlink is to create the missing image.
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u"text [[page2|{{logoxxx.png}}]] text")
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>text <a href="/page2"><span class="moin-transclusion" data-href="/logoxxx.png"><a href="/+modify/logoxxx.png">' in rendered
+        assert '</a></span></a> text</p>' in rendered
+        # image used as alternate to nonexistent page
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u"text [[page2xxx|{{logo.png}}]] text")
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>text <a class="moin-nonexistent" href="/page2xxx"><span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src="' in rendered
+        assert '/logo.png" /></span></a> text</p>' in rendered
+        # transclude block elem as link alternate to nonexistent page
+        # XXX html validation errors, block element inside A.
+        # IE9, Firefox, Chrome, Safari, and Opera display this OK;  the hyperlink is the entire div enclosing the block elem
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'text [[MyPage|{{page2}}]] text')
+        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"Double\n\nLine")
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>text <a class="moin-nonexistent" href="/MyPage"><div class="moin-transclusion" data-href="/page2"><p>Double</p><p>Line</p></div></a> text</p>' in rendered
+        # transclude empty item as link alternate to nonexistent page
+        # hyperlink will be empty span and invisible
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'text [[MyPage|{{page2}}]] text')
+        update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"")
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>text <a class="moin-nonexistent" href="/MyPage"><span class="moin-transclusion" data-href="/page2"></span></a> text</p>' in rendered
+        # transclude external page as link alternate to nonexistent page
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'text [[MyPage|{{http://moinmo.in}}]] text')
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p>text <a class="moin-nonexistent" href="/MyPage"><object class="moin-http moin-transclusion" data="http://moinmo.in" data-href="http://moinmo.in">http://moinmo.in</object></a> text</p>' in rendered

MoinMoin/converter/_tests/test_moinwiki_in.py

                 '<page><body><p>Text <emphasis>Emphasis</emphasis></p><p>Text</p></body></page>'),
             ("Text''''''Text''''",
                 '<page><body><p>TextText</p></body></page>'),
+            ("''italic '''strongitalic ''''' normal",
+                '<page><body><p><emphasis>italic <strong>strongitalic </strong></emphasis> normal</p></body></page>'),
+            ("'''strong '''''italic '''strongitalic''''' normal",
+                '<page><body><p><strong>strong </strong><emphasis>italic <strong>strongitalic</strong></emphasis> normal</p></body></page>'),
         ]
         for i in data:
             yield (self.do, ) + i
                 '<page><body><p><span font-size="120%">larger</span></p></body></page>'),
             ("--(strike through)--",
                 '<page><body><p><span text-decoration="line-through">strike through</span></p></body></page>'),
+            ("normal ~+big __underline__ big+~ normal",
+                '<page><body><p>normal <span font-size="120%">big <span text-decoration="underline">underline</span> big</span> normal</p></body></page>'),
+            ("/* normal __underline__ normal */",
+                '<page><body><p><span class="comment">normal <span text-decoration="underline">underline</span> normal</span></p></body></page>'),
             (u'&quot;',
                 '<page><body><p>"</p></body></page>'),
             (u'&#34;',

MoinMoin/converter/_util.py

         if elem:
             self.top_append(elem)
 
-    def top_check(self, *names):
+    def top_check(self, *names, **kwargs):
         """
-        Checks if the name of the top of the stack matches the parameters.
+        Check if the top of the stack name and attrib matches the parameters.
         """
-        return self._list[-1].name in names
+        attrib = kwargs.get('attrib', {})
+        return self._list[-1].name in names and set(attrib.items()).issubset(self._list[-1].elem.attrib.items())

MoinMoin/converter/_wiki_macro.py

             type = Type(name)
         else:
             type = Type(type='x-moin', subtype='format', parameters={'name': name})
-        logging.debug("parser type: %r" % type)
+        logging.debug("parser type: %r" % (type, ))
 
         elem = moin_page.part(attrib={moin_page.content_type: type})
 

MoinMoin/converter/archive_in.py

         return cls()
 
     def process_name(self, member_name):
+        name = unicode(member_name, 'utf-8')
         attrib = {
-            xlink.href: Iri(scheme='wiki', authority='', path='/'+self.item_name, query=u'do=get&member={0}'.format(member_name)),
+            xlink.href: Iri(scheme='wiki', authority='', path='/'+self.item_name, query=u'do=get&member={0}'.format(name)),
         }
-        return moin_page.a(attrib=attrib, children=[member_name, ])
+        return moin_page.a(attrib=attrib, children=[name, ])
 
     def process_datetime(self, dt):
-        return dt.isoformat()
+        return unicode(dt.isoformat(' '))
 
     def process_size(self, size):
         return unicode(size)
                          self.process_datetime(dt),
                          self.process_name(name),
                         ) for size, dt, name in contents]
-            table = self.build_dom_table(contents, head=[_("Size"), _("Date"), _("Name")], cls='zebra')
+            table = self.build_dom_table(contents, head=[_("Size"), _("Timestamp"), _("Name")], cls='zebra')
             body = moin_page.body(children=(table, ))
             return moin_page.page(children=(body, ))
         except ArchiveException as err:
             rows = []
             tf = tarfile.open(fileobj=fileobj, mode='r')
             for tinfo in tf.getmembers():
-                rows.append((
-                    tinfo.size,
-                    datetime.utcfromtimestamp(tinfo.mtime),
-                    tinfo.name,
-                ))
+                if tinfo.isfile():
+                    # display only normal files, not directories
+                    rows.append((
+                        tinfo.size,
+                        datetime.utcfromtimestamp(tinfo.mtime),
+                        tinfo.name,
+                    ))
             return rows
         except tarfile.TarError as err:
             raise ArchiveException(str(err))
             rows = []
             zf = zipfile.ZipFile(fileobj, mode='r')
             for zinfo in zf.filelist:
-                rows.append((
-                    zinfo.file_size,
-                    datetime(*zinfo.date_time), # y,m,d,h,m,s
-                    zinfo.filename,
-                ))
+                if not (zinfo.file_size == 0 and zinfo.filename.endswith('/')):
+                    # display only normal files, not directories
+                    rows.append((
+                        zinfo.file_size,
+                        datetime(*zinfo.date_time), # y,m,d,h,m,s
+                        zinfo.filename,
+                    ))
             return rows
         except (RuntimeError, zipfile.BadZipfile) as err:
             # RuntimeError is raised by zipfile stdlib module in case of

MoinMoin/converter/creole_in.py

     """
 
     def block_nowiki_lines(self, iter_content):
-        "Unescaping generator for the lines in a nowiki block"
+        """Unescaping generator for the lines in a nowiki block"""
 
         for line in iter_content:
             match = self.nowiki_end_re.match(line)
             yield line
 
     def block_nowiki_repl(self, iter_content, stack, nowiki):
-        "Handles a complete nowiki block"
+        """Handles a complete nowiki block"""
 
         stack.clear()
 

MoinMoin/converter/docbook_in.py

         # XXX: Error handling could probably be better.
         except NameSpaceError as detail:
             return self.error(str(detail))
-        return result
 
     def error(self, message):
         """

MoinMoin/converter/docbook_out.py

 logging = log.getLogger(__name__)
 
 from MoinMoin.util.tree import html, moin_page, xlink, docbook, xml
+from MoinMoin.constants.contenttypes import CONTENTTYPE_NONEXISTENT
 
 
 class Converter(object):
         """
         href = element.get(xlink.href, None)
         attrib = {}
-        mimetype = Type(_type=element.get(moin_page.type_, 'application/x-nonexistent'))
+        mimetype = Type(_type=element.get(moin_page.type_, CONTENTTYPE_NONEXISTENT))
         if href:
             attrib[docbook.fileref] = href
             if Type('image/').issupertype(mimetype):

MoinMoin/converter/html_out.py

 from MoinMoin import wikiutil
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.util.tree import html, moin_page, xlink, xml, Name
+from MoinMoin.constants.contenttypes import CONTENTTYPE_NONEXISTENT
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
         # TODO: maybe IE8 would display transcluded external pages if we could do <object... type="text/html" ...>
         href = elem.get(xlink.href, None)
         attrib = {}
-        mimetype = Type(_type=elem.get(moin_page.type_, 'application/x-nonexistent'))
+        mimetype = Type(_type=elem.get(moin_page.type_, CONTENTTYPE_NONEXISTENT))
         # Get the object type
         obj_type = self.eval_object_type(mimetype, href)
 
                 attribs = elem.attrib.copy()
                 if moin_page.page_href in attribs:
                     del attribs[moin_page.page_href]
+                if attribs and len(item) == 1:
+
+                    if item[0].tag.name in ('object', 'a'):
+                        # png, jpg, gif are objects here, will be changed to img when they are processed
+                        # transclusion is a single inline element "My pet {{bird.jpg}} flys." or "[[SomePage|{{Logo.png}}]]"
+                        return self.new_copy(html.span, item, attribs)
+
+                    elif item[0].tag.name == 'p':
+                        # transclusion is a single p-tag that can be coerced to inline  "Yes, we have {{no}} bananas."
+                        new_span = html.span(children=item[0][:])
+                        return self.new_copy(html.span, new_span, attribs)
+
+                # transclusion is a block element
                 return self.new_copy(html.div, item, attribs)
 
         raise RuntimeError('page:page need to contain exactly one page:body tag, got {0!r}'.format(elem[:]))

MoinMoin/converter/include.py

 
 Although this module is named include.py, many comments within and the moin docs
 use the word transclude as defined by http://www.linfo.org/transclusion.html, etc.
+
+Adjusting the DOM
+=================
+
+After expanding the include elements, in many cases it is necessary to adjust
+the DOM to prevent the generation of invalid HTML.  Using a simple example,
+"\n{{SomeItem}}\n", the starting DOM structure created by the moinwiki_in.py
+(or other parser) is:
+
+    Page > Body > P > Include
+
+After expansion of the Include, the structure will be:
+
+    Page > Body > P > Page > Body > (P | Div | Object |...)
+
+moinwiki_in.py (or other parser) does not adjust the DOM structure based upon
+whether the contents of the transcluded item are inline or block.  Sometime after
+include processing is complete, html_out.py will convert the transcluded
+Body > Page into a Div or Span wrapping the transclusion contents.
+
+This works well for things like "\n||mytable||{{BlockOrInline}}||\n" where
+almost any type of element is valid within a table cell's td.
+
+But without DOM adjustment, "\n{{Block}}\n" will generate invalid HTML
+because html_out.py will convert the DOM structure:
+
+    Page > Body > P > Page > Body > (Pre | Div | P, P... | ...)
+
+into:
+
+    ...<body><p><div>...</div></p></body>...
+
+where the </p> is invalid.
+
+In some cases it is desirable to coerce a transcluded small image or phrase into a
+inline element embedded within a paragraph. Here html_out.py will wrap the transclusion in
+a Span rather than a Div or convert a P-tag containing a phrase into a Span.
+
+    "My pet {{bird.jpg}} flys.", "[[SomePage|{{Logo.png}}]]" or "Yes, we have {{no}} bananas."
+
+In complex cases where a block level item is transcluded within the midst of
+several levels of text markup, such as:
+
+   "\nplain ''italic '''bold {{BlockItem}} bold''' italic'' plain\n"
+
+then we must avoid generating invalid html like:
+
+    <p>plain <emphasis>italic <strong>bold <div>
+    ...</div> bold</strong> italic</emphasis> plain</p>
+
+where <div...</div> contains the transcluded item, but rather:
+
+    <p>plain <emphasis>italic <strong>bold</strong></emphasis></p><div>
+    ...</div><p><emphasis><strong> bold</strong> italic</emphasis> plain</p>
+
+In these complex cases, we must build a DOM structure that will replace
+the containing element's parent, grand-parent, great-grand-parent...
+
+When a block element is embedded within a comment, it is important that the
+class="comment" is copied to the transclusion to provide the show/hide and
+highlighted styles normally applied to comments.
+
+    "\n/* normal ''italic ~-small {{detail.csv}} small-~ italic'' normal */\n".
+
+Conveniently, the class="comment" is added to the span element within the
+moinwiki_in.py parser and is available to include.py.  However, the moin-big
+and moin-small classes are applied to span elements by html_out.py so those
+classes are not available.  Italic, bold, stroke, and underline styling
+effects are implemented through specialized tags rather than CSS classes.
+In the example above, only class="comment" will be applied to detail.csv.
 """
 
 
 from __future__ import absolute_import, division
 
 from emeraldtree import ElementTree as ET
-import re, types
+import re, types, copy
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
 from MoinMoin.items import Item
 from MoinMoin.util.mime import type_moin_document
 from MoinMoin.util.iri import Iri, IriPath
-from MoinMoin.util.tree import moin_page, xinclude, xlink
+from MoinMoin.util.tree import html, moin_page, xinclude, xlink
 
 from MoinMoin.converter.html_out import mark_item_as_transclusion, Attributes
 
+# elements generated by moin wiki markup that cannot have block children
+NO_BLOCK_CHILDREN = [
+        'p',
+        'span', # /*comment*/, ~+big+~, ~-small-~ via classes comment, moin-big, moin-small
+        'emphasis', # ''italic''
+        'strong', # '''bold'''
+        'del', # --(stroke)--
+        'ins', # __underline__
+        # 'sub', # ,,subscript,, # no markup allowed within subscripts
+        # 'sup', # ^superscript^ # no markup allowed within superscripts
+        'a', # [[SomeItem|{{logo.png}}]]
+        ]
+
 
 class XPointer(list):
     """
             return cls()
 
     def recurse(self, elem, page_href):
-        # on first call, elem.tag.name=='page'. Decendants (body, div, p, include, page, etc.) are processed by recursing through DOM
+        # on first call, elem.tag.name=='page'. Descendants (body, div, p, include, page, etc.) are processed by recursing through DOM
 
         # stack is used to detect transclusion loops
         page_href_new = elem.get(self.tag_page_href)
             while i < len(elem):
                 child = elem[i]
                 if isinstance(child, ET.Node):
-                    # almost everything in the DOM will be an ET.Node, exceptions are unicode nodes under p nodes
 
                     ret = self.recurse(child, page_href)
 
                     if ret:
-                        # "Normally" we are here because child.tag.name==include and ret is a transcluded item (ret.tag.name=page, image, or object, etc.)
-                        # that must be inserted into the DOM replacing elem[i].
-                        # This is complicated by the DOM having many inclusions, such as "\n{{SomePage}}\n" that are a child of a "p".
-                        # To prevent generation of invalid HTML5 (e.g. "<p>text<p>text</p></p>"), the DOM must be adjusted.
-                        if isinstance(ret, types.ListType):
-                            # the transclusion may be a return of the container variable from below, add to DOM replacing the current node
-                            elem[i:i+1] = ret
-                        elif elem.tag.name == 'p':
-                            # ancestor P nodes with tranclusions  have special case issues, we may need to mangle the ret
+                        # Either child or a descendant of child is a transclusion.
+                        # See top of this script for notes on why these DOM adjustmenta are required.
+                        if isinstance(ret, ET.Node) and elem.tag.name in NO_BLOCK_CHILDREN:
                             body = ret[0]
-                            # check for instance where ret is a page, ret[0] a body, ret[0][0] a P
-                            if not isinstance(body, unicode) and ret.tag.name == 'page' and body.tag.name == 'body' and \
-                                len(body) == 1 and body[0].tag.name == 'p':
-                                # special case:  "some text {{SomePage}} more text" or "\n{{SomePage}}\n" where SomePage contains a single p.
-                                # the content of the transcluded P will be inserted directly into ancestor P.
-                                p = body[0]
+                            if len(body) == 0:
+                                # the transcluded item is empty, insert an empty span into DOM
+                                attrib = Attributes(ret).convert()
+                                elem[i] = ET.Element(moin_page.span, attrib=attrib)
+                            elif isinstance(body[0], ET.Node) and (len(body) > 1 or body[0].tag.name not in ('p', 'object', 'a')):
+                                # Complex case: "some text {{BlockItem}} more text" or "\n{{BlockItem}}\n" where
+                                # the BlockItem body contains multiple p's, a table, preformatted text, etc.
+                                # These block elements cannot be made a child of the current elem, so we create
+                                # a container to replace elem.
+                                # Create nodes to hold any siblings before and after current child (elem[i])
+                                before = copy.deepcopy(elem)
+                                after = copy.deepcopy(elem)
+                                before[:] = elem[0:i]
+                                after[:] = elem[i+1:]
+                                if len(before):
+                                    # there are siblings before transclude, save them in container
+                                    container.append(before)
+                                new_trans_ptr = len(container)
                                 # get attributes from page node; we expect {class: "moin-transclusion"; data-href: "http://some.org/somepage"}
                                 attrib = Attributes(ret).convert()
-                                # make new span node and "convert" p to span by copying all of p's children
-                                span = ET.Element(moin_page.span, attrib=attrib, children=p[:])
-                                # insert the new span into the DOM replacing old include, page, body, and p elements
-                                elem[i] = span
-                            elif not isinstance(body, unicode) and ret.tag.name == 'page' and body.tag.name == 'body':
-                                # special case: "some text {{SomePage}} more text" or "\n{{SomePage}}\n" and SomePage body contains multiple p's, a table, preformatted text, etc.
-                                # note: ancestor P may have text before or after include
-                                if i > 0:
-                                    # there is text before transclude, make new p node to hold text before include and save in container
-                                    pa = ET.Element(moin_page.p)
-                                    pa[:] = elem[0:i]
-                                    container.append(pa)
-                                # get attributes from page node; we expect {class: "moin-transclusion"; data-href: "http://some.org/somepage"}
-                                attrib = Attributes(ret).convert()
-                                # make new div node, copy all of body's children, and save in container
+                                # make new div node to hold transclusion, copy children, and save in container
                                 div = ET.Element(moin_page.div, attrib=attrib, children=body[:])
-                                container.append(div)
-                                 # empty elem of siblings that were just placed in container
-                                elem[0:i+1] = []
-                                if len(elem) > 0:
-                                    # there is text after transclude, make new p node to hold text, copy siblings, save in container
-                                    pa = ET.Element(moin_page.p)
-                                    pa[:] = elem[:]
-                                    container.append(pa)
-                                    elem[:] = []
-                                # elem is now empty so while loop will terminate and container will be returned up one level in recursion
+                                container.append(div) # new_trans_ptr is index to this
+                                if len(after):
+                                    container.append(after)
+                                if elem.tag.name == 'a':
+                                    # invalid input [[MyPage|{{BlockItem}}]], best option is to retain A-tag and fail html validation
+                                    # TODO: error may not be obvious to user - add error message
+                                    elem[i] = div
+                                else:
+                                    # move up 1 level in recursion where elem becomes the child and is usually replaced by container
+                                    return [container, new_trans_ptr]
                             else:
-                                # ret may be a unicode string: take default action
+                                # default action for odd things like circular transclusion error messages
                                 elem[i] = ret
+                        elif isinstance(ret, types.ListType):
+                            # a container has been returned. Note: there are two places where a container may be returned
+                            ret_container, trans_ptr = ret
+                            # trans_ptr points to the transclusion within ret_container.
+                            # Here the transclusion will always contain a block level element
+                            if elem.tag.name in NO_BLOCK_CHILDREN:
+                                # Complex case, transclusion effects grand-parent, great-grand-parent, e.g.:
+                                # "/* comment {{BlockItem}} */" or  "text ''italic {{BlockItem}} italic'' text"
+                                # elem is an inline element, build a bigger container to replace elem's parent,
+                                before = copy.deepcopy(elem)
+                                after = copy.deepcopy(elem)
+                                before[:] = elem[0:i] + ret_container[0:trans_ptr]
+                                after[:] = ret_container[trans_ptr+1:] + elem[i+1:]
+                                if len(before):
+                                    container.append(before)
+                                new_trans_ptr = len(container)
+                                # child may have classes like "comment" that must be added to transcluded element
+                                classes = child.attrib.get(moin_page.class_, '').split()
+                                classes += ret_container[trans_ptr].attrib.get(html.class_, '').split() # this must be html, not moin_page
+                                ret_container[trans_ptr].attrib[html.class_] = ' '.join(classes) # this must be html, not moin_page
+                                container.append(ret_container[trans_ptr]) # the transclusion
+                                if len(after):
+                                    container.append(after)
+                                return [container, new_trans_ptr]
+                            else:
+                                # elem is a block element, replace child element with the container generated in lower recursion
+                                elem[i:i+1] = ret_container # elem[i] is the child
+                                # avoid duplicate recursion over nodes already processed
+                                i += len(ret_container) -1
                         else:
-                            # default action for any ret not fitting special cases above
+                            # default action for any ret not fitting special cases above, e.g. tranclusion is within a table cell
                             elem[i] = ret
+                # we are finished with this child, advance to next sibling
                 i += 1
-            if len(container) > 0:
-                return container
 
         finally:
             self.stack.pop()

MoinMoin/converter/mediawiki_in.py

     """
 
     def block_table_lines(self, iter_content):
-        "Unescaping generator for the lines in a table block"
+        """Unescaping generator for the lines in a table block"""
         for line in iter_content:
             match = self.table_end_re.match(line)
             if match:

MoinMoin/converter/moinwiki_in.py

     # Matches the possibly escaped end of a nowiki block
 
     def block_nowiki_lines(self, iter_content, marker_len):
-        "Unescaping generator for the lines in a nowiki block"
+        """Unescaping generator for the lines in a nowiki block"""
 
         for line in iter_content:
             match = self.nowiki_end_re.match(line)
                 else:
                     stack.push(moin_page.strong())
             elif stack.top_check('strong'):
-                if stack.top_check('strong'):
+                stack.pop()
+                if stack.top_check('emphasis'):
                     stack.pop()
                 else:
-                    stack.push(moin_page.strong())
+                    stack.push(moin_page.emphasis())
             else:
                 if len(emphstrong_follow) == 3:
                     stack.push(moin_page.emphasis())
     """
 
     def inline_underline_repl(self, stack, underline):
-        if not stack.top_check('span'):
-            attrib = {moin_page.text_decoration: 'underline'}
+        attrib = {moin_page.text_decoration: 'underline'}
+        if stack.top_check('span', attrib=attrib):
+            stack.pop()
+        else:
             stack.push(moin_page.span(attrib=attrib))
-        else:
-            stack.pop()
 
     inline_link = r"""
         (?P<link>

MoinMoin/converter/moinwiki_out.py

 
 
 class Moinwiki(object):
-    '''
+    """
     Moinwiki syntax elements
     It's dummy
-    '''
+    """
     h = u'='
     a_open = u'[['
     a_separator = u'|'

MoinMoin/converter/nonexistent_in.py

 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.util.iri import Iri
 from MoinMoin.util.tree import moin_page, xlink
+from MoinMoin.constants.contenttypes import CONTENTTYPE_NONEXISTENT
 
 
 class Converter(object):
 
 from . import default_registry
 from MoinMoin.util.mime import Type, type_moin_document
-default_registry.register(Converter._factory, Type('application/x-nonexistent'), type_moin_document)
+default_registry.register(Converter._factory, Type(CONTENTTYPE_NONEXISTENT), type_moin_document)

MoinMoin/converter/rst_out.py

                 line = [u'+']
                 for col in range(len(cols)):
                     if self.table[row][col][1] > 1:
-                        line.append(' '*cols[col])
+                        line.append(u' '*cols[col])
                     elif row == self.header_count - 1:
                         line.append(u'='*cols[col])
                     else:

MoinMoin/items/__init__.py

 
 from jinja2 import Markup
 
-from whoosh.query import Term, And, Prefix
+from whoosh.query import Term, Prefix, And, Or, Not
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
     CONTENTTYPE, SIZE, ACTION, ADDRESS, HOSTNAME, USERID, COMMENT,
     HASH_ALGORITHM, ITEMID, REVID, DATAID, CURRENT, PARENTID
     )
-from MoinMoin.constants.contenttypes import charset
+from MoinMoin.constants.contenttypes import charset, CONTENTTYPE_NONEXISTENT
+from MoinMoin.constants.itemtypes import (
+    ITEMTYPE_NONEXISTENT, ITEMTYPE_USERPROFILE, ITEMTYPE_DEFAULT,
+    )
 
 from .content import content_registry, Content, NonExistentContent, Draw
 
     def __init__(self, item, itemtype=None, contenttype=None):
         self.item = item
         self.meta = {
-            ITEMTYPE: itemtype or u'nonexistent',
-            CONTENTTYPE: contenttype or u'application/x-nonexistent'
+            ITEMTYPE: itemtype or ITEMTYPE_NONEXISTENT,
+            CONTENTTYPE: contenttype or CONTENTTYPE_NONEXISTENT
         }
         self.data = StringIO('')
         self.revid = None
         return form
 
 
+UNKNOWN_ITEM_GROUP = "unknown items"
+
+def _build_contenttype_query(groups):
+    """
+    Build a Whoosh query from a list of contenttype groups.
+    """
+    queries = []
+    for g in groups:
+        for e in content_registry.groups[g]:
+            ct_unicode = unicode(e.content_type)
+            queries.append(Term(CONTENTTYPE, ct_unicode))
+            queries.append(Prefix(CONTENTTYPE, ct_unicode + u';'))
+    return Or(queries)
+
 IndexEntry = namedtuple('IndexEntry', 'relname meta')
 
 MixedIndexEntry = namedtuple('MixedIndexEntry', 'relname meta hassubitems')
                                              action=unicode(action),
                                              contenttype_current=contenttype_current,
                                              contenttype_guessed=contenttype_guessed,
+                                             return_rev=True,
                                              )
         item_modified.send(app._get_current_object(), item_name=name)
         return newrev.revid, newrev.meta[SIZE]
 
         return dirs, files
 
-    @timed()
-    def filter_index(self, index, startswith=None, selected_groups=None):
-        """
-        Filter a list of IndexEntry.
+    def build_index_query(self, startswith=None, selected_groups=None):
+        prefix = self.subitems_prefix
+        if startswith:
+            query = Prefix(NAME_EXACT, prefix + startswith) | Prefix(NAME_EXACT, prefix + startswith.swapcase())
+        else:
+            query = Prefix(NAME_EXACT, prefix)
 
-        :param startswith: if set, only items whose names start with startswith
-                           are selected.
-        :param selected_groups: if set, only items whose contentypes belong to
-                                the selected contenttype_groups are selected.
-        """
-        if startswith is not None:
-            index = [e for e in index
-                     if e.relname.startswith((startswith, startswith.swapcase()))]
+        if selected_groups:
+            selected_groups = set(selected_groups)
+            has_unknown = UNKNOWN_ITEM_GROUP in selected_groups
+            if has_unknown:
+                selected_groups.remove(UNKNOWN_ITEM_GROUP)
+            ct_query = _build_contenttype_query(selected_groups)
+            if has_unknown:
+                ct_query |= Not(_build_contenttype_query(content_registry.groups))
+            query &= ct_query
 
-        def build_contenttypes(groups):
-            contenttypes = []
-            for g in groups:
-                entries = content_registry.groups.get(g, []) # .get is a temporary workaround for "unknown items" group
-                contenttypes.extend([e.content_type for e in entries])
-            return contenttypes
-
-        def contenttype_match(tested, cts):
-            for ct in cts:
-                if ct.issupertype(tested):
-                    return True
-            return False
-
-        if selected_groups is not None:
-            selected_contenttypes = build_contenttypes(selected_groups)
-            filtered_index = [e for e in index if contenttype_match(Type(e.meta[CONTENTTYPE]), selected_contenttypes)]
-
-            unknown_item_group = "unknown items"
-            if unknown_item_group in selected_groups:
-                all_contenttypes = build_contenttypes(content_registry.group_names)
-                filtered_index.extend([e for e in index
-                                       if not contenttype_match(Type(e.meta[CONTENTTYPE]), all_contenttypes)])
-
-            index = filtered_index
-        return index
+        return query
 
     def get_index(self, startswith=None, selected_groups=None):
-        dirs, files = self.make_flat_index(self.get_subitem_revs())
-        return dirs, self.filter_index(files, startswith, selected_groups)
+        query = Term(WIKINAME, app.cfg.interwikiname) & self.build_index_query(startswith, selected_groups)
+        revs = flaskg.storage.search(query, sortedby=NAME_EXACT, limit=None)
+        return self.make_flat_index(revs)
 
     def get_mixed_index(self):
         dirs, files = self.make_flat_index(self.get_subitem_revs())
     """
     A "conventional" wiki item.
     """
-    itemtype = u'default'
+    itemtype = ITEMTYPE_DEFAULT
     display_name = L_('Default')
     description = L_('Wiki item')
     order = -10
     Currently userprofile is implemented as a contenttype. This is a stub of an
     itemtype implementation of userprofile.
     """
-    itemtype = u'userprofile'
+    itemtype = ITEMTYPE_USERPROFILE
     display_name = L_('User profile')
     description = L_('User profile item (not implemented yet!)')
 
     A dummy Item for nonexistent items (when modifying, a nonexistent item with
     undetermined itemtype)
     """
-    itemtype = u'nonexistent'
+    itemtype = ITEMTYPE_NONEXISTENT
     shown = False
 
     def _convert(self, doc):

MoinMoin/items/_tests/test_Item.py

 
         # test Item.make_flat_index
         # TODO: test Item.get_subitem_revs
-        dirs, files = baseitem.make_flat_index(baseitem.get_subitem_revs())
+        dirs, files = baseitem.get_index()
         assert dirs == build_index(basename, [u'cd', u'ij'])
         assert files == build_index(basename, [u'ab', u'gh', u'ij', u'mn'])
 
             (u'mn', False),
         ])
 
-        # test Item.filter_index
         # check filtered index when startswith param is passed
-        filtered_files = baseitem.filter_index(files, startswith=u'a')
-        assert filtered_files == build_index(basename, [u'ab'])
+        dirs, files = baseitem.get_index(startswith=u'a')
+        assert dirs == []
+        assert files == build_index(basename, [u'ab'])
 
         # check filtered index when contenttype_groups is passed
-        ctgroups = ["image items"]
-        filtered_files = baseitem.filter_index(files, selected_groups=ctgroups)
-        assert filtered_files == build_index(basename, [u'mn'])
-
-        # If we ask for text/plain type, should Foo/cd be returned?
+        ctgroups = ["other text items"]
+        dirs, files = baseitem.get_index(selected_groups=ctgroups)
+        assert dirs == build_index(basename, [u'cd', u'ij'])
+        assert files == build_index(basename, [u'ab', u'gh', u'ij'])
 
     def test_meta_filter(self):
         name = u'Test_item'

MoinMoin/items/content.py

 from MoinMoin.forms import File
 from MoinMoin.constants.contenttypes import (
     GROUP_MARKUP_TEXT, GROUP_OTHER_TEXT, GROUP_IMAGE, GROUP_AUDIO, GROUP_VIDEO,
-    GROUP_DRAWING, GROUP_OTHER,
+    GROUP_DRAWING, GROUP_OTHER, CONTENTTYPE_NONEXISTENT,
     )
 from MoinMoin.constants.keys import (
     NAME, NAME_EXACT, WIKINAME, CONTENTTYPE, SIZE, TAGS, HASH_ALGORITHM
 
         def __lt__(self, other):
             if isinstance(other, self.__class__):
-                if self.content_type != other.content_type:
-                    return other.content_type.issupertype(self.content_type)
-                if self.priority != other.priority:
-                    return self.priority < other.priority
-                return False
+                # Within the registry, content_type is sorted in descending
+                # order (more specific first) while priority is in ascending
+                # order (smaller first).
+                return (other.content_type, self.priority) < (self.content_type, other.priority)
             return NotImplemented
 
     def __init__(self, group_names):
         return doc
 
     def _render_data(self):
-        from MoinMoin.converter import default_registry as reg
-        # TODO: Real output format
-        doc = self.internal_representation()
-        doc = self._expand_document(doc)
-        flaskg.clock.start('conv_dom_html')