<commit_before># -*- coding: utf-8 -*-
##
##
## This file is part of Indico.
## Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN).
##
## Indico is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License as
## published by the Free Software Foundation; either version 3 of the
## License, or (at your option) any later version.
##
## Indico is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Indico;if not, see <http://www.gnu.org/licenses/>.

from __future__ import absolute_import

import inspect
import os
import re
import time

from flask import request, redirect, Blueprint
from flask import current_app as app
from flask import url_for as _url_for
from flask import send_file as _send_file
from werkzeug.wrappers import Response as WerkzeugResponse
from werkzeug.datastructures import Headers, FileStorage
from werkzeug.exceptions import NotFound, HTTPException
from werkzeug.routing import BaseConverter, UnicodeConverter, RequestRedirect, BuildError
from werkzeug.urls import url_parse
from werkzeug.utils import secure_filename

from indico.util.caching import memoize
from indico.web.handlers import RHHtdocs


def _convert_request_value(x):
    if isinstance(x, unicode):
        return x.encode('utf-8')
    elif isinstance(x, FileStorage):
        x.file = x.stream
        if isinstance(x.filename, unicode):
            x.filename = x.filename.encode('utf-8')
        return x
    raise ValueError('Unexpected item in request data: %s' % type(x))


def create_flat_args():
    """Creates a dict containing the GET/POST arguments in a style old indico code expects.

    Do not use this for anything new - use request.* directly instead!"""
    args = request.args.copy()
    for key, values in request.form.iterlists():
        args.setlist(key, values)
    for key, values in request.files.iterlists():
        args.setlist(key, values)
    flat_args = {}
    for key, item in args.iterlists():
        flat_args[key] = map(_convert_request_value, item) if len(item) > 1 else _convert_request_value(item[0])
    return flat_args


@memoize
def make_view_func(obj):
    """Turns an object in to a view function.

    This function is called on each view_func passed to IndicoBlueprint.add_url_route().
    It handles RH classes and normal functions.
    """
    if inspect.isclass(obj):
        # Some class
        if issubclass(obj, RHHtdocs):
            # Special RH used to provide htdocs folders of plugins
            def wrapper(filepath, plugin=None):
                path = obj.calculatePath(filepath, plugin=plugin)
                if not os.path.isfile(path):
                    raise NotFound
                return _send_file(path)
        elif hasattr(obj, 'process'):
            # Indico RH
            # TODO: check if we can get rid of the unused req arg of RHs?
            if len(inspect.getargspec(obj.__init__).args) < 2:
                make_instance = lambda: obj()
            else:
                make_instance = lambda: obj(None)

            def wrapper(**kwargs):
                params = create_flat_args()
                params.update(kwargs)
                return make_instance().process(params)
        else:
            # Some class we didn't expect.
            raise ValueError('Unexpected view func class: %r' % obj)

        wrapper.__name__ = obj.__name__
        wrapper.__doc__ = obj.__doc__
        return wrapper
    elif callable(obj):
        # Normal function
        return obj
    else:
        # If you ever get this error you should probably feel bad. :)
        raise ValueError('Unexpected view func: %r' % obj)


@memoize
def redirect_view(endpoint, code=302):
    """Creates a view function that redirects to the given endpoint."""
    def _redirect(**kwargs):
        return redirect(_url_for(endpoint, **kwargs), code=code)

    return _redirect


def iter_blueprint_rules(blueprint):
    for func in blueprint.deferred_functions:
        yield dict(zip(func.func_code.co_freevars, (c.cell_contents for c in func.func_closure)))


def legacy_rule_from_endpoint(endpoint):
    endpoint = re.sub(r':\d+$', '', endpoint)
    if '-' in endpoint:
        return '/' + endpoint.replace('-', '.py/')
    else:
        return '/' + endpoint + '.py'


def make_compat_redirect_func(blueprint, endpoint, view_func=None):
    def _redirect():
        # In case of POST we can't safely redirect since the method would switch to GET
        # and thus the request would most likely fail.
        if view_func and request.method == 'POST':
            return view_func(**request.view_args)
        # Ugly hack to get non-list arguments unless they are used multiple times.
        # This is necessary since passing a list for an URL path argument breaks things.
        args = dict((k, v[0] if len(v) == 1 else v) for k, v in request.args.iterlists())
        try:
            target = _url_for('%s.%s' % (blueprint.name, endpoint), **args)
        except BuildError:
            raise NotFound
        return redirect(target, 302 if app.debug else 301)
    return _redirect


def make_compat_blueprint(blueprint):
    from indico.web.flask.blueprints.legacy import legacy_endpoints

    compat = Blueprint('compat_' + blueprint.name, __name__)
    used_endpoints = set()
    for rule in iter_blueprint_rules(blueprint):
        # Rules without an endpoint are never legacy rules
        if not rule.get('endpoint'):
            continue
        # Rules which do not have a matching .py file are also not legacy rules
        if rule['endpoint'] not in legacy_endpoints:
            continue

        endpoint = rule['endpoint']
        i = 0
        while endpoint in used_endpoints:
            i += 1
            endpoint = '%s:%s' % (rule['endpoint'], i)
        used_endpoints.add(endpoint)
        compat.add_url_rule(legacy_rule_from_endpoint(endpoint), endpoint,
                            make_compat_redirect_func(blueprint, rule['endpoint'], rule['view_func']),
                            methods=rule['options'].get('methods'))
    return compat


