Commits

Thomas Waldmann committed 2334273 Merge

merged default branch into namespaces branch

Comments (0)

Files changed (215)

MoinMoin/_tests/__init__.py

 """
 
 
-import os, shutil
-import socket, errno
+import socket
 from StringIO import StringIO
 
-from flask import current_app as app
 from flask import g as flaskg
 
-from MoinMoin import config, security, user
-from MoinMoin.config import NAME, CONTENTTYPE
+from MoinMoin.constants.contenttypes import CHARSET
+from MoinMoin.constants.keys import NAME, CONTENTTYPE
 from MoinMoin.items import Item
 from MoinMoin.util.crypto import random_string
-from MoinMoin.storage.error import ItemAlreadyExistsError
 
 # Promoting the test user -------------------------------------------
 # Usually the tests run as anonymous user, but for some stuff, you
 # need more privs...
 
+
 def become_valid(username=u"ValidUser"):
     """ modify flaskg.user to make the user valid.
         Note that a valid user will only be in ACL special group "Known", if
 def update_item(name, meta, data):
     """ creates or updates an item  """
     if isinstance(data, unicode):
-        data = data.encode(config.charset)
+        data = data.encode(CHARSET)
     item = flaskg.storage[name]
 
     meta = meta.copy()
     rev = item.store_revision(meta, StringIO(data), return_rev=True)
     return rev
 
+
 def create_random_string_list(length=14, count=10):
     """ creates a list of random strings """
     chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
     return [u"{0}".format(random_string(length, chars)) for counter in range(count)]
 
+
 def nuke_item(name):
     """ complete destroys an item """
     item = Item.create(name)

MoinMoin/_tests/_test_template.py

     )
 
     from MoinMoin._tests import wikiconfig
+
     class Config(wikiconfig.Config):
         foo = 'bar'  # we want to have this non-default setting
 

MoinMoin/_tests/ldap_testbase.py

 SLAPD_EXECUTABLE = 'slapd'  # filename of LDAP server executable - if it is not
                             # in your PATH, you have to give full path/filename.
 
-import os, shutil, tempfile, time, base64
+import os
+import shutil
+import tempfile
+import time
+import base64
 from StringIO import StringIO
 import signal
 import subprocess
     def __init__(self,
                  config=None,  # config filename for -f
                  executable=SLAPD_EXECUTABLE,
-                 debug_flags='', # None,  # for -d stats,acl,args,trace,sync,config
+                 debug_flags='',  # None,  # for -d stats,acl,args,trace,sync,config
                  proto='ldap', ip='127.0.0.1', port=3890,  # use -h proto://ip:port
                  service_name=''  # defaults to -n executable:port, use None to not use -n
                 ):
         self.proto = proto
         self.ip = ip
         self.port = port
-        self.url = '{0}://{1}:{2}'.format(proto, ip, port) # can be used for ldap.initialize() call
+        self.url = '{0}://{1}:{2}'.format(proto, ip, port)  # can be used for ldap.initialize() call
         if service_name == '':
             self.service_name = '{0}:{1}'.format(executable, port)
         else:
         started = None
         if timeout:
             lo = ldap.initialize(self.url)
-            ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) # ldap v2 is outdated
+            ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)  # ldap v2 is outdated
             started = False
             wait_until = time.time() + timeout
             while time.time() < wait_until:
 
     def start_slapd(self):
         """ start a slapd and optionally wait until it talks with us """
-        self.slapd = Slapd(config=self.slapd_conf, port=3890+self.instance)
+        self.slapd = Slapd(config=self.slapd_conf, port=3890 + self.instance)
         started = self.slapd.start(timeout=self.timeout)
         return started
 
     def load_directory(self, ldif_content):
         """ load the directory with the ldif_content (str) """
         lo = ldap.initialize(self.slapd.url)
-        ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) # ldap v2 is outdated
+        ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)  # ldap v2 is outdated
         lo.simple_bind_s(self.rootdn, self.rootpw)
 
         class LDIFLoader(ldif.LDIFParser):

MoinMoin/_tests/test_error.py

 """
 
 
-import pytest
-
 from MoinMoin import error
 
 

MoinMoin/_tests/test_test_environ.py

 
 from StringIO import StringIO
 
-import pytest
-
 from flask import current_app as app
 from flask import g as flaskg
 
-from MoinMoin.conftest import init_test_app, deinit_test_app
-from MoinMoin.config import NAME, CURRENT, CONTENTTYPE, IS_SYSITEM, SYSITEM_VERSION
-from MoinMoin.storage.error import NoSuchItemError
+from MoinMoin.constants.keys import NAME, CONTENTTYPE
 
 from MoinMoin._tests import wikiconfig
 

MoinMoin/_tests/test_user.py

 """
 
 
-import pytest
-
-from flask import current_app as app
 from flask import g as flaskg
 
 from MoinMoin import user
-from MoinMoin.util import crypto
 
 
 class TestSimple(object):

MoinMoin/_tests/test_wikiutil.py

 
 from flask import current_app as app
 
-from MoinMoin import config, wikiutil
-from MoinMoin._tests import wikiconfig
+from MoinMoin.constants.chartypes import CHARS_SPACES
+from MoinMoin import wikiutil
 
 from werkzeug import MultiDict
 
             (u'a     b     c', u'a b c'),
             (u'a   b  /  c    d  /  e   f', u'a b/c d/e f'),
             # All 30 unicode spaces
-            (config.chars_spaces, u''),
+            (CHARS_SPACES, u''),
             )
         for test, expected in cases:
             result = wikiutil.normalize_pagename(test, app.cfg)
     assert result == expected
 
 def testdrawing2fname():
-    # with extension not in config.drawing_extensions
+    # with extension not in DRAWING_EXTENSIONS
     result = wikiutil.drawing2fname('Moin_drawing.txt')
     expected = 'Moin_drawing.txt.tdraw'
     assert result == expected
-    # with extension in config.drawing_extensions
+    # with extension in DRAWING_EXTENSIONS
     result = wikiutil.drawing2fname('Moindir.Moin_drawing.jpg')
     expected = 'Moindir.Moin_drawing.jpg'
     assert result == expected

MoinMoin/_tests/wikiconfig.py

 from os.path import abspath, dirname, join
 from MoinMoin.config.default import DefaultConfig
 
+
 class Config(DefaultConfig):
     _here = abspath(dirname(__file__))
     _root = abspath(join(_here, '..', '..'))
-    data_dir = join(_here, 'wiki', 'data') # needed for plugins package TODO
+    data_dir = join(_here, 'wiki', 'data')  # needed for plugins package TODO
     index_storage = 'FileStorage', (join(_here, 'wiki', 'index'), ), {}
     content_acl = None
     item_root = 'FrontPage'
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
+from MoinMoin.constants.misc import ANON
 from MoinMoin.i18n import i18n_init
 from MoinMoin.i18n import _, L_, N_
 
 
             Rule('/<itemname:wikipage>')
             Rule('/<itemname:wikipage>/edit')
-
-        :param map: the :class:`Map`.
         """
         regex = '[^/]+?(/[^/]+?)*'
         weight = 200
 
     # if we still have no user obj, create a dummy:
     if not userobj:
-        userobj = user.User(name=u'anonymous', auth_method='invalid')
+        userobj = user.User(name=ANON, auth_method='invalid')
     # if we have a valid user we store it in the session
     if userobj.valid:
         session['user.itemid'] = userobj.itemid

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

 </td>
 </tr>
 {% endfor %}
-</tdbody>
+</tbody>
 </table>
 {% endblock %}

MoinMoin/apps/admin/views.py

 from MoinMoin.themes import render_template
 from MoinMoin.apps.admin import admin
 from MoinMoin import user
-from MoinMoin.storage.error import NoSuchRevisionError
-from MoinMoin.config import NAME, ITEMID, SIZE, EMAIL
-from MoinMoin.config import SUPERUSER
+from MoinMoin.constants.keys import NAME, ITEMID, SIZE, EMAIL
+from MoinMoin.constants.rights import SUPERUSER
 from MoinMoin.security import require_permission
 
+
 @admin.route('/superuser')
 @require_permission(SUPERUSER)
 def index():
     return render_template('admin/index.html', title_name=_(u"Admin"))
 
+
 @admin.route('/user')
 def index_user():
     return render_template('user/index_user.html', title_name=_(u"User"))
     User Account Browser
     """
     groups = flaskg.groups
-    revs = user.search_users() # all users
+    revs = user.search_users()  # all users
     user_accounts = [dict(uid=rev.meta[ITEMID],
                           name=rev.meta[NAME],
                           email=rev.meta[EMAIL],
 @admin.route('/sysitems_upgrade', methods=['GET', 'POST', ])
 @require_permission(SUPERUSER)
 def sysitems_upgrade():
-    from MoinMoin.storage.backends import upgrade_sysitems
+    from MoinMoin.storage.backends import upgrade_sysitems  # XXX broken import, either fix or kill this
     from MoinMoin.storage.error import BackendError
     if request.method == 'GET':
         action = 'syspages_upgrade'
 
 from MoinMoin.config import default as defaultconfig
 
+
 @admin.route('/wikiconfig', methods=['GET', ])
 @require_permission(SUPERUSER)
 def wikiconfig():

MoinMoin/apps/feed/_tests/test_feed.py

 
 from flask import url_for
 
-from MoinMoin.items import Item
-from MoinMoin.config import CONTENTTYPE, COMMENT
+from MoinMoin.constants.keys import COMMENT
 from MoinMoin._tests import update_item, wikiconfig
 
 class TestFeeds(object):

MoinMoin/apps/feed/views.py

 from flask import request, Response
 from flask import current_app as app
 from flask import g as flaskg
-from flask import url_for
 
 from werkzeug.contrib.atom import AtomFeed
 from jinja2 import Markup
 
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.apps.feed import feed
-from MoinMoin.config import (NAME, NAME_EXACT, WIKINAME, ACL, ACTION, ADDRESS,
-                            HOSTNAME, USERID, COMMENT, MTIME, REVID, ALL_REVS,
-                            PARENTID, LATEST_REVS)
+from MoinMoin.constants.keys import NAME, NAME_EXACT, WIKINAME, COMMENT, MTIME, REVID, ALL_REVS, PARENTID, LATEST_REVS
 from MoinMoin.themes import get_editor_info, render_template
 from MoinMoin.items import Item
 from MoinMoin.util.crypto import cache_key
 from MoinMoin.util.interwiki import url_for_item
 
+
 @feed.route('/atom/<itemname:item_name>')
 @feed.route('/atom', defaults=dict(item_name=''))
 def atom(item_name):
                     content = hl_item.content._render_data_diff_atom(previous_rev, this_rev)
                 else:
                     # full html rendering for new items
-                    content = render_template('atom.html', get='first_revision', rev=this_rev, content=Markup(hl_item.content._render_data()), revision=this_revid)
+                    content = render_template('atom.html', get='first_revision', rev=this_rev,
+                                              content=Markup(hl_item.content._render_data()), revision=this_revid)
                 content_type = 'html'
             except Exception as e:
                 logging.exception("content rendering crashed")
             if rev_comment:
                 # Trim down extremely long revision comment
                 if len(rev_comment) > 80:
-                    content = render_template('atom.html', get='comment_cont_merge', comment=rev_comment[79:], content=Markup(content))
+                    content = render_template('atom.html', get='comment_cont_merge', comment=rev_comment[79:],
+                                              content=Markup(content))
                     rev_comment = u"{0}...".format(rev_comment[:79])
                 feed_title = u"{0} - {1}".format(author.get(NAME, ''), rev_comment)
             else:

MoinMoin/apps/frontend/_tests/test_frontend.py

 
 from StringIO import StringIO
 
-import pytest
-
 from flask import url_for
 from flask import g as flaskg
 from werkzeug import ImmutableMultiDict, FileStorage
 
 from MoinMoin.apps.frontend import views
 from MoinMoin import user
-from MoinMoin.util import crypto
-from MoinMoin._tests import wikiconfig
 
 
 class TestFrontend(object):

MoinMoin/apps/frontend/views.py

 import mimetypes
 import json
 from datetime import datetime
-from itertools import chain
 from collections import namedtuple
 from functools import wraps, partial
 
-from flask import request, url_for, flash, Response, make_response, redirect, session, abort, jsonify
+from flask import request, url_for, flash, Response, make_response, redirect, abort, jsonify
 from flask import current_app as app
 from flask import g as flaskg
 from flask.ext.babel import format_date
 logging = log.getLogger(__name__)
 
 from MoinMoin.i18n import _, L_, N_
-from MoinMoin.themes import render_template, get_editor_info, contenttype_to_class
+from MoinMoin.themes import render_template, contenttype_to_class
 from MoinMoin.apps.frontend import frontend
-from MoinMoin.forms import OptionalText, RequiredText, URL, YourOpenID, YourEmail, RequiredPassword, Checkbox, InlineCheckbox, Select, Names, Tags, Natural, Submit, Hidden, MultiSelect
+from MoinMoin.forms import (OptionalText, RequiredText, URL, YourOpenID, YourEmail, RequiredPassword, Checkbox,
+                            InlineCheckbox, Select, Names, Tags, Natural, Hidden, MultiSelect)
 from MoinMoin.items import BaseChangeForm, Item, NonExistent
 from MoinMoin.items.content import content_registry
-from MoinMoin import config, user, util
+from MoinMoin import user, util
 from MoinMoin.constants.keys import *
+from MoinMoin.constants.itemtypes import ITEMTYPE_DEFAULT
+from MoinMoin.constants.chartypes import CHARS_UPPER, CHARS_LOWER
 from MoinMoin.util import crypto
 from MoinMoin.util.interwiki import url_for_item
-from MoinMoin.search import SearchForm, ValidSearch
-from MoinMoin.security.textcha import TextCha, TextChaizedForm, TextChaValid
-from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError
+from MoinMoin.search import SearchForm
+from MoinMoin.security.textcha import TextCha, TextChaizedForm
 from MoinMoin.signalling import item_displayed, item_modified
 from MoinMoin.storage.middleware.protecting import AccessDenied
 
     item_name = app.cfg.item_root
     return redirect(url_for_item(item_name))
 
+
 @frontend.route('/robots.txt')
 def robots():
     return Response("""\
     refs = OptionalText.using(label='refs')
     tags = Tags.using(optional=True).using(label='tags')
     history = InlineCheckbox.using(label=L_('search also in non-current revisions'))
-    submit = Submit.using(default=L_('Lookup'))
+    submit_label = L_('Lookup')
 
 
 @frontend.route('/+lookup', methods=['GET', 'POST'])
     # TAGS might be there multiple times, thus we need multi:
     lookup_form = LookupForm.from_flat(request.values.items(multi=True))
     valid = lookup_form.validate()
-    lookup_form['submit'].set_default() # XXX from_flat() kills all values
     if valid:
         history = bool(request.values.get('history'))
         idx_name = ALL_REVS if history else LATEST_REVS
     with flaskg.storage.indexer.ix[LATEST_REVS].searcher() as searcher:
         # The search process should be as fast as possible so use
         # the indexer low-level documents instead of high-level Revisions.
-        doc = searcher.document(name_exact=item_name)
+        doc = searcher.document(**{NAME_EXACT: item_name})
         if not doc:
             return set()
         transcluded_names = set(doc[ITEMTRANSCLUSIONS])
             transcluded_names.update(transclusions)
         return transcluded_names
 
+
 @frontend.route('/+search/<itemname:item_name>', methods=['GET', 'POST'])
 @frontend.route('/+search', defaults=dict(item_name=u''), methods=['GET', 'POST'])
 def search(item_name):
     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
     if valid:
         history = bool(request.values.get('history'))
         q = qp.parse(query)
 
         _filter = None
-        if item_name: # Only search this item and subitems
+        if item_name:  # Only search this item and subitems
             prefix_name = item_name + u'/'
             terms = [Term(NAME_EXACT, item_name), Prefix(NAME_EXACT, prefix_name), ]
 
             flaskg.clock.start('search')
             results = searcher.search(q, filter=_filter, limit=100)
             flaskg.clock.stop('search')
-            # XXX if found that calling key_terms like you see below is 1000..10000x
-            # slower than the search itself, so we better don't do that right now.
-            key_terms_is_fast = False
-            if key_terms_is_fast:
-                flaskg.clock.start('search suggestions')
-                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)])
-                flaskg.clock.stop('search suggestions')
-            else:
-                name_suggestions = u''
-                content_suggestions = u''
+            flaskg.clock.start('search suggestions')
+            name_suggestions = [word for word, score in results.key_terms(NAME, docs=20, numterms=10)]
+            content_suggestions = [word for word, score in results.key_terms(CONTENT, docs=20, numterms=10)]
+            flaskg.clock.stop('search suggestions')
             flaskg.clock.start('search render')
             html = render_template('search.html',
                                    results=results,
-                                   name_suggestions=name_suggestions,
-                                   content_suggestions=content_suggestions,
+                                   name_suggestions=u', '.join(name_suggestions),
+                                   content_suggestions=u', '.join(content_suggestions),
                                    query=query,
                                    medium_search_form=search_form,
                                    item_name=item_name,
 @presenter('meta', add_trail=True)
 def show_item_meta(item):
     show_revision = request.view_args['rev'] != CURRENT
-    show_navigation = False # TODO
+    show_navigation = False  # TODO
     first_rev = None
     last_rev = None
     if show_navigation:
                            show_navigation=show_navigation,
                           )
 
+
 @frontend.route('/+content/+<rev>/<itemname:item_name>')
 @frontend.route('/+content/<itemname:item_name>', defaults=dict(rev=CURRENT))
 def content_item(item_name, rev):
     """ same as show_item, but we only show the content """
-    # 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
     item_displayed.send(app._get_current_object(),
                         item_name=item_name)
     try:
                            data_rendered=Markup(item.content._render_data()),
                            )
 
+
 @presenter('get')
 def get_item(item):
     return item.content.do_get()
 
+
 @presenter('download')
 def download_item(item):
     mimetype = request.values.get("mimetype")
     return item.content.do_get(force_attachment=True, mimetype=mimetype)
 
+
 @frontend.route('/+convert/<itemname:item_name>')
 def convert_item(item_name):
     """
     item_name_converted = item_name + 'converted'
     try:
         # TODO implement Content.create and use it here
-        converted_item = Item.create(item_name_converted, itemtype=u'default', contenttype=contenttype)
+        converted_item = Item.create(item_name_converted, itemtype=ITEMTYPE_DEFAULT, contenttype=contenttype)
     except AccessDenied:
         abort(403)
     return converted_item.content._convert(item.content.internal_representation())
     After successful POST, redirects to the page.
     """
     # XXX drawing applets don't send itemtype
-    itemtype = request.values.get('itemtype', u'default')
+    itemtype = request.values.get('itemtype', ITEMTYPE_DEFAULT)
     contenttype = request.values.get('contenttype')
     try:
         item = Item.create(item_name, itemtype=itemtype, contenttype=contenttype)
 class TargetChangeForm(BaseChangeForm):
     target = RequiredText.using(label=L_('Target')).with_properties(placeholder=L_("The name of the target item"))
 
+
 class RevertItemForm(BaseChangeForm):
     name = 'revert_item'
 
+
 class DeleteItemForm(BaseChangeForm):
     name = 'delete_item'
 
+
 class DestroyItemForm(BaseChangeForm):
     name = 'destroy_item'
 
+
 class RenameItemForm(TargetChangeForm):
     name = 'rename_item'
 
                            form=form,
                           )
 
+
 @frontend.route('/+ajaxdelete/<itemname:item_name>', methods=['POST'])
 @frontend.route('/+ajaxdelete', defaults=dict(item_name=''), methods=['POST'])
 def ajaxdelete(item_name):
 
     return jsonify(response)
 
+
 @frontend.route('/+ajaxdestroy/<itemname:item_name>', methods=['POST'])
 @frontend.route('/+ajaxdestroy', defaults=dict(item_name=''), methods=['POST'])
 def ajaxdestroy(item_name):
 def destroy_item(item_name, rev):
     if rev is None:
         # no revision given
-        _rev = CURRENT # for item creation
+        _rev = CURRENT  # for item creation
         destroy_item = True
     else:
         _rev = rev
     """
     data_file = request.files.get('data_file')
     subitem_name = data_file.filename
-    contenttype = data_file.content_type # guess by browser, based on file name
+    contenttype = data_file.content_type  # guess by browser, based on file name
     data = data_file.stream
     if item_name:
         subitem_prefix = item_name + u'/'
     contenttype_group_descriptions[g] = ', '.join([e.display_name for e in content_registry.groups[g]])
 contenttype_groups.append('unknown items')
 
-ContenttypeGroup = MultiSelect.of(Enum.using(valid_values=contenttype_groups).with_properties(descriptions=contenttype_group_descriptions)).using(optional=True)
+ContenttypeGroup = MultiSelect.of(Enum.using(valid_values=contenttype_groups).with_properties(
+                                  descriptions=contenttype_group_descriptions)).using(optional=True)
+
 
 class IndexForm(Form):
     contenttype = ContenttypeGroup
-    submit = Submit.using(default=L_('Filter'))
+    submit_label = L_('Filter')
+
 
 @frontend.route('/+index/', defaults=dict(item_name=''), methods=['GET', 'POST'])
 @frontend.route('/+index/<itemname:item_name>', methods=['GET', 'POST'])
 def index(item_name):
     try:
-        item = Item.create(item_name) # when item_name='', it gives toplevel index
+        item = Item.create(item_name)  # when item_name='', it gives toplevel index
     except AccessDenied:
         abort(403)
 
     # values, eg. calling items with multi=True. See Werkzeug documentation for
     # more.
     form = IndexForm.from_flat(request.args.items(multi=True))
-    form['submit'].set_default() # XXX from_flat() kills all values
     if not form['contenttype']:
         form['contenttype'].set(contenttype_groups)
 
     history = [dict((k, v) for k, v in rev.meta.iteritems() if k != CONTENT) for rev in revs]
     history_page = util.getPageContent(history, offset, results_per_page)
     return render_template('history.html',
-                           item_name=item_name, # XXX no item here
+                           item_name=item_name,  # XXX no item here
                            history_page=history_page,
                            bookmark_time=bookmark_time,
                           )
                            bookmark_time=bookmark_time,
                           )
 
+
 def _compute_item_sets():
     """
     compute sets of existing, linked, transcluded and no-revision item names
             return False
         if element['password1'].value != element['password2'].value:
             return self.note_error(element, state, 'passwords_mismatch_msg')
+        return True
 
-        return True
 
 class RegistrationForm(TextChaizedForm):
     """a simple user registration form"""
     password2 = RequiredPassword.with_properties(placeholder=L_("Repeat the same password"))
     email = YourEmail
     openid = YourOpenID.using(optional=True)
-    submit = Submit.using(default=L_('Register'))
+    submit_label = L_('Register')
 
     validators = [ValidRegistration()]
 
 
     openid = YourOpenID
 
+
 def _using_moin_auth():
     """Check if MoinAuth is being used for authentication.
 
                     if is_ok:
                         flash(_('Account verification required, please see the email we sent to your address.'), "info")
                     else:
-                        flash(_('An error occurred while sending the verification email: "%(message)s" Please contact an administrator to activate your account.',
+                        flash(_('An error occurred while sending the verification email: "%(message)s" '
+                                'Please contact an administrator to activate your account.',
                             message=msg), "error")
                 else:
                     flash(_('Account created, please log in now.'), "info")
 
 @frontend.route('/+verifyemail', methods=['GET'])
 def verifyemail():
-    u = None
+    u = token = None
     if 'username' in request.values and 'token' in request.values:
         u = user.User(auth_username=request.values['username'])
         token = request.values['token']
-    if u and u.disabled and u.validate_recovery_token(token):
+    if u and u.disabled and token and u.validate_recovery_token(token):
         u.profile[DISABLED] = False
         u.save()
         flash(_("Your account has been activated, you can log in now."), "info")
     else:
-        flash(_('Your token is invalid!'), "error")
+        flash(_('Your username and/or token is invalid!'), "error")
     return redirect(url_for('.show_root'))
 
 
 
     username = OptionalText.using(label=L_('Name')).with_properties(placeholder=L_("Your login name"))
     email = YourEmail.using(optional=True)
-    submit = Submit.using(default=L_('Recover password'))
+    submit_label = L_('Recover password')
 
     validators = [ValidLostPassword()]
 
                            form=form,
                           )
 
+
 class ValidPasswordRecovery(Validator):
     """Validator for a valid password recovery form
     """
 
         return True
 
+
 class PasswordRecoveryForm(Form):
     """a simple password recovery form"""
     name = 'recoverpass'
 
     username = RequiredText.using(label=L_('Name')).with_properties(placeholder=L_("Your login name"))
-    token = RequiredText.using(label=L_('Recovery token')).with_properties(placeholder=L_("The recovery token that has been sent to you"))
-    password1 = RequiredPassword.using(label=L_('New password')).with_properties(placeholder=L_("The login password you want to use"))
-    password2 = RequiredPassword.using(label=L_('New password (repeat)')).with_properties(placeholder=L_("Repeat the same password"))
-    submit = Submit.using(default=L_('Change password'))
+    token = RequiredText.using(label=L_('Recovery token')).with_properties(
+        placeholder=L_("The recovery token that has been sent to you"))
+    password1 = RequiredPassword.using(label=L_('New password')).with_properties(
+        placeholder=L_("The login password you want to use"))
+    password2 = RequiredPassword.using(label=L_('New password (repeat)')).with_properties(
+        placeholder=L_("Repeat the same password"))
+    submit_label = L_('Change password')
 
     validators = [ValidPasswordRecovery()]
 
     username = RequiredText.using(label=L_('Name'), optional=False).with_properties(autofocus=True)
     password = RequiredPassword
     openid = YourOpenID.using(optional=True)
-    submit = Submit.using(default=L_('Log in'))
+    # This field results in a login_submit field in the POST form, which is in
+    # turn looked for by setup_user() in app.py as marker for login requests.
+    submit = Hidden.using(default='1')
+    submit_label = L_('Log in')
 
     validators = [ValidLogin()]
 
     name = 'usersettings_password'
     validators = [ValidChangePass()]
 
-    password_current = RequiredPassword.using(label=L_('Current Password')).with_properties(placeholder=L_("Your current login password"))
-    password1 = RequiredPassword.using(label=L_('New password')).with_properties(placeholder=L_("The login password you want to use"))
-    password2 = RequiredPassword.using(label=L_('New password (repeat)')).with_properties(placeholder=L_("Repeat the same password"))
-    submit = Submit.using(default=L_('Change password'))
+    password_current = RequiredPassword.using(label=L_('Current Password')).with_properties(
+        placeholder=L_("Your current login password"))
+    password1 = RequiredPassword.using(label=L_('New password')).with_properties(
+        placeholder=L_("The login password you want to use"))
+    password2 = RequiredPassword.using(label=L_('New password (repeat)')).with_properties(
+        placeholder=L_("Repeat the same password"))
+    submit_label = L_('Change password')
+
 
 class UserSettingsNotificationForm(Form):
     name = 'usersettings_notification'
     email = YourEmail
-    submit = Submit.using(default=L_('Save'))
+    submit_label = L_('Save')
 
 
 class UserSettingsNavigationForm(Form):
     name = 'usersettings_navigation'
+    # XXX Flatland insists a form having at least one element
+    dummy = Hidden
     # TODO: find a good way to handle quicklinks here
-    submit = Submit.using(default=L_('Save'))
+    submit_label = L_('Save')
 
 
 class UserSettingsOptionsForm(Form):
     scroll_page_after_edit = Checkbox.using(label=L_('Scroll page after edit'))
     show_comments = Checkbox.using(label=L_('Show comment sections'))
     disabled = Checkbox.using(label=L_('Disable this account forever'))
-    submit = Submit.using(default=L_('Save'))
+    submit_label = L_('Save')
 
 
 @frontend.route('/+usersettings', methods=['GET', 'POST'])
 
     # these forms can't be global because we need app object, which is only available within a request:
     class UserSettingsPersonalForm(Form):
-        name = 'usersettings_personal' # "name" is duplicate
+        name = 'usersettings_personal'  # "name" is duplicate
         name = Names.using(label=L_('Names')).with_properties(placeholder=L_("The login names you want to use"))
-        display_name = OptionalText.using(label=L_('Display-Name')).with_properties(placeholder=L_("Your display name (informational)"))
+        display_name = OptionalText.using(label=L_('Display-Name')).with_properties(
+            placeholder=L_("Your display name (informational)"))
         openid = YourOpenID.using(optional=True)
         #timezones_keys = sorted(Locale('en').time_zones.keys())
         timezones_keys = [unicode(tz) for tz in pytz.common_timezones]
                                    key=lambda x: x[1])
         locales_keys = [l[0] for l in locales_available]
         locale = Select.using(label=L_('Locale')).with_properties(labels=dict(locales_available)).valued(*locales_keys)
-        submit = Submit.using(default=L_('Save'))
+        submit_label = L_('Save')
 
     class UserSettingsUIForm(Form):
         name = 'usersettings_ui'
         themes_available = sorted([(unicode(t.identifier), t.name) for t in get_themes_list()],
                                   key=lambda x: x[1])
         themes_keys = [t[0] for t in themes_available]
-        theme_name = Select.using(label=L_('Theme name')).with_properties(labels=dict(themes_available)).valued(*themes_keys)
-        css_url = URL.using(label=L_('User CSS URL'), optional=True).with_properties(placeholder=L_("Give the URL of your custom CSS (optional)"))
-        edit_rows = Natural.using(label=L_('Editor size')).with_properties(placeholder=L_("Editor textarea height (0=auto)"))
-        results_per_page = Natural.using(label=L_('History results per page')).with_properties(placeholder=L_("Number of results per page (0=no paging)"))
-        submit = Submit.using(default=L_('Save'))
+        theme_name = Select.using(label=L_('Theme name')).with_properties(
+            labels=dict(themes_available)).valued(*themes_keys)
+        css_url = URL.using(label=L_('User CSS URL'), optional=True).with_properties(
+            placeholder=L_("Give the URL of your custom CSS (optional)"))
+        edit_rows = Natural.using(label=L_('Editor size')).with_properties(
+            placeholder=L_("Editor textarea height (0=auto)"))
+        results_per_page = Natural.using(label=L_('History results per page')).with_properties(
+            placeholder=L_("Number of results per page (0=no paging)"))
+        submit_label = L_('Save')
 
     form_classes = dict(
         personal=UserSettingsPersonalForm,
                     response['flash'].append((_("Your password has been changed."), "info"))
                 else:
                     if part == 'personal':
-                        if form['openid'].value and form['openid'].value != flaskg.user.openid and user.search_users(openid=form['openid'].value):
+                        if (form['openid'].value and form['openid'].value != flaskg.user.openid and
+                            user.search_users(openid=form['openid'].value)):
                             # duplicate openid
                             response['flash'].append((_("This openid is already in use."), "error"))
                             success = False
                         if set(form['name'].value) != set(flaskg.user.name):
                             new_names = set(form['name'].value) - set(flaskg.user.name)
                             for name in new_names:
-                                if user.search_users(name_exact=name):
+                                if user.search_users(**{NAME_EXACT: name}):
                                     # duplicate name
-                                    response['flash'].append((_("The username %(name)r is already in use.", name=name), "error"))
+                                    response['flash'].append((_("The username %(name)r is already in use.", name=name),
+                                                              "error"))
                                     success = False
                     if part == 'notification':
                         if (form['email'].value != flaskg.user.email and
-                            user.search_users(email=form['email'].value) and app.cfg.user_email_unique):
+                            user.search_users(**{EMAIL: form['email'].value}) and app.cfg.user_email_unique):
                             # duplicate email
                             response['flash'].append((_('This email is already in use'), 'error'))
                             success = False
                     if success:
                         user_old_email = flaskg.user.email
                         d = dict(form.value)
-                        d.pop('submit')
                         for k, v in d.items():
                             flaskg.user.profile[k] = v
-                        if part == 'notification' and app.cfg.user_email_verification and form['email'].value != user_old_email:
+                        if (part == 'notification' and app.cfg.user_email_verification and
+                            form['email'].value != user_old_email):
                             # disable account
                             flaskg.user.profile[DISABLED] = True
                             # send verification mail
                             is_ok, msg = flaskg.user.mail_email_verification()
                             if is_ok:
                                 flaskg.user.logout_session()
-                                response['flash'].append((_('Your account has been disabled because you changed your email address. Please see the email we sent to your address to reactivate it.'), "info"))
+                                response['flash'].append((_('Your account has been disabled because you changed your '
+                                                            'email address. Please see the email we sent to your '
+                                                            'address to reactivate it.'), "info"))
                                 response['redirect'] = url_for('.show_root')
                             else:
                                 # sending the verification email didn't work. reset email change and alert the user.
                                 flaskg.user.profile[DISABLED] = False
                                 flaskg.user.profile[EMAIL] = user_old_email
                                 flaskg.user.save()
-                                response['flash'].append((_('Your email address was not changed because sending the verification email failed. Please try again later.'), "error"))
+                                response['flash'].append((_('Your email address was not changed because sending the '
+                                                            'verification email failed. Please try again later.'),
+                                                          "error"))
                         else:
                             flaskg.user.save()
 
                 item_names.append(name)
     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_name=item_name,  # XXX no item
                            item_names=item_names)
 
 
     :returns: start, end, matches dict
     """
     if start_re is None:
-        start_re = re.compile(u'([{0}][{1}]+)'.format(config.chars_upper,
-                                                     config.chars_lower))
+        start_re = re.compile(u'([{0}][{1}]+)'.format(CHARS_UPPER, CHARS_LOWER))
     if end_re is None:
-        end_re = re.compile(u'([{0}][{1}]+)$'.format(config.chars_upper,
-                                                    config.chars_lower))
+        end_re = re.compile(u'([{0}][{1}]+)$'.format(CHARS_UPPER, CHARS_LOWER))
 
     # If we don't get results with wiki words matching, fall back to
     # simple first word and last word, using spaces.
     if not flaskg.storage[item_name]:
         abort(404, item_name)
     sitemap = NestedItemListBuilder().recurse_build([item_name])
-    del sitemap[0] # don't show current item name as sole toplevel list item
+    del sitemap[0]  # don't show current item name as sole toplevel list item
     return render_template('sitemap.html',
-                           item_name=item_name, # XXX no item
+                           item_name=item_name,  # XXX no item
                            sitemap=sitemap,
                           )
 
     def __init__(self):
         self.children = set()
         self.numnodes = 0
-        self.maxnodes = 35 # approx. max count of nodes, not strict
+        self.maxnodes = 35  # approx. max count of nodes, not strict
 
     def recurse_build(self, names):
         result = []
             scale = weight_max / 2
         else:
             scale = weight_max / (count_max - count_min)
+
         def cls(count):
             # return the css class for this tag
             weight = scale * (count - count_min)
-            return "weight{0}".format(int(weight)) # weight0, ..., weight9
+            return "weight{0}".format(int(weight))  # weight0, ..., weight9
         tags = [(cls(count), tag) for tag, count in tags_counts]
     else:
         tags = []

MoinMoin/apps/misc/views.py

 
 from MoinMoin.apps.misc import misc
 
-from MoinMoin.config import NAME, MTIME
+from MoinMoin.constants.keys import MTIME
 from MoinMoin.themes import render_template
-from MoinMoin import wikiutil
 
 SITEMAP_HAS_SYSTEM_ITEMS = True
 
+
 @misc.route('/sitemap')
 def sitemap():
     """
     for rev in flaskg.storage.documents(wikiname=app.cfg.interwikiname):
         name = rev.name
         mtime = rev.meta[MTIME]
-        if False: # was: wikiutil.isSystemItem(name)   XXX add back later, when we have that in the index
+        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

MoinMoin/auth/__init__.py

         self.multistage = multistage
         self.redirect_to = redirect_to
 
+
 class ContinueLogin(LoginReturn):
     """ ContinueLogin - helper for auth method login that just continues """
     def __init__(self, user_obj, message=None):
         LoginReturn.__init__(self, user_obj, True, message=message)
 
+
 class CancelLogin(LoginReturn):
     """ CancelLogin - cancel login showing a message """
     def __init__(self, message):
         LoginReturn.__init__(self, None, False, message=message)
 
+
 class MultistageFormLogin(LoginReturn):
     """ MultistageFormLogin - require user to fill in another form """
     def __init__(self, multistage):
         LoginReturn.__init__(self, None, False, multistage=multistage)
 
+
 class MultistageRedirectLogin(LoginReturn):
     """ MultistageRedirectLogin - redirect user to another site before continuing login """
     def __init__(self, url):
     name = None
     login_inputs = []
     logout_possible = False
+
     def __init__(self, trusted=False, **kw):
         self.trusted = trusted
         if kw:
             raise TypeError("got unexpected arguments %r" % kw)
+
     def login(self, user_obj, **kw):
         return ContinueLogin(user_obj)
+
     def request(self, user_obj, **kw):
         return user_obj, True
+
     def logout(self, user_obj, **kw):
         if self.name and user_obj and user_obj.auth_method == self.name:
             logging.debug("{0}: logout - invalidating user {1!r}".format(self.name, user_obj.name))
             user_obj.valid = False
         return user_obj, True
+
     def login_hint(self):
         return None
 
+
 class MoinAuth(BaseAuth):
     """ handle login from moin login form """
     def __init__(self, **kw):
         Alternatively you can directly give a fixed user name (user_name)
         that will be considered as authenticated.
     """
-    name = 'given' # was 'http' in 1.8.x and before
+    name = 'given'  # was 'http' in 1.8.x and before
 
     def __init__(self,
                  env_var=None,  # environment variable we want to read (default: REMOTE_USER)
             u.create_or_update()
         if u and u.valid:
             logging.debug("returning valid user {0!r}".format(u))
-            return u, True # True to get other methods called, too
+            return u, True  # True to get other methods called, too
         else:
             logging.debug("returning {0!r}".format(user_obj))
             return user_obj, True
 
     return userobj
 
+
 def handle_logout(userobj):
     """ Logout the passed user from every configured authentication method. """
     if userobj is None:
             break
     return userobj
 
+
 def handle_request(userobj):
     """ Handle the per-request callbacks of the configured authentication methods. """
     for authmethod in app.cfg.auth:
             break
     return userobj
 
+
 def setup_from_session():
     userobj = None
     if 'user.itemid' in session:

MoinMoin/auth/_tests/test_auth.py

 Test for auth.__init__
 """
 
-from flask import current_app as app
 from flask import g as flaskg
 
-import pytest
-
 from MoinMoin._tests import wikiconfig
+from MoinMoin.constants.misc import ANON
 from MoinMoin.auth import GivenAuth, handle_login, get_multistage_continuation_url
 from MoinMoin.user import create_user
 
     test_user1 = handle_login(flaskg.user, login_username='test_user', login_password='test_password', stage='moin')
     test_login_message = [u'Invalid username or password.']
     assert flaskg._login_messages == test_login_message
-    assert test_user1.name0 == u'anonymous'
+    assert test_user1.name0 == ANON
     assert not test_user1.valid
     # pop the message
     flaskg._login_messages.pop()

MoinMoin/auth/_tests/test_http.py

 
 from MoinMoin.user import create_user
 from MoinMoin.auth.http import HTTPAuthMoin
-import pytest
+from MoinMoin.constants.misc import ANON
+
 
 class TestHTTPAuthMoin(object):
     """ Test: HTTPAuthMoin """
         flaskg.user.auth_method = 'invalid'
         test_user, bool_val = httpauthmoin_obj.request(flaskg.user)
         assert not test_user.valid
-        assert test_user.name0 == u'anonymous'
+        assert test_user.name0 == ANON

MoinMoin/auth/_tests/test_log.py

 from MoinMoin import log
 logging = log.getLogger(__name__)
 
+from flask import g as flaskg
+
 from MoinMoin.auth.log import AuthLog
-from flask import g as flaskg
-import pytest
+from MoinMoin.constants.misc import ANON
+
 
 class TestAuthLog(object):
     """ Test: TestAuthLog """
         result = authlog_obj.login(flaskg.user)
         assert result.continue_flag
         test_user_obj = result.user_obj
-        assert test_user_obj.name0 == u'anonymous'
+        assert test_user_obj.name0 == ANON
 
     def test_request(self):
         authlog_obj = AuthLog()
         result = authlog_obj.request(flaskg.user)
         test_user, bool_value = result
-        assert test_user.name0 == u'anonymous'
+        assert test_user.name0 == ANON
         assert not test_user.valid
         assert bool_value
 
         authlog_obj = AuthLog()
         result = authlog_obj.logout(flaskg.user)
         test_user, bool_value = result
-        assert test_user.name0 == u'anonymous'
+        assert test_user.name0 == ANON
         assert not test_user.valid
         assert bool_value

MoinMoin/auth/http.py

 
 from flask import request
 
-from MoinMoin import config, user
+from MoinMoin import user
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.auth import BaseAuth, GivenAuth
 
 
         auth = request.authorization
         if auth and auth.username and auth.password is not None:
-            logging.debug("http basic auth, received username: {0!r} password: {1!r}".format(auth.username, auth.password))
+            logging.debug("http basic auth, received username: {0!r} password: {1!r}".format(
+                auth.username, auth.password))
             u = user.User(name=auth.username.decode(self.coding),
                           password=auth.password.decode(self.coding),
                           auth_method=self.name, auth_attribs=[], trusted=self.trusted)
             u.create_or_update()
         if u and u.valid:
             logging.debug("returning valid user {0!r}".format(u))
-            return u, True # True to get other methods called, too
+            return u, True  # True to get other methods called, too
         else:
             logging.debug("returning {0!r}".format(user_obj))
             return user_obj, True

MoinMoin/auth/ldap_login.py

         bind_pw='',
         base_dn='',  # base DN we use for searching
                      #base_dn = 'ou=SOMEUNIT,dc=example,dc=org'
-        scope=ldap.SCOPE_SUBTREE, # scope of the search we do (2 == ldap.SCOPE_SUBTREE)
-        referrals=0, # LDAP REFERRALS (0 needed for AD)
+        scope=ldap.SCOPE_SUBTREE,  # scope of the search we do (2 == ldap.SCOPE_SUBTREE)
+        referrals=0,  # LDAP REFERRALS (0 needed for AD)
         search_filter='(uid=%(username)s)',  # ldap filter used for searching:
                                              #search_filter = '(sAMAccountName=%(username)s)' # (AD)
                                              #search_filter = '(uid=%(username)s)' # (OpenLDAP)
                                              # you can also do more complex filtering like:
                                              # "(&(cn=%(username)s)(memberOf=CN=WikiUsers,OU=Groups,DC=example,DC=org))"
         # some attribute names we use to extract information from LDAP:
-        givenname_attribute=None, # ('givenName') ldap attribute we get the first name from
-        surname_attribute=None, # ('sn') ldap attribute we get the family name from
-        displayname_attribute=None, # ('displayName') ldap attribute we get the display_name from
-        email_attribute=None, # ('mail') ldap attribute we get the email address from
-        email_callback=None, # called to make up email address
-        name_callback=None, # called to use a Wiki name different from the login name
-        coding='utf-8', # coding used for ldap queries and result values
-        timeout=10, # how long we wait for the ldap server [s]
-        start_tls=0, # 0 = No, 1 = Try, 2 = Required
+        givenname_attribute=None,  # ('givenName') ldap attribute we get the first name from
+        surname_attribute=None,  # ('sn') ldap attribute we get the family name from
+        displayname_attribute=None,  # ('displayName') ldap attribute we get the display_name from
+        email_attribute=None,  # ('mail') ldap attribute we get the email address from
+        email_callback=None,  # called to make up email address
+        name_callback=None,  # called to use a Wiki name different from the login name
+        coding='utf-8',  # coding used for ldap queries and result values
+        timeout=10,  # how long we wait for the ldap server [s]
+        start_tls=0,  # 0 = No, 1 = Try, 2 = Required
         tls_cacertdir=None,
         tls_cacertfile=None,
         tls_certfile=None,
         tls_keyfile=None,
-        tls_require_cert=0, # 0 == ldap.OPT_X_TLS_NEVER (needed for self-signed certs)
-        bind_once=False, # set to True to only do one bind - useful if configured to bind as the user on the first attempt
-        autocreate=False, # set to True if you want to autocreate user profiles
-        name='ldap', # use e.g. 'ldap_pdc' and 'ldap_bdc' (or 'ldap1' and 'ldap2') if you auth against 2 ldap servers
-        report_invalid_credentials=True, # whether to emit "invalid username or password" msg at login time or not
+        tls_require_cert=0,  # 0 == ldap.OPT_X_TLS_NEVER (needed for self-signed certs)
+        bind_once=False,  # set to True to only do one bind - useful if configured to bind as the user on first attempt
+        autocreate=False,  # set to True if you want to autocreate user profiles
+        name='ldap',  # use e.g. 'ldap_pdc' and 'ldap_bdc' (or 'ldap1' and 'ldap2') if you auth against 2 ldap servers
+        report_invalid_credentials=True,  # whether to emit "invalid username or password" msg at login time or not
         **kw
         ):
         super(LDAPAuth, self).__init__(**kw)
             try:
                 u = None
                 dn = None