def endpoint_for_url(url):
    urldata = url_parse(url)
    adapter = app.url_map.bind(urldata.netloc)
    try:
        return adapter.match(urldata.path)
    except RequestRedirect, e:
        return endpoint_for_url(e.new_url)
    except HTTPException:
        return None


def url_for(endpoint, *targets, **values):
    """Wrapper for Flask's url_for() function.

    Instead of an endpoint you can also pass an URLHandler - in this case **only** its _endpoint will be used.
    However, there is usually no need to do so. This is just so you can use it in places where sometimes a UH
    might be passed instead.

    The `target` argument allows you to pass some object having a `getLocator` method returning a dict. This
    should be used e.g. when generating an URL for an event since `getLocator()` provides the `{'confId': 123}`
    dict instead of you having to pass `confId=event.getId()` as a kwarg.

    For details on Flask's url_for, please see its documentation.
    Anyway, the important arguments you can put in `values` besides actual arguments are:
    _external: if set to `True`, an absolute URL is generated
    _secure: if True/False, set _scheme to https/http if possible (only with _external)
    _scheme: a string specifying the desired URL scheme (only with _external) - use _secure if possible!
    _anchor: if provided this is added as #anchor to the URL.
    """

    if hasattr(endpoint, '_endpoint'):
        endpoint = endpoint._endpoint

    secure = values.pop('_secure', None)
    if secure is not None:
        from indico.core.config import Config
        if secure and Config.getInstance().getBaseSecureURL():
            values['_scheme'] = 'https'
        elif not secure:
            values['_scheme'] = 'http'

    if targets:
        locator = {}
        for target in targets:
            if target:  # don't fail on None or mako's Undefined
                locator.update(target.getLocator())
        intersection = set(locator.iterkeys()) & set(values.iterkeys())
        if intersection:
            raise ValueError('url_for kwargs collide with locator: %s' % ', '.join(intersection))
        values.update(locator)

    for key, value in values.iteritems():
        # Avoid =True and =False in the URL
        if isinstance(value, bool):
            values[key] = int(value)<commit_msg>Support locator property in addition to getLocator<commit_after># -*- coding: utf-8 -*-
##
##
## This file is part of Indico.
## Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN).
##
## Indico is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License as
## published by the Free Software Foundation; either version 3 of the
## License, or (at your option) any later version.
##
## Indico is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Indico;if not, see <http://www.gnu.org/licenses/>.

from __future__ import absolute_import

import inspect
import os
import re
import time

from flask import request, redirect, Blueprint
from flask import current_app as app
from flask import url_for as _url_for
from flask import send_file as _send_file
from werkzeug.wrappers import Response as WerkzeugResponse
from werkzeug.datastructures import Headers, FileStorage
from werkzeug.exceptions import NotFound, HTTPException
from werkzeug.routing import BaseConverter, UnicodeConverter, RequestRedirect, BuildError
from werkzeug.urls import url_parse
from werkzeug.utils import secure_filename

from indico.util.caching import memoize
from indico.web.handlers import RHHtdocs


def _convert_request_value(x):
    if isinstance(x, unicode):
        return x.encode('utf-8')
    elif isinstance(x, FileStorage):
        x.file = x.stream
        if isinstance(x.filename, unicode):
            x.filename = x.filename.encode('utf-8')
        return x
    raise ValueError('Unexpected item in request data: %s' % type(x))


def create_flat_args():
    """Creates a dict containing the GET/POST arguments in a style old indico code expects.

    Do not use this for anything new - use request.* directly instead!"""
    args = request.args.copy()
    for key, values in request.form.iterlists():
        args.setlist(key, values)
    for key, values in request.files.iterlists():
        args.setlist(key, values)
    flat_args = {}
    for key, item in args.iterlists():
        flat_args[key] = map(_convert_request_value, item) if len(item) > 1 else _convert_request_value(item[0])
    return flat_args


@memoize
def make_view_func(obj):
    """Turns an object in to a view function.

    This function is called on each view_func passed to IndicoBlueprint.add_url_route().
    It handles RH classes and normal functions.
    """
    if inspect.isclass(obj):
        # Some class
        if issubclass(obj, RHHtdocs):
            # Special RH used to provide htdocs folders of plugins
            def wrapper(filepath, plugin=None):
                path = obj.calculatePath(filepath, plugin=plugin)
                if not os.path.isfile(path):
                    raise NotFound
                return _send_file(path)
        elif hasattr(obj, 'process'):
            # Indico RH
            # TODO: check if we can get rid of the unused req arg of RHs?
            if len(inspect.getargspec(obj.__init__).args) < 2:
                make_instance = lambda: obj()
            else:
                make_instance = lambda: obj(None)

            def wrapper(**kwargs):
                params = create_flat_args()
                params.update(kwargs)
                return make_instance().process(params)
        else:
            # Some class we didn't expect.
            raise ValueError('Unexpected view func class: %r' % obj)

        wrapper.__name__ = obj.__name__
        wrapper.__doc__ = obj.__doc__
        return wrapper
    elif callable(obj):
        # Normal function
        return obj
    else:
        # If you ever get this error you should probably feel bad. :)
        raise ValueError('Unexpected view func: %r' % obj)