+                server = self.server_uri
                 coding = self.coding
                 logging.debug("Setting misc. ldap options...")
-                ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) # ldap v2 is outdated
+                ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)  # ldap v2 is outdated
                 ldap.set_option(ldap.OPT_REFERRALS, self.referrals)
                 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
 
                         if value is not None:
                             ldap.set_option(option, value)
 
-                server = self.server_uri
                 logging.debug("Trying to initialize {0!r}.".format(server))
                 l = ldap.initialize(server)
                 logging.debug("Connected to LDAP server {0!r}.".format(server))
                 result_length = len(lusers)
                 if result_length != 1:
                     if result_length > 1:
-                        logging.warning("Search found more than one ({0}) matches for {1!r}.".format(result_length, filterstr))
+                        logging.warning("Search found more than one ({0}) matches for {1!r}.".format(
+                            result_length, filterstr))
                     if result_length == 0:
                         logging.debug("Search found no matches for {0!r}.".format(filterstr, ))
                     if self.report_invalid_credentials:
                     username = self.name_callback(ldap_dict)
 
                 if email:
-                    u = user.User(auth_username=username, auth_method=self.name, auth_attribs=('name', 'password', 'email', 'mailto_author', ),
+                    u = user.User(auth_username=username, auth_method=self.name,
+                                  auth_attribs=('name', 'password', 'email', 'mailto_author', ),
                                   trusted=self.trusted)
                     u.email = email
                 else:
-                    u = user.User(auth_username=username, auth_method=self.name, auth_attribs=('name', 'password', 'mailto_author', ),
+                    u = user.User(auth_username=username, auth_method=self.name,
+                                  auth_attribs=('name', 'password', 'mailto_author', ),
                                   trusted=self.trusted)
                 u.name = username
                 u.display_name = display_name
-                logging.debug("creating user object with name {0!r} email {1!r} display name {2!r}".format(username, email, display_name))
+                logging.debug("creating user object with name {0!r} email {1!r} display name {2!r}".format(
+                    username, email, display_name))
 
             except ldap.INVALID_CREDENTIALS as err:
-                logging.debug("invalid credentials (wrong password?) for dn {0!r} (username: {1!r})".format(dn, username))
+                logging.debug("invalid credentials (wrong password?) for dn {0!r} (username: {1!r})".format(
+                    dn, username))
                 return CancelLogin(_("Invalid username or password."))
 
             if u and self.autocreate:

MoinMoin/auth/log.py

 
 from MoinMoin.auth import BaseAuth, ContinueLogin
 
+
 class AuthLog(BaseAuth):
     """ just log the call, do nothing else """
     name = "log"

MoinMoin/auth/openidrp.py

 from flask import current_app as app
 from MoinMoin.auth import BaseAuth, get_multistage_continuation_url
 from MoinMoin.auth import ContinueLogin, CancelLogin, MultistageFormLogin, MultistageRedirectLogin
-from MoinMoin.config import ITEMID
+from MoinMoin.constants.keys import ITEMID
 from MoinMoin import user
 from MoinMoin.i18n import _, L_, N_
 

MoinMoin/auth/smb_mount.py

 
 from MoinMoin.auth import BaseAuth, CancelLogin, ContinueLogin
 
+
 class SMBMount(BaseAuth):
     """ auth plugin for (un)mounting an smb share,
         this is a wrapper around mount.cifs -o <options> //server/share mountpoint
         See man mount.cifs for details.
     """
     def __init__(self,
-        server, # mount.cifs //server/share
-        share, # mount.cifs //server/share
-        mountpoint_fn, # function of username to determine the mountpoint, e.g.:
-                       # lambda username: u'/mnt/wiki/%s' % username
-        dir_user, # username to get the uid that is used for mount.cifs -o uid=... (e.g. 'www-data')
-        domain, # mount.cifs -o domain=...
-        dir_mode='0700', # mount.cifs -o dir_mode=...
-        file_mode='0600', # mount.cifs -o file_mode=...
-        iocharset='utf-8', # mount.cifs -o iocharset=... (try 'iso8859-1' if default does not work)
-        coding='utf-8', # encoding used for username/password/cmdline (try 'iso8859-1' if default does not work)
-        log='/dev/null', # logfile for mount.cifs output
+        server,  # mount.cifs //server/share
+        share,  # mount.cifs //server/share
+        mountpoint_fn,  # function of username to determine the mountpoint, e.g.:
+                        # lambda username: u'/mnt/wiki/%s' % username
+        dir_user,  # username to get the uid that is used for mount.cifs -o uid=... (e.g. 'www-data')
+        domain,  # mount.cifs -o domain=...
+        dir_mode='0700',  # mount.cifs -o dir_mode=...
+        file_mode='0600',  # mount.cifs -o file_mode=...
+        iocharset='utf-8',  # mount.cifs -o iocharset=... (try 'iso8859-1' if default does not work)
+        coding='utf-8',  # encoding used for username/password/cmdline (try 'iso8859-1' if default does not work)
+        log='/dev/null',  # logfile for mount.cifs output
         **kw
         ):
         super(SMBMount, self).__init__(**kw)
     def do_smb(self, username, password, login):
         logging.debug("login={0} logout={1}: got name={2!r}".format(login, not login, username))
 
-        import os, pwd, subprocess
+        import os
+        import pwd
+        import subprocess
         web_username = self.dir_user
-        web_uid = pwd.getpwnam(web_username)[2] # XXX better just use current uid?
+        web_uid = pwd.getpwnam(web_username)[2]  # XXX better just use current uid?
 
         mountpoint = self.mountpoint_fn(username)
         if login:
-            cmd = u"sudo mount -t cifs -o user=%(user)s,domain=%(domain)s,uid=%(uid)d,dir_mode=%(dir_mode)s,file_mode=%(file_mode)s,iocharset=%(iocharset)s //%(server)s/%(share)s %(mountpoint)s >>%(log)s 2>&1"
+            cmd = (u"sudo mount -t cifs -o user=%(user)s,domain=%(domain)s,uid=%(uid)d,dir_mode=%(dir_mode)s,file_mode="
+                   u"%(file_mode)s,iocharset=%(iocharset)s //%(server)s/%(share)s %(mountpoint)s >>%(log)s 2>&1")
         else:
             cmd = u"sudo umount %(mountpoint)s >>%(log)s 2>&1"
 
         if login:
             try:
                 if not os.path.exists(mountpoint):
-                    os.makedirs(mountpoint) # the dir containing the mountpoint must be writeable for us!
+                    os.makedirs(mountpoint)  # the dir containing the mountpoint must be writeable for us!
             except OSError:
                 pass
             env['PASSWD'] = password.encode(self.coding)

MoinMoin/config/__init__.py

-# Copyright: 2011 MoinMoin:ThomasWaldmann
+# Copyright: 2011-2013 MoinMoin:ThomasWaldmann
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
-"""
-MoinMoin - configuration defaults and support code
-"""
-
-# provide compatibility for stuff not using MoinMoin.constants yet
-from MoinMoin.constants.rights import *
-from MoinMoin.constants.chartypes import *
-from MoinMoin.constants.contenttypes import *
-from MoinMoin.constants.keys import *
-from MoinMoin.constants.misc import *
+# Nothing to see here any more, please do direct imports from MoinMoin.constants.*

MoinMoin/config/default.py

 logging = log.getLogger(__name__)
 
 from MoinMoin.i18n import _, L_, N_
-from MoinMoin import config, error
+from MoinMoin import error
+from MoinMoin.constants.rights import ACL_RIGHTS_CONTENTS, ACL_RIGHTS_FUNCTIONS
+from MoinMoin.constants.keys import *
 from MoinMoin import datastruct
 from MoinMoin.auth import MoinAuth
 from MoinMoin.util import plugins
 
         plugins._loadPluginModule(self)
 
-        if self.user_defaults['timezone'] is None:
-            self.user_defaults['timezone'] = self.timezone_default
-        if self.user_defaults['theme_name'] is None:
-            self.user_defaults['theme_name'] = self.theme_default
+        if self.user_defaults[TIMEZONE] is None:
+            self.user_defaults[TIMEZONE] = self.timezone_default
+        if self.user_defaults[THEME_NAME] is None:
+            self.user_defaults[THEME_NAME] = self.theme_default
         # Note: do not assign user_defaults['locale'] = locale_default
         # to give browser language detection a chance.
         try:
         self.mail_enabled = self.mail_enabled and True or False
 
         if self.namespace_mapping is None:
-            raise error.ConfigurationError("No storage configuration specified! You need to define a namespace_mapping. "
-                                           "For further reference, please see HelpOnStorageConfiguration.")
+            raise error.ConfigurationError(
+                "No storage configuration specified! You need to define a namespace_mapping. "
+                "For further reference, please see HelpOnStorageConfiguration.")
 
         if self.backend_mapping is None:
-            raise error.ConfigurationError("No storage configuration specified! You need to define a backend_mapping. " +
-                                           "For further reference, please see HelpOnStorageConfiguration.")
+            raise error.ConfigurationError(
+                "No storage configuration specified! You need to define a backend_mapping. " +
+                "For further reference, please see HelpOnStorageConfiguration.")
 
         if self.acl_mapping is None:
-            raise error.ConfigurationError("No acl configuration specified! You need to define a acl_mapping. "
-                                           "For further reference, please see HelpOnStorageConfiguration.")
+            raise error.ConfigurationError(
+                "No acl configuration specified! You need to define a acl_mapping. "
+                "For further reference, please see HelpOnStorageConfiguration.")
 
         if self.secrets is None:  # admin did not setup a real secret
-            raise error.ConfigurationError("No secret configured! You need to set secrets = 'somelongsecretstring' in your wiki config.")
+            raise error.ConfigurationError(
+                "No secret configured! You need to set secrets = 'somelongsecretstring' in your wiki config.")
 
         if self.interwikiname is None:  # admin did not setup a real interwikiname
-            raise error.ConfigurationError("No interwikiname configured! You need to set interwikiname = u'YourUniqueStableInterwikiName' in your wiki config.")
+            raise error.ConfigurationError(
+                "No interwikiname configured! "
+                "You need to set interwikiname = u'YourUniqueStableInterwikiName' in your wiki config.")
 
         secret_key_names = ['security/ticket', ]
         if self.textchas:
         secret_min_length = 10
         if isinstance(self.secrets, str):
             if len(self.secrets) < secret_min_length:
-                raise error.ConfigurationError("The secrets = '...' wiki config setting is a way too short string (minimum length is {0} chars)!".format(
-                    secret_min_length))
+                raise error.ConfigurationError(
+                    "The secrets = '...' wiki config setting is a way too short string "
+                    "(minimum length is {0} chars)!".format(secret_min_length))
             # for lazy people: set all required secrets to same value
             secrets = {}
             for key in secret_key_names:
                 if len(secret) < secret_min_length:
                     raise ValueError
             except (KeyError, ValueError):
-                raise error.ConfigurationError("You must set a (at least {0} chars long) secret string for secrets['{1}']!".format(
+                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
         unknown = ['"{0}"'.format(name) for name in dir(self)
                   if not name.startswith('_') and
                   name not in DefaultConfig.__dict__ and
-                  not isinstance(getattr(self, name), (type(sys), type(DefaultConfig)))]
+                  not isinstance(getattr(self, name), (type(re), type(DefaultConfig)))]
         if unknown:
             msg = """
 Unknown configuration options: {0}.
        username_lower in password_lower or password_lower in username_lower:
         return _("Password is too easy to guess (password contains name or name contains password).")
 
-    keyboards = (ur"`1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./", # US kbd
-                 ur"^1234567890ß´qwertzuiopü+asdfghjklöä#yxcvbnm,.-", # german kbd
-                ) # add more keyboards!
+    keyboards = (ur"`1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./",  # US kbd
+                 ur"^1234567890ß´qwertzuiopü+asdfghjklöä#yxcvbnm,.-",  # german kbd
+                )  # TODO add more keyboards!
     for kbd in keyboards:
         rev_kbd = kbd[::-1]
         if password in kbd or password in rev_kbd or \
   # ==========================================================================
   'user': ('User Preferences', None, (
     ('user_defaults',
-      dict(
-        name=[],
-        display_name=None,
-        email=None,
-        openid=None,
-        css_url=None,
-        mailto_author=False,
-        edit_on_doubleclick=True,
-        scroll_page_after_edit=True,
-        show_comments=False,
-        want_trivial=False,
-        enc_password=u'',  # empty value == invalid hash
-        disabled=False,
-        bookmarks={},
-        quicklinks=[],
-        subscribed_items=[],
-        email_subscribed_events=[
+     {
+        NAME: [],
+        DISPLAY_NAME: None,
+        EMAIL: None,
+        OPENID: None,
+        CSS_URL: None,
+        MAILTO_AUTHOR: False,
+        EDIT_ON_DOUBLECLICK: True,
+        SCROLL_PAGE_AFTER_EDIT: True,
+        SHOW_COMMENTS: False,
+        WANT_TRIVIAL: False,
+        ENC_PASSWORD: u'',  # empty value == invalid hash
+        DISABLED: False,
+        BOOKMARKS: {},
+        QUICKLINKS: [],
+        SUBSCRIBED_ITEMS: [],
+        EMAIL_SUBSCRIBED_EVENTS: [
             # XXX PageChangedEvent.__name__
             # XXX PageRenamedEvent.__name__
             # XXX PageDeletedEvent.__name__
             # XXX PageCopiedEvent.__name__
             # XXX PageRevertedEvent.__name__
         ],
-        theme_name=None, # None -> use cfg.theme_default
-        edit_rows=0,
-        results_per_page=0,
-        locale=None, # None -> do browser language detection, otherwise just use this locale
-        timezone=None, # None -> use cfg.timezone_default
-      ),
+        THEME_NAME: None,  # None -> use cfg.theme_default
+        EDIT_ROWS: 0,
+        RESULTS_PER_PAGE: 0,
+        LOCALE: None,  # None -> do browser language detection, otherwise just use this locale
+        TIMEZONE: None,  # None -> use cfg.timezone_default
+     },
      'Default attributes of the user object'),
   )),
   # ==========================================================================
     ('refresh', None,
      "refresh = (minimum_delay_s, targets_allowed) enables use of `#refresh 5 PageName` processing instruction, targets_allowed must be either `'internal'` or `'external'`"),
 
-    ('siteid', 'MoinMoin', None), # XXX just default to some existing module name to
-                                  # make plugin loader etc. work for now
+    ('siteid', 'MoinMoin', None),  # XXX just default to some existing module name to
+                                   # make plugin loader etc. work for now
   )),
 }
 
     (
       ('functions', u'',
        'Access Control List for functions.'),
-      ('rights_contents', config.ACL_RIGHTS_CONTENTS,
+      ('rights_contents', ACL_RIGHTS_CONTENTS,
        'Valid tokens for right sides of content ACL entries.'),
-      ('rights_functions', config.ACL_RIGHTS_FUNCTIONS,
+      ('rights_functions', ACL_RIGHTS_FUNCTIONS,
        'Valid tokens for right sides of function ACL entries.'),
     )),
 
     )),
 }
 
+
 def _add_options_to_defconfig(opts, addgroup=True):
     for groupname in opts:
         group_short, group_doc, group_opts = opts[groupname]

MoinMoin/conftest.py

 
 # exclude some directories from py.test test discovery, pathes relative to this file
 collect_ignore = ['static',  # same
-                  '../wiki', # no tests there
-                  '../instance', # tw likes to use this for wiki data (non-revisioned)
+                  '../wiki',  # no tests there
+                  '../instance',  # tw likes to use this for wiki data (non-revisioned)
                  ]
 import atexit
 import os
 import py
 import MoinMoin.log
 
-""" Logging for tests to avoid useless output like timing information on stderr on test failures
-"""
+# Logging for tests to avoid useless output like timing information on stderr on test failures
 Moindir = py.path.local(__file__).dirname
 config_file = Moindir + '/_tests/test_logging.conf'
 MoinMoin.log.load_config(config_file)
         namespace_mapping=namespace_mapping,
         backend_mapping=backend_mapping,
         acl_mapping=acl_mapping,
-        create_storage=True, # create a fresh storage at each app start
-        destroy_storage=True, # kill all storage contents at app shutdown
-        create_index=True, # create a fresh index at each app start
-        destroy_index=True, # kill index contents at app shutdown
+        create_storage=True,  # create a fresh storage at each app start
+        destroy_storage=True,  # kill all storage contents at app shutdown
+        create_index=True,  # create a fresh index at each app start
+        destroy_index=True,  # kill index contents at app shutdown
     )
     app = create_app_ext(flask_config_dict=dict(SECRET_KEY='foobarfoobar'),
                          moin_config_class=given_config,
     before_wiki()
     return app, ctx
 
+
 def deinit_test_app(app, ctx):
     teardown_wiki('')
     ctx.pop()
 prev_ctx = None
 prev_cfg = None
 
+
 class MoinTestFunction(pytest.collect.Function):
     def setup(self):
         global prev_app, prev_ctx, prev_cfg
 def pytest_pycollect_makemodule(path, parent):
     return Module(path, parent=parent)
 
+
 def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
     if collector.funcnamefilter(name) and inspect.isfunction(obj):
         return MoinTestFunction(name, parent=collector)
 
+
 def pytest_pyfunc_call(pyfuncitem):
     """hook to intercept generators and run them as a single test items"""
     if inspect.isgeneratorfunction(pyfuncitem.obj):
             kwarg = item[1:]
             item[0](*kwarg)
 
+
 def pytest_report_header(config):
     return "The tests here are implemented only for pytest-2"
 
+
 class Module(pytest.collect.Module):
     def run(self, *args, **kwargs):
         if coverage is not None:

MoinMoin/constants/chartypes.py

 
-chars_upper = u"\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004a\u004b\u004c\u004d\u004e\u004f\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005a\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c6\u00c7\u00c8\u00c9\u00ca\u00cb\u00cc\u00cd\u00ce\u00cf\u00d0\u00d1\u00d2\u00d3\u00d4\u00d5\u00d6\u00d8\u00d9\u00da\u00db\u00dc\u00dd\u00de\u0100\u0102\u0104\u0106\u0108\u010a\u010c\u010e\u0110\u0112\u0114\u0116\u0118\u011a\u011c\u011e\u0120\u0122\u0124\u0126\u0128\u012a\u012c\u012e\u0130\u0132\u0134\u0136\u0139\u013b\u013d\u013f\u0141\u0143\u0145\u0147\u014a\u014c\u014e\u0150\u0152\u0154\u0156\u0158\u015a\u015c\u015e\u0160\u0162\u0164\u0166\u0168\u016a\u016c\u016e\u0170\u0172\u0174\u0176\u0178\u0179\u017b\u017d\u0181\u0182\u0184\u0186\u0187\u0189\u018a\u018b\u018e\u018f\u0190\u0191\u0193\u0194\u0196\u0197\u0198\u019c\u019d\u019f\u01a0\u01a2\u01a4\u01a6\u01a7\u01a9\u01ac\u01ae\u01af\u01b1\u01b2\u01b3\u01b5\u01b7\u01b8\u01bc\u01c4\u01c7\u01ca\u01cd\u01cf\u01d1\u01d3\u01d5\u01d7\u01d9\u01db\u01de\u01e0\u01e2\u01e4\u01e6\u01e8\u01ea\u01ec\u01ee\u01f1\u01f4\u01f6\u01f7\u01f8\u01fa\u01fc\u01fe\u0200\u0202\u0204\u0206\u0208\u020a\u020c\u020e\u0210\u0212\u0214\u0216\u0218\u021a\u021c\u021e\u0220\u0222\u0224\u0226\u0228\u022a\u022c\u022e\u0230\u0232\u0386\u0388\u0389\u038a\u038c\u038e\u038f\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03d2\u03d3\u03d4\u03d8\u03da\u03dc\u03de\u03e0\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0460\u0462\u0464\u0466\u0468\u046a\u046c\u046e\u0470\u0472\u0474\u0476\u0478\u047a\u047c\u047e\u0480\u048a\u048c\u048e\u0490\u0492\u0494\u0496\u0498\u049a\u049c\u049e\u04a0\u04a2\u04a4\u04a6\u04a8\u04aa\u04ac\u04ae\u04b0\u04b2\u04b4\u04b6\u04b8\u04ba\u04bc\u04be\u04c0\u04c1\u04c3\u04c5\u04c7\u04c9\u04cb\u04cd\u04d0\u04d2\u04d4\u04d6\u04d8\u04da\u04dc\u04de\u04e0\u04e2\u04e4\u04e6\u04e8\u04ea\u04ec\u04ee\u04f0\u04f2\u04f4\u04f8\u0500\u0502\u0504\u0506\u0508\u050a\u050c\u050e\u0531\u0532\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u10a0\u10a1\u10a2\u10a3\u10a4\u10a5\u10a6\u10a7\u10a8\u10a9\u10aa\u10ab\u10ac\u10ad\u10ae\u10af\u10b0\u10b1\u10b2\u10b3\u10b4\u10b5\u10b6\u10b7\u10b8\u10b9\u10ba\u10bb\u10bc\u10bd\u10be\u10bf\u10c0\u10c1\u10c2\u10c3\u10c4\u10c5\u1e00\u1e02\u1e04\u1e06\u1e08\u1e0a\u1e0c\u1e0e\u1e10\u1e12\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1e1e\u1e20\u1e22\u1e24\u1e26\u1e28\u1e2a\u1e2c\u1e2e\u1e30\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e50\u1e52\u1e54\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e68\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u1e7a\u1e7c\u1e7e\u1e80\u1e82\u1e84\u1e86\u1e88\u1e8a\u1e8c\u1e8e\u1e90\u1e92\u1e94\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6\u1ec8\u1eca\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0\u1ef2\u1ef4\u1ef6\u1ef8\u1f08\u1f09\u1f0a\u1f0b\u1f0c\u1f0d\u1f0e\u1f0f\u1f18\u1f19\u1f1a\u1f1b\u1f1c\u1f1d\u1f28\u1f29\u1f2a\u1f2b\u1f2c\u1f2d\u1f2e\u1f2f\u1f38\u1f39\u1f3a\u1f3b\u1f3c\u1f3d\u1f3e\u1f3f\u1f48\u1f49\u1f4a\u1f4b\u1f4c\u1f4d\u1f59\u1f5b\u1f5d\u1f5f\u1f68\u1f69\u1f6a\u1f6b\u1f6c\u1f6d\u1f6e\u1f6f\u1fb8\u1fb9\u1fba\u1fbb\u1fc8\u1fc9\u1fca\u1fcb\u1fd8\u1fd9\u1fda\u1fdb\u1fe8\u1fe9\u1fea\u1feb\u1fec\u1ff8\u1ff9\u1ffa\u1ffb\u2102\u2107\u210b\u210c\u210d\u2110\u2111\u2112\u2115\u2119\u211a\u211b\u211c\u211d\u2124\u2126\u2128\u212a\u212b\u212c\u212d\u2130\u2131\u2133\u213e\u213f\u2145\uff21\uff22\uff23\uff24\uff25\uff26\uff27\uff28\uff29\uff2a\uff2b\uff2c\uff2d\uff2e\uff2f\uff30\uff31\uff32\uff33\uff34\uff35\uff36\uff37\uff38\uff39\uff3a"
+CHARS_UPPER = u"\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004a\u004b\u004c\u004d\u004e\u004f\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005a\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c6\u00c7\u00c8\u00c9\u00ca\u00cb\u00cc\u00cd\u00ce\u00cf\u00d0\u00d1\u00d2\u00d3\u00d4\u00d5\u00d6\u00d8\u00d9\u00da\u00db\u00dc\u00dd\u00de\u0100\u0102\u0104\u0106\u0108\u010a\u010c\u010e\u0110\u0112\u0114\u0116\u0118\u011a\u011c\u011e\u0120\u0122\u0124\u0126\u0128\u012a\u012c\u012e\u0130\u0132\u0134\u0136\u0139\u013b\u013d\u013f\u0141\u0143\u0145\u0147\u014a\u014c\u014e\u0150\u0152\u0154\u0156\u0158\u015a\u015c\u015e\u0160\u0162\u0164\u0166\u0168\u016a\u016c\u016e\u0170\u0172\u0174\u0176\u0178\u0179\u017b\u017d\u0181\u0182\u0184\u0186\u0187\u0189\u018a\u018b\u018e\u018f\u0190\u0191\u0193\u0194\u0196\u0197\u0198\u019c\u019d\u019f\u01a0\u01a2\u01a4\u01a6\u01a7\u01a9\u01ac\u01ae\u01af\u01b1\u01b2\u01b3\u01b5\u01b7\u01b8\u01bc\u01c4\u01c7\u01ca\u01cd\u01cf\u01d1\u01d3\u01d5\u01d7\u01d9\u01db\u01de\u01e0\u01e2\u01e4\u01e6\u01e8\u01ea\u01ec\u01ee\u01f1\u01f4\u01f6\u01f7\u01f8\u01fa\u01fc\u01fe\u0200\u0202\u0204\u0206\u0208\u020a\u020c\u020e\u0210\u0212\u0214\u0216\u0218\u021a\u021c\u021e\u0220\u0222\u0224\u0226\u0228\u022a\u022c\u022e\u0230\u0232\u0386\u0388\u0389\u038a\u038c\u038e\u038f\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03d2\u03d3\u03d4\u03d8\u03da\u03dc\u03de\u03e0\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0460\u0462\u0464\u0466\u0468\u046a\u046c\u046e\u0470\u0472\u0474\u0476\u0478\u047a\u047c\u047e\u0480\u048a\u048c\u048e\u0490\u0492\u0494\u0496\u0498\u049a\u049c\u049e\u04a0\u04a2\u04a4\u04a6\u04a8\u04aa\u04ac\u04ae\u04b0\u04b2\u04b4\u04b6\u04b8\u04ba\u04bc\u04be\u04c0\u04c1\u04c3\u04c5\u04c7\u04c9\u04cb\u04cd\u04d0\u04d2\u04d4\u04d6\u04d8\u04da\u04dc\u04de\u04e0\u04e2\u04e4\u04e6\u04e8\u04ea\u04ec\u04ee\u04f0\u04f2\u04f4\u04f8\u0500\u0502\u0504\u0506\u0508\u050a\u050c\u050e\u0531\u0532\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u10a0\u10a1\u10a2\u10a3\u10a4\u10a5\u10a6\u10a7\u10a8\u10a9\u10aa\u10ab\u10ac\u10ad\u10ae\u10af\u10b0\u10b1\u10b2\u10b3\u10b4\u10b5\u10b6\u10b7\u10b8\u10b9\u10ba\u10bb\u10bc\u10bd\u10be\u10bf\u10c0\u10c1\u10c2\u10c3\u10c4\u10c5\u1e00\u1e02\u1e04\u1e06\u1e08\u1e0a\u1e0c\u1e0e\u1e10\u1e12\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1e1e\u1e20\u1e22\u1e24\u1e26\u1e28\u1e2a\u1e2c\u1e2e\u1e30\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e50\u1e52\u1e54\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e68\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u1e7a\u1e7c\u1e7e\u1e80\u1e82\u1e84\u1e86\u1e88\u1e8a\u1e8c\u1e8e\u1e90\u1e92\u1e94\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6\u1ec8\u1eca\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0\u1ef2\u1ef4\u1ef6\u1ef8\u1f08\u1f09\u1f0a\u1f0b\u1f0c\u1f0d\u1f0e\u1f0f\u1f18\u1f19\u1f1a\u1f1b\u1f1c\u1f1d\u1f28\u1f29\u1f2a\u1f2b\u1f2c\u1f2d\u1f2e\u1f2f\u1f38\u1f39\u1f3a\u1f3b\u1f3c\u1f3d\u1f3e\u1f3f\u1f48\u1f49\u1f4a\u1f4b\u1f4c\u1f4d\u1f59\u1f5b\u1f5d\u1f5f\u1f68\u1f69\u1f6a\u1f6b\u1f6c\u1f6d\u1f6e\u1f6f\u1fb8\u1fb9\u1fba\u1fbb\u1fc8\u1fc9\u1fca\u1fcb\u1fd8\u1fd9\u1fda\u1fdb\u1fe8\u1fe9\u1fea\u1feb\u1fec\u1ff8\u1ff9\u1ffa\u1ffb\u2102\u2107\u210b\u210c\u210d\u2110\u2111\u2112\u2115\u2119\u211a\u211b\u211c\u211d\u2124\u2126\u2128\u212a\u212b\u212c\u212d\u2130\u2131\u2133\u213e\u213f\u2145\uff21\uff22\uff23\uff24\uff25\uff26\uff27\uff28\uff29\uff2a\uff2b\uff2c\uff2d\uff2e\uff2f\uff30\uff31\uff32\uff33\uff34\uff35\uff36\uff37\uff38\uff39\uff3a"
 
-chars_lower = u"\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006a\u006b\u006c\u006d\u006e\u006f\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007a\u00aa\u00b5\u00ba\u00df\u00e0\u00e1\u00e2\u00e3\u00e4\u00e5\u00e6\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f8\u00f9\u00fa\u00fb\u00fc\u00fd\u00fe\u00ff\u0101\u0103\u0105\u0107\u0109\u010b\u010d\u010f\u0111\u0113\u0115\u0117\u0119\u011b\u011d\u011f\u0121\u0123\u0125\u0127\u0129\u012b\u012d\u012f\u0131\u0133\u0135\u0137\u0138\u013a\u013c\u013e\u0140\u0142\u0144\u0146\u0148\u0149\u014b\u014d\u014f\u0151\u0153\u0155\u0157\u0159\u015b\u015d\u015f\u0161\u0163\u0165\u0167\u0169\u016b\u016d\u016f\u0171\u0173\u0175\u0177\u017a\u017c\u017e\u017f\u0180\u0183\u0185\u0188\u018c\u018d\u0192\u0195\u0199\u019a\u019b\u019e\u01a1\u01a3\u01a5\u01a8\u01aa\u01ab\u01ad\u01b0\u01b4\u01b6\u01b9\u01ba\u01bd\u01be\u01bf\u01c6\u01c9\u01cc\u01ce\u01d0\u01d2\u01d4\u01d6\u01d8\u01da\u01dc\u01dd\u01df\u01e1\u01e3\u01e5\u01e7\u01e9\u01eb\u01ed\u01ef\u01f0\u01f3\u01f5\u01f9\u01fb\u01fd\u01ff\u0201\u0203\u0205\u0207\u0209\u020b\u020d\u020f\u0211\u0213\u0215\u0217\u0219\u021b\u021d\u021f\u0223\u0225\u0227\u0229\u022b\u022d\u022f\u0231\u0233\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u0390\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca\u03cb\u03cc\u03cd\u03ce\u03d0\u03d1\u03d5\u03d6\u03d7\u03d9\u03db\u03dd\u03df\u03e1\u03e3\u03e5\u03e7\u03e9\u03eb\u03ed\u03ef\u03f0\u03f1\u03f2\u03f3\u03f5\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0461\u0463\u0465\u0467\u0469\u046b\u046d\u046f\u0471\u0473\u0475\u0477\u0479\u047b\u047d\u047f\u0481\u048b\u048d\u048f\u0491\u0493\u0495\u0497\u0499\u049b\u049d\u049f\u04a1\u04a3\u04a5\u04a7\u04a9\u04ab\u04ad\u04af\u04b1\u04b3\u04b5\u04b7\u04b9\u04bb\u04bd\u04bf\u04c2\u04c4\u04c6\u04c8\u04ca\u04cc\u04ce\u04d1\u04d3\u04d5\u04d7\u04d9\u04db\u04dd\u04df\u04e1\u04e3\u04e5\u04e7\u04e9\u04eb\u04ed\u04ef\u04f1\u04f3\u04f5\u04f9\u0501\u0503\u0505\u0507\u0509\u050b\u050d\u050f\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582\u0583\u0584\u0585\u0586\u0587\u1e01\u1e03\u1e05\u1e07\u1e09\u1e0b\u1e0d\u1e0f\u1e11\u1e13\u1e15\u1e17\u1e19\u1e1b\u1e1d\u1e1f\u1e21\u1e23\u1e25\u1e27\u1e29\u1e2b\u1e2d\u1e2f\u1e31\u1e33\u1e35\u1e37\u1e39\u1e3b\u1e3d\u1e3f\u1e41\u1e43\u1e45\u1e47\u1e49\u1e4b\u1e4d\u1e4f\u1e51\u1e53\u1e55\u1e57\u1e59\u1e5b\u1e5d\u1e5f\u1e61\u1e63\u1e65\u1e67\u1e69\u1e6b\u1e6d\u1e6f\u1e71\u1e73\u1e75\u1e77\u1e79\u1e7b\u1e7d\u1e7f\u1e81\u1e83\u1e85\u1e87\u1e89\u1e8b\u1e8d\u1e8f\u1e91\u1e93\u1e95\u1e96\u1e97\u1e98\u1e99\u1e9a\u1e9b\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u1ec9\u1ecb\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1\u1ef3\u1ef5\u1ef7\u1ef9\u1f00\u1f01\u1f02\u1f03\u1f04\u1f05\u1f06\u1f07\u1f10\u1f11\u1f12\u1f13\u1f14\u1f15\u1f20\u1f21\u1f22\u1f23\u1f24\u1f25\u1f26\u1f27\u1f30\u1f31\u1f32\u1f33\u1f34\u1f35\u1f36\u1f37\u1f40\u1f41\u1f42\u1f43\u1f44\u1f45\u1f50\u1f51\u1f52\u1f53\u1f54\u1f55\u1f56\u1f57\u1f60\u1f61\u1f62\u1f63\u1f64\u1f65\u1f66\u1f67\u1f70\u1f71\u1f72\u1f73\u1f74\u1f75\u1f76\u1f77\u1f78\u1f79\u1f7a\u1f7b\u1f7c\u1f7d\u1f80\u1f81\u1f82\u1f83\u1f84\u1f85\u1f86\u1f87\u1f90\u1f91\u1f92\u1f93\u1f94\u1f95\u1f96\u1f97\u1fa0\u1fa1\u1fa2\u1fa3\u1fa4\u1fa5\u1fa6\u1fa7\u1fb0\u1fb1\u1fb2\u1fb3\u1fb4\u1fb6\u1fb7\u1fbe\u1fc2\u1fc3\u1fc4\u1fc6\u1fc7\u1fd0\u1fd1\u1fd2\u1fd3\u1fd6\u1fd7\