@memoize
def redirect_view(endpoint, code=302):
    """Creates a view function that redirects to the given endpoint."""
    def _redirect(**kwargs):
        return redirect(_url_for(endpoint, **kwargs), code=code)

    return _redirect


def iter_blueprint_rules(blueprint):
    for func in blueprint.deferred_functions:
        yield dict(zip(func.func_code.co_freevars, (c.cell_contents for c in func.func_closure)))


def legacy_rule_from_endpoint(endpoint):
    endpoint = re.sub(r':\d+$', '', endpoint)
    if '-' in endpoint:
        return '/' + endpoint.replace('-', '.py/')
    else:
        return '/' + endpoint + '.py'


def make_compat_redirect_func(blueprint, endpoint, view_func=None):
    def _redirect():
        # In case of POST we can't safely redirect since the method would switch to GET
        # and thus the request would most likely fail.
        if view_func and request.method == 'POST':
            return view_func(**request.view_args)
        # Ugly hack to get non-list arguments unless they are used multiple times.
        # This is necessary since passing a list for an URL path argument breaks things.
        args = dict((k, v[0] if len(v) == 1 else v) for k, v in request.args.iterlists())
        try:
            target = _url_for('%s.%s' % (blueprint.name, endpoint), **args)
        except BuildError:
            raise NotFound
        return redirect(target, 302 if app.debug else 301)
    return _redirect


def make_compat_blueprint(blueprint):
    from indico.web.flask.blueprints.legacy import legacy_endpoints

    compat = Blueprint('compat_' + blueprint.name, __name__)
    used_endpoints = set()
    for rule in iter_blueprint_rules(blueprint):
        # Rules without an endpoint are never legacy rules
        if not rule.get('endpoint'):
            continue
        # Rules which do not have a matching .py file are also not legacy rules
        if rule['endpoint'] not in legacy_endpoints:
            continue

        endpoint = rule['endpoint']
        i = 0
        while endpoint in used_endpoints:
            i += 1
            endpoint = '%s:%s' % (rule['endpoint'], i)
        used_endpoints.add(endpoint)
        compat.add_url_rule(legacy_rule_from_endpoint(endpoint), endpoint,
                            make_compat_redirect_func(blueprint, rule['endpoint'], rule['view_func']),
                            methods=rule['options'].get('methods'))
    return compat


def endpoint_for_url(url):
    urldata = url_parse(url)
    adapter = app.url_map.bind(urldata.netloc)
    try:
        return adapter.match(urldata.path)
    except RequestRedirect, e:
        return endpoint_for_url(e.new_url)
    except HTTPException:
        return None


def url_for(endpoint, *targets, **values):
    """Wrapper for Flask's url_for() function.

    Instead of an endpoint you can also pass an URLHandler - in this case **only** its _endpoint will be used.
    However, there is usually no need to do so. This is just so you can use it in places where sometimes a UH
    might be passed instead.

    The `target` argument allows you to pass some object having a `locator` property or `getLocator` method
    returning a dict. This should be used e.g. when generating an URL for an event since ``getLocator()``
    provides the ``{'confId': 123}`` dict instead of you having to pass ``confId=event.getId()`` as a kwarg.

    For details on Flask's url_for, please see its documentation.
    Anyway, the important arguments you can put in `values` besides actual arguments are:
    _external: if set to `True`, an absolute URL is generated
    _secure: if True/False, set _scheme to https/http if possible (only with _external)
    _scheme: a string specifying the desired URL scheme (only with _external) - use _secure if possible!
    _anchor: if provided this is added as #anchor to the URL.
    """

    if hasattr(endpoint, '_endpoint'):
        endpoint = endpoint._endpoint

    secure = values.pop('_secure', None)
    if secure is not None:
        from indico.core.config import Config
        if secure and Config.getInstance().getBaseSecureURL():
            values['_scheme'] = 'https'
        elif not secure:
            values['_scheme'] = 'http'

    if targets:
        locator = {}
        for target in targets:
            if target:  # don't fail on None or mako's Undefined
                try:
                    target_locator = target.locator
                except AttributeError:
                    target_locator = target.getLocator()
                locator.update(target_locator)
        intersection = set(locator.iterkeys()) & set(values.iterkeys())
        if intersection:
            raise ValueError('url_for kwargs collide with locator: %s' % ', '.join(intersection))
        values.update(locator)

    for key, value in values.iteritems():
        # Avoid =True and =False in the URL
        if isinstance(value, bool):
            values[key] = int(value)
{'fraction_non_alphanumeric': 0.08758006915707726, 'fraction_numerical': 0.003514539992063942, 'mean_word_length': 2.7568143100511073, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5182891', 'n_tokens_mistral': 4924, 'n_tokens_neox': 4771, 'n_words': 1795}