<commit_before>## 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/>.

import os
import re
from heapq import heappush
from urlparse import urlparse

from flask_pluginengine import (PluginEngine, Plugin, PluginBlueprintMixin, PluginBlueprintSetupStateMixin,
                                current_plugin, render_plugin_template)
from markupsafe import Markup
from webassets import Environment, Bundle

from indico.core import signals
from indico.core.config import Config
from indico.core.db import db
from indico.core.db.sqlalchemy.util.models import import_all_models
from indico.core.models.settings import SettingsProxy
from indico.util.decorators import cached_classproperty, classproperty
from indico.web.assets import SASS_BASE_MODULES, configure_pyscss
from indico.web.flask.util import url_for
from indico.web.flask.wrappers import IndicoBlueprint, IndicoBlueprintSetupState
from MaKaC.webinterface.pages.base import WPJinjaMixin

class IndicoPlugin(Plugin):
    """Base class for an Indico plugin

    All your plugins need to inherit from this class. It extends the
    `Plugin` class from Flask-PluginEngine with useful indico-specific
    functionality that makes it easier to write custom plugins.

    When creating your plugin, the class-level docstring is used to
    generate the friendly name and description of a plugin. Its first
    line becomes the name while everything else goes into the description.

    This class provides methods for some of the more common hooks Indico
    provides. Additional signals are defined in :mod:`~indico.core.signals`
    and can be connected to custom functions using :meth:`connect`.
    """

    #: WTForm for the plugin's settings. All fields must return JSON-serializable types.
    settings_form = None
    #: A dictionary which can contain the kwargs for a specific field in the `settings_form`.
    settings_form_field_opts = {}
    #: A dictionary containing default values for settings
    default_settings = {}

    def init(self):
        """Called when the plugin is being loaded/initialized.

        If you want to run custom initialization code, this is the
        method to override. Make sure to call the base method or
        the other overridable methods in this class will not be
        called anymore.
        """
        self.alembic_versions_path = os.path.join(self.root_path, 'migrations')
        self.connect(signals.cli, self.add_cli_command)
        self.connect(signals.shell_context, lambda _, add_to_context: self.extend_shell_context(add_to_context))
        self.connect(signals.get_blueprints, lambda app: (self, self.get_blueprints()))
        self._setup_assets()
        self._import_models()

    def _setup_assets(self):
        config = Config.getInstance()
        url_base_path = urlparse(config.getBaseURL()).path
        output_dir = os.path.join(self.root_path, 'static')
        url = '{}/static/plugins/{}'.format(url_base_path, self.name)
        self.assets = Environment(output_dir, url, debug=config.getDebug())
        configure_pyscss(self.assets)
        self.register_assets()

    def _import_models(self):
        old_models = set(db.Model._decl_class_registry.items())
        import_all_models(self.package_name)
        added_models = set(db.Model._decl_class_registry.items()) - old_models
        # Ensure that only plugin schemas have been touched. It would be nice if we could actually
        # restrict a plugin to plugin_PLUGNNAME but since we load all models from the plugin's package
        # which could contain more than one plugin this is not easily possible.
        for name, model in added_models:
            schema = model.__table__.schema
            if not schema.startswith('plugin_'):
                raise Exception("Plugin '{}' added a model which is not in a plugin schema ('{}' in '{}')"
                                .format(self.name, name, schema))

    def get_blueprints(self):
        """Return blueprints to be registered on the application

        A single blueprint can be returned directly, for multiple blueprint you need
        to yield them or return an iterable.
        """
        pass

    def add_cli_command(self, manager):
        """Add custom commands/submanagers to the manager of the `indico` cli tool."""
        pass

    def extend_shell_context(self, add_to_context):
        """Add custom items to the `indico shell` context."""
        pass

    def register_assets(self):
        """Add assets to the plugin's webassets environment.

        In most cases the whole method can consist of calls to
        :meth:`register_js_bundle` and :meth:`register_css_bundle`.
        """
        pass

    def register_js_bundle(self, name, *files):
        """Registers a JS bundle in the plugin's webassets environment"""
        pretty_name = re.sub(r'_js$', '', name)
        bundle = Bundle(*files, filters='rjsmin', output='js/{}_%(version)s.min.js'.format(pretty_name))
        self.assets.register(name, bundle)

    def register_css_bundle(self, name, *files):
        """Registers an SCSS bundle in the plugin's webassets environment"""
        pretty_name = re.sub(r'_css$', '', name)
        bundle = Bundle(*files,
                        filters=('pyscss', 'cssrewrite', 'cssmin'),
                        output='css/{}_%(version)s.min.css'.format(pretty_name),
                        depends=SASS_BASE_MODULES)
        self.assets.register(name, bundle)

    def inject_css(self, name, view_class=None):
        """Injects a CSS bundle into Indico's pages

        :param name: Name of the bundle
        :param view_class: If a WP class is specified, only inject it into pages using that class
        """
        kwargs = {}
        if view_class is not None:
            kwargs['sender'] = view_class
        self.connect(signals.inject_css, lambda _: self.assets[name].urls(), **kwargs)

    def inject_js(self, name, view_class=None):
        """Injects a JS bundle into Indico's pages

        :param name: Name of the bundle
        :param view_class: If a WP class is specified, only inject it into pages using that class
        """
        kwargs = {}
        if view_class is not None:
            kwargs['sender'] = view_class
        self.connect(signals.inject_js, lambda _: self.assets[name].urls(), **kwargs)

    def template_hook(self, name, receiver, priority=50, markup=True):
        """Registers a function to be called when a template hook is invoked.

        The receiver function should always support arbitrary ``**kwargs``
        to prevent breakage in future Indico versions which might add new
        arguments to a hook::

            def receiver(something, **kwargs):
                return do_stuff(something)

        It needs to return a unicode string. If you intend to return plaintext
        it is adviable to set the `markup` param to `False` which results in the
        string being considered "unsafe" which will cause it to be HTML-escaped.

        :param name: The name of the template hook.
        :param receiver: The receiver function.
        :param priority: The priority to use when multiple plugins
                         inject data for the same hook.
        :param markup: If the returned data is HTML
        """
        self.connect(signals.template_hook, lambda _, **kw: (markup, priority, receiver(**kw)), sender=unicode(name))

    @classproperty
    @classmethod
    def instance(cls):
        """The Plugin instance used by the current app"""
        instance = plugin_engine.get_plugin(cls.name)
        if instance is None:
            raise RuntimeError('Plugin is not active in the current app')
        return instance

    @cached_classproperty
    @classmethod
    def settings(cls):
        """:class:`SettingsProxy` for the plugin's settings"""
        if cls.name is None:
            raise RuntimeError('Plugin has not been loaded yet')
        instance = cls.instance
        with instance.plugin_context():  # in case the default settings come from a property
            return SettingsProxy('plugin_{}'.format(cls.name), instance.default_settings)

def include_plugin_js_assets(bundle_name):
    """Jinja template function to generate HTML tags for a plugin JS asset bundle."""
    return Markup('\n'.join('<script src="{}"></script>'.format(url)
                            for url in current_plugin.assets[bundle_name].urls()))

def include_plugin_css_assets(bundle_name):
    """Jinja template function to generate HTML tags for a plugin CSS asset bundle."""
    return Markup('\n'.join('<link rel="stylesheet" type="text/css" href="{}">'.format(url)
                            for url in current_plugin.assets[bundle_name].urls()))

def plugin_hook(*name, **kwargs):
    """Template function to let plugins add their own data to a template.

    :param name: The name of the hook.  Only accepts one argument.
    :param kwargs: Data to pass to the signal receivers.
    """
    if len(name) != 1:
        raise TypeError('plugin_hook() accepts only one positional argument, {} given'.format(len(name)))
    name = name[0]
    values = []
    for _, (is_markup, priority, value) in signals.template_hook.send(unicode(name), **kwargs):
        if is_markup:
            value = Markup(value)
        heappush(values, (priority, value))
    return Markup(u'\n').join(x[1] for x in values)

def url_for_plugin(endpoint, *targets, **values):
    """Like :func:`~indico.web.flask.util.url_for` but prepending ``'plugin_'`` to the blueprint name."""
    if '.' in endpoint[1:]:  # 'foo' or '.foo' should not get the prefix
        endpoint = 'plugin_{}'.format(endpoint)
    return url_for(endpoint, *targets, **values)

class IndicoPluginEngine(PluginEngine):
    plugin_class = IndicoPlugin

class IndicoPluginBlueprintSetupState(PluginBlueprintSetupStateMixin, IndicoBlueprintSetupState):
    pass

class IndicoPluginBlueprint(PluginBlueprintMixin, IndicoBlueprint):
    """The Blueprint class all plugins need to use.

    It contains the necessary logic to run the blueprint's view
    functions inside the correct plugin context and to make the
    static folder work.
    """
    def make_setup_state(self, app, options, first_registration=False):
        return IndicoPluginBlueprintSetupState(self, app, options, first_registration)

class WPJinjaMixinPlugin(WPJinjaMixin):
    def _getPageContent(self, params):
        template = params.pop('_jinja_template')
        return render_plugin_template(template, **params)

plugin_engine = IndicoPluginEngine()
<commit_msg>Fix plugin static rule with url prefix<commit_after>## 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/>.

import os
import re
from heapq import heappush
from urlparse import urlparse

from flask_pluginengine import (PluginEngine, Plugin, PluginBlueprintMixin, PluginBlueprintSetupStateMixin,
                                current_plugin, render_plugin_template)
from markupsafe import Markup
from webassets import Environment, Bundle

from indico.core import signals
from indico.core.config import Config
from indico.core.db import db
from indico.core.db.sqlalchemy.util.models import import_all_models
from indico.core.models.settings import SettingsProxy
from indico.util.decorators import cached_classproperty, classproperty
from indico.web.assets import SASS_BASE_MODULES, configure_pyscss
from indico.web.flask.util import url_for
from indico.web.flask.wrappers import IndicoBlueprint, IndicoBlueprintSetupState
from MaKaC.webinterface.pages.base import WPJinjaMixin

class IndicoPlugin(Plugin):
    """Base class for an Indico plugin

    All your plugins need to inherit from this class. It extends the
    `Plugin` class from Flask-PluginEngine with useful indico-specific
    functionality that makes it easier to write custom plugins.

    When creating your plugin, the class-level docstring is used to
    generate the friendly name and description of a plugin. Its first
    line becomes the name while everything else goes into the description.

    This class provides methods for some of the more common hooks Indico
    provides. Additional signals are defined in :mod:`~indico.core.signals`
    and can be connected to custom functions using :meth:`connect`.
    """

    #: WTForm for the plugin's settings. All fields must return JSON-serializable types.
    settings_form = None
    #: A dictionary which can contain the kwargs for a specific field in the `settings_form`.
    settings_form_field_opts = {}
    #: A dictionary containing default values for settings
    default_settings = {}

    def init(self):
        """Called when the plugin is being loaded/initialized.

        If you want to run custom initialization code, this is the
        method to override. Make sure to call the base method or
        the other overridable methods in this class will not be
        called anymore.
        """
        self.alembic_versions_path = os.path.join(self.root_path, 'migrations')
        self.connect(signals.cli, self.add_cli_command)
        self.connect(signals.shell_context, lambda _, add_to_context: self.extend_shell_context(add_to_context))
        self.connect(signals.get_blueprints, lambda app: (self, self.get_blueprints()))
        self._setup_assets()
        self._import_models()

    def _setup_assets(self):
        config = Config.getInstance()
        url_base_path = urlparse(config.getBaseURL()).path
        output_dir = os.path.join(self.root_path, 'static')
        url = '{}/static/plugins/{}'.format(url_base_path, self.name)
        self.assets = Environment(output_dir, url, debug=config.getDebug())
        configure_pyscss(self.assets)
        self.register_assets()

    def _import_models(self):
        old_models = set(db.Model._decl_class_registry.items())
        import_all_models(self.package_name)
        added_models = set(db.Model._decl_class_registry.items()) - old_models
        # Ensure that only plugin schemas have been touched. It would be nice if we could actually
        # restrict a plugin to plugin_PLUGNNAME but since we load all models from the plugin's package
        # which could contain more than one plugin this is not easily possible.
        for name, model in added_models:
            schema = model.__table__.schema
            if not schema.startswith('plugin_'):
                raise Exception("Plugin '{}' added a model which is not in a plugin schema ('{}' in '{}')"
                                .format(self.name, name, schema))

    def get_blueprints(self):
        """Return blueprints to be registered on the application

        A single blueprint can be returned directly, for multiple blueprint you need
        to yield them or return an iterable.
        """
        pass

    def add_cli_command(self, manager):
        """Add custom commands/submanagers to the manager of the `indico` cli tool."""
        pass

    def extend_shell_context(self, add_to_context):
        """Add custom items to the `indico shell` context."""
        pass

    def register_assets(self):
        """Add assets to the plugin's webassets environment.

        In most cases the whole method can consist of calls to
        :meth:`register_js_bundle` and :meth:`register_css_bundle`.
        """
        pass

    def register_js_bundle(self, name, *files):
        """Registers a JS bundle in the plugin's webassets environment"""
        pretty_name = re.sub(r'_js$', '', name)
        bundle = Bundle(*files, filters='rjsmin', output='js/{}_%(version)s.min.js'.format(pretty_name))
        self.assets.register(name, bundle)

    def register_css_bundle(self, name, *files):
        """Registers an SCSS bundle in the plugin's webassets environment"""
        pretty_name = re.sub(r'_css$', '', name)
        bundle = Bundle(*files,
                        filters=('pyscss', 'cssrewrite', 'cssmin'),
                        output='css/{}_%(version)s.min.css'.format(pretty_name),
                        depends=SASS_BASE_MODULES)
        self.assets.register(name, bundle)

    def inject_css(self, name, view_class=None):
        """Injects a CSS bundle into Indico's pages

        :param name: Name of the bundle
        :param view_class: If a WP class is specified, only inject it into pages using that class
        """
        kwargs = {}
        if view_class is not None:
            kwargs['sender'] = view_class
        self.connect(signals.inject_css, lambda _: self.assets[name].urls(), **kwargs)

    def inject_js(self, name, view_class=None):
        """Injects a JS bundle into Indico's pages

        :param name: Name of the bundle
        :param view_class: If a WP class is specified, only inject it into pages using that class
        """
        kwargs = {}
        if view_class is not None:
            kwargs['sender'] = view_class
        self.connect(signals.inject_js, lambda _: self.assets[name].urls(), **kwargs)

    def template_hook(self, name, receiver, priority=50, markup=True):
        """Registers a function to be called when a template hook is invoked.

        The receiver function should always support arbitrary ``**kwargs``
        to prevent breakage in future Indico versions which might add new
        arguments to a hook::

            def receiver(something, **kwargs):
                return do_stuff(something)

        It needs to return a unicode string. If you intend to return plaintext
        it is adviable to set the `markup` param to `False` which results in the
        string being considered "unsafe" which will cause it to be HTML-escaped.

        :param name: The name of the template hook.
        :param receiver: The receiver function.
        :param priority: The priority to use when multiple plugins
                         inject data for the same hook.
        :param markup: If the returned data is HTML
        """
        self.connect(signals.template_hook, lambda _, **kw: (markup, priority, receiver(**kw)), sender=unicode(name))

    @classproperty
    @classmethod
    def instance(cls):
        """The Plugin instance used by the current app"""
        instance = plugin_engine.get_plugin(cls.name)
        if instance is None:
            raise RuntimeError('Plugin is not active in the current app')
        return instance

    @cached_classproperty
    @classmethod
    def settings(cls):
        """:class:`SettingsProxy` for the plugin's settings"""
        if cls.name is None:
            raise RuntimeError('Plugin has not been loaded yet')
        instance = cls.instance
        with instance.plugin_context():  # in case the default settings come from a property
            return SettingsProxy('plugin_{}'.format(cls.name), instance.default_settings)

def include_plugin_js_assets(bundle_name):
    """Jinja template function to generate HTML tags for a plugin JS asset bundle."""
    return Markup('\n'.join('<script src="{}"></script>'.format(url)
                            for url in current_plugin.assets[bundle_name].urls()))

def include_plugin_css_assets(bundle_name):
    """Jinja template function to generate HTML tags for a plugin CSS asset bundle."""
    return Markup('\n'.join('<link rel="stylesheet" type="text/css" href="{}">'.format(url)
                            for url in current_plugin.assets[bundle_name].urls()))

def plugin_hook(*name, **kwargs):
    """Template function to let plugins add their own data to a template.

    :param name: The name of the hook.  Only accepts one argument.
    :param kwargs: Data to pass to the signal receivers.
    """
    if len(name) != 1:
        raise TypeError('plugin_hook() accepts only one positional argument, {} given'.format(len(name)))
    name = name[0]
    values = []
    for _, (is_markup, priority, value) in signals.template_hook.send(unicode(name), **kwargs):
        if is_markup:
            value = Markup(value)
        heappush(values, (priority, value))
    return Markup(u'\n').join(x[1] for x in values)

def url_for_plugin(endpoint, *targets, **values):
    """Like :func:`~indico.web.flask.util.url_for` but prepending ``'plugin_'`` to the blueprint name."""
    if '.' in endpoint[1:]:  # 'foo' or '.foo' should not get the prefix
        endpoint = 'plugin_{}'.format(endpoint)
    return url_for(endpoint, *targets, **values)

class IndicoPluginEngine(PluginEngine):
    plugin_class = IndicoPlugin

class IndicoPluginBlueprintSetupState(PluginBlueprintSetupStateMixin, IndicoBlueprintSetupState):
    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        if rule.startswith('/static'):
            with self._unprefixed():
                super(IndicoPluginBlueprintSetupState, self).add_url_rule(rule, endpoint, view_func, **options)
        else:
            super(IndicoPluginBlueprintSetupState, self).add_url_rule(rule, endpoint, view_func, **options)

class IndicoPluginBlueprint(PluginBlueprintMixin, IndicoBlueprint):
    """The Blueprint class all plugins need to use.

    It contains the necessary logic to run the blueprint's view
    functions inside the correct plugin context and to make the
    static folder work.
    """
    def make_setup_state(self, app, options, first_registration=False):
        return IndicoPluginBlueprintSetupState(self, app, options, first_registration)

class WPJinjaMixinPlugin(WPJinjaMixin):
    def _getPageContent(self, params):
        template = params.pop('_jinja_template')
        return render_plugin_template(template, **params)

plugin_engine = IndicoPluginEngine()
<|endoftext|>


<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/>.

"""
HTTP API - Handlers
"""

# python stdlib imports
import hashlib
import hmac
import posixpath
import re
import time
import urllib
from flask import request, session
from urlparse import parse_qs
from ZODB.POSException import ConflictError

# indico imports
from werkzeug.exceptions import NotFound
from indico.web.http_api import HTTPAPIHook
from indico.web.http_api.auth import APIKeyHolder
from indico.web.http_api.responses import HTTPAPIResult, HTTPAPIError
from indico.web.http_api.util import get_query_parameter
from indico.web.http_api import API_MODE_ONLYKEY, API_MODE_SIGNED, API_MODE_ONLYKEY_SIGNED, API_MODE_ALL_SIGNED
from indico.web.http_api.fossils import IHTTPAPIExportResultFossil
from indico.web.http_api.metadata.serializer import Serializer
from indico.web.flask.util import ResponseUtil
from indico.util.contextManager import ContextManager
from indico.modules.oauth.errors import OAuthError
from indico.modules.oauth.components import OAuthUtils

# indico legacy imports
from indico.core.db import DBMgr
from indico.core.config import Config
from MaKaC.common.logger import Logger
from MaKaC.common.fossilize import fossilize, clearCache
from MaKaC.accessControl import AccessWrapper
from MaKaC.common.info import HelperMaKaCInfo
from MaKaC.common.cache import GenericCache
from MaKaC.plugins.RoomBooking.default.factory import Factory

# Remove the extension at the end or before the querystring
RE_REMOVE_EXTENSION = re.compile(r'\.(\w+)(?:$|(?=\?))')

def normalizeQuery(path, query, remove=('signature',), separate=False):
    """Normalize request path and query so it can be used for caching and signing

    Returns a string consisting of path and sorted query string.
    Dynamic arguments like signature and timestamp are removed from the query string.
    """
    qparams = parse_qs(query)
    sorted_params = []

    for key, values in sorted(qparams.items(), key=lambda x: x[0].lower()):
        key = key.lower()
        if key not in remove:
            for v in sorted(values):
                sorted_params.append((key, v))

    if separate:
        return path, sorted_params and urllib.urlencode(sorted_params)
    elif sorted_params:
        return '%s?%s' % (path, urllib.urlencode(sorted_params))
    else:
        return path

def validateSignature(ak, minfo, signature, timestamp, path, query):
    ttl = HelperMaKaCInfo.getMaKaCInfoInstance().getAPISignatureTTL()
    if not timestamp and not (ak.isPersistentAllowed() and minfo.isAPIPersistentAllowed()):
        raise HTTPAPIError('Signature invalid (no timestamp)', 403)
    elif timestamp and abs(timestamp - int(time.time())) > ttl:
        raise HTTPAPIError('Signature invalid (bad timestamp)', 403)
    digest = hmac.new(ak.getSignKey(), normalizeQuery(path, query), hashlib.sha1).hexdigest()
    if signature != digest:
        raise HTTPAPIError('Signature invalid', 403)

def checkAK(apiKey, signature, timestamp, path, query):
    minfo = HelperMaKaCInfo.getMaKaCInfoInstance()
    apiMode = minfo.getAPIMode()
    if not apiKey:
        if apiMode in (API_MODE_ONLYKEY, API_MODE_ONLYKEY_SIGNED, API_MODE_ALL_SIGNED):
            raise HTTPAPIError('API key is missing', 403)
        return None, True
    akh = APIKeyHolder()
    if not akh.hasKey(apiKey):
        raise HTTPAPIError('Invalid API key', 403)
    ak = akh.getById(apiKey)
    if ak.isBlocked():
        raise HTTPAPIError('API key is blocked', 403)
    # Signature validation
    onlyPublic = False
    if signature:
        validateSignature(ak, minfo, signature, timestamp, path, query)
    elif apiMode in (API_MODE_SIGNED, API_MODE_ALL_SIGNED):
        raise HTTPAPIError('Signature missing', 403)
    elif apiMode == API_MODE_ONLYKEY_SIGNED:
        onlyPublic = True
    return ak, onlyPublic

def buildAW(ak, onlyPublic=False):
    aw = AccessWrapper()
    aw.setIP(str(request.remote_addr))
    if ak and not onlyPublic:
        # If we have an authenticated request, require HTTPS
        minfo = HelperMaKaCInfo.getMaKaCInfoInstance()
        # Dirty hack: Google calendar converts HTTP API requests from https to http
        # Therefore, not working with Indico setup (requiring https for HTTP API authenticated)
        if not request.is_secure and minfo.isAPIHTTPSRequired() and request.user_agent.browser != 'google':
            raise HTTPAPIError('HTTPS is required', 403)
        aw.setUser(ak.getUser())
    return aw

def handler(prefix, path):
    path = posixpath.join('/', prefix, path)
    ContextManager.destroy()
    clearCache()  # init fossil cache
    logger = Logger.get('httpapi')
    if request.method == 'POST':
        # Convert POST data to a query string
        queryParams = dict((key, value.encode('utf-8')) for key, value in request.form.iteritems())
        query = urllib.urlencode(queryParams)
    else:
        # Parse the actual query string
        queryParams = dict((key, value.encode('utf-8')) for key, value in request.args.iteritems())
        query = request.query_string

    dbi = DBMgr.getInstance()
    dbi.startRequest()
    minfo = HelperMaKaCInfo.getMaKaCInfoInstance()
    if minfo.getRoomBookingModuleActive():
        Factory.getDALManager().connect()

    apiKey = get_query_parameter(queryParams, ['ak', 'apikey'], None)
    cookieAuth = get_query_parameter(queryParams, ['ca', 'cookieauth'], 'no') == 'yes'
    signature = get_query_parameter(queryParams, ['signature'])
    timestamp = get_query_parameter(queryParams, ['timestamp'], 0, integer=True)
    noCache = get_query_parameter(queryParams, ['nc', 'nocache'], 'no') == 'yes'
    pretty = get_query_parameter(queryParams, ['p', 'pretty'], 'no') == 'yes'
    onlyPublic = get_query_parameter(queryParams, ['op', 'onlypublic'], 'no') == 'yes'
    onlyAuthed = get_query_parameter(queryParams, ['oa', 'onlyauthed'], 'no') == 'yes'
    oauthToken = 'oauth_token' in queryParams
    # Check if OAuth data is supplied in the Authorization header
    if not oauthToken and request.headers.get('Authorization') is not None:
        oauthToken = 'oauth_token' in request.headers.get('Authorization')

    # Get our handler function and its argument and response type
    hook, dformat = HTTPAPIHook.parseRequest(path, queryParams)
    if hook is None or dformat is None:
        raise NotFound

    # Disable caching if we are not just retrieving data (or the hook requires it)
    if request.method == 'POST' or hook.NO_CACHE:
        noCache = True

    ak = error = result = None
    ts = int(time.time())
    typeMap = {}
    responseUtil = ResponseUtil()
    try:
        used_session = None
        if cookieAuth:
            used_session = session
            if not used_session.user:  # ignore guest sessions
                used_session = None

        if apiKey or oauthToken or not used_session:
            if not oauthToken:
                # Validate the API key (and its signature)
                ak, enforceOnlyPublic = checkAK(apiKey, signature, timestamp, path, query)
                if enforceOnlyPublic:
                    onlyPublic = True
                # Create an access wrapper for the API key's user
                aw = buildAW(ak, onlyPublic)
            else:  # Access Token (OAuth)
                at = OAuthUtils.OAuthCheckAccessResource()
                aw = buildAW(at, onlyPublic)
            # Get rid of API key in cache key if we did not impersonate a user
            if ak and aw.getUser() is None:
                cacheKey = normalizeQuery(path, query,
                                          remove=('_', 'ak', 'apiKey', 'signature', 'timestamp', 'nc', 'nocache',
                                                  'oa', 'onlyauthed'))
            else:
                cacheKey = normalizeQuery(path, query,
                                          remove=('_', 'signature', 'timestamp', 'nc', 'nocache', 'oa', 'onlyauthed'))
                if signature:
                    # in case the request was signed, store the result under a different key
                    cacheKey = 'signed_' + cacheKey
        else:
            # We authenticated using a session cookie.
            if Config.getInstance().getCSRFLevel() >= 2:
                token = request.headers.get('X-CSRF-Token', get_query_parameter(queryParams, ['csrftoken']))
                if used_session.csrf_protected and used_session.csrf_token != token:
                    raise HTTPAPIError('Invalid CSRF token', 403)
            aw = AccessWrapper()
            if not onlyPublic:
                aw.setUser(used_session.user)
            userPrefix = 'user-' + used_session.user.getId() + '_'
            cacheKey = userPrefix + normalizeQuery(path, query,
                                                   remove=('_', 'nc', 'nocache', 'ca', 'cookieauth', 'oa', 'onlyauthed',
                                                           'csrftoken'))

        # Bail out if the user requires authentication but is not authenticated
        if onlyAuthed and not aw.getUser():
            raise HTTPAPIError('Not authenticated', 403)

        addToCache = not hook.NO_CACHE
        cache = GenericCache('HTTPAPI')
        cacheKey = RE_REMOVE_EXTENSION.sub('', cacheKey)
        if not noCache:
            obj = cache.get(cacheKey)
            if obj is not None:
                result, extra, ts, complete, typeMap = obj
                addToCache = False
        if result is None:
            ContextManager.set("currentAW", aw)
            # Perform the actual exporting
            res = hook(aw)
            if isinstance(res, tuple) and len(res) == 4:
                result, extra, complete, typeMap = res
            else:
                result, extra, complete, typeMap = res, {}, True, {}
        if result is not None and addToCache:
            ttl = HelperMaKaCInfo.getMaKaCInfoInstance().getAPICacheTTL()
            cache.set(cacheKey, (result, extra, ts, complete, typeMap), ttl)
    except HTTPAPIError, e:
        error = e
        if e.getCode():
            responseUtil.status = e.getCode()
            if responseUtil.status == 405:
                responseUtil.headers['Allow'] = 'GET' if request.method == 'POST' else 'POST'
    except OAuthError, e:
        error = e
        if e.getCode():
            responseUtil.status = e.getCode()

    if result is None and error is None:
        # TODO: usage page
        raise NotFound
    else:
        if ak and error is None:
            # Commit only if there was an API key and no error
            for _retry in xrange(10):
                dbi.sync()
                if minfo.getRoomBookingModuleActive():
                    Factory.getDALManager().sync()
                normPath, normQuery = normalizeQuery(path, query, remove=('signature', 'timestamp'), separate=True)
                ak.used(request.remote_addr, normPath, normQuery, not onlyPublic)
                try:
                    if minfo.getRoomBookingModuleActive():
                        Factory.getDALManager().disconnect()
                    dbi.endRequest(True)
                except ConflictError:
                    pass  # retry
                else:
                    break
        else:
            # No need to commit stuff if we didn't use an API key
            # (nothing was written)
            if minfo.getRoomBookingModuleActive():
                Factory.getDALManager().rollback()
                Factory.getDALManager().disconnect()
            dbi.endRequest(False)

        # Log successful POST api requests
        if error is None and request.method == 'POST':
            logger.info('API request: %s?%s' % (path, query))

        serializer = Serializer.create(dformat, pretty=pretty, typeMap=typeMap,
                                       **queryParams)
        if error:
            if not serializer.schemaless:
                # if our serializer has a specific schema (HTML, ICAL, etc...)
                # use JSON, since it is universal
                serializer = Serializer.create('json')

            result = fossilize(error)
        else:
            if serializer.encapsulate:
                result = fossilize(HTTPAPIResult(result, path, query, ts, complete, extra), IHTTPAPIExportResultFossil)
                del result['_fossil']

        try:
            data = serializer(result)
            serializer.set_headers(responseUtil)
            return responseUtil.make_response(data)
        except:
            logger.exception('Serialization error in request %s?%s' % (path, query))
            raise
<commit_msg>API Mode with no AK, accept AK<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/>.

"""
HTTP API - Handlers
"""

# python stdlib imports
import hashlib
import hmac
import posixpath
import re
import time
import urllib
from flask import request, session
from urlparse import parse_qs
from ZODB.POSException import ConflictError

# indico imports
from werkzeug.exceptions import NotFound
from indico.web.http_api import HTTPAPIHook
from indico.web.http_api.auth import APIKeyHolder
from indico.web.http_api.responses import HTTPAPIResult, HTTPAPIError
from indico.web.http_api.util import get_query_parameter
from indico.web.http_api import API_MODE_ONLYKEY, API_MODE_SIGNED, API_MODE_ONLYKEY_SIGNED, API_MODE_ALL_SIGNED
from indico.web.http_api.fossils import IHTTPAPIExportResultFossil
from indico.web.http_api.metadata.serializer import Serializer
from indico.web.flask.util import ResponseUtil
from indico.util.contextManager import ContextManager
from indico.modules.oauth.errors import OAuthError
from indico.modules.oauth.components import OAuthUtils

# indico legacy imports
from indico.core.db import DBMgr
from indico.core.config import Config
from MaKaC.common.logger import Logger
from MaKaC.common.fossilize import fossilize, clearCache
from MaKaC.accessControl import AccessWrapper
from MaKaC.common.info import HelperMaKaCInfo
from MaKaC.common.cache import GenericCache
from MaKaC.plugins.RoomBooking.default.factory import Factory

# Remove the extension at the end or before the querystring
RE_REMOVE_EXTENSION = re.compile(r'\.(\w+)(?:$|(?=\?))')

def normalizeQuery(path, query, remove=('signature',), separate=False):
    """Normalize request path and query so it can be used for caching and signing

    Returns a string consisting of path and sorted query string.
    Dynamic arguments like signature and timestamp are removed from the query string.
    """
    qparams = parse_qs(query)
    sorted_params = []

    for key, values in sorted(qparams.items(), key=lambda x: x[0].lower()):
        key = key.lower()
        if key not in remove:
            for v in sorted(values):
                sorted_params.append((key, v))

    if separate:
        return path, sorted_params and urllib.urlencode(sorted_params)
    elif sorted_params:
        return '%s?%s' % (path, urllib.urlencode(sorted_params))
    else:
        return path

def validateSignature(ak, minfo, signature, timestamp, path, query):
    ttl = HelperMaKaCInfo.getMaKaCInfoInstance().getAPISignatureTTL()
    if not timestamp and not (ak.isPersistentAllowed() and minfo.isAPIPersistentAllowed()):
        raise HTTPAPIError('Signature invalid (no timestamp)', 403)
    elif timestamp and abs(timestamp - int(time.time())) > ttl:
        raise HTTPAPIError('Signature invalid (bad timestamp)', 403)
    digest = hmac.new(ak.getSignKey(), normalizeQuery(path, query), hashlib.sha1).hexdigest()
    if signature != digest:
        raise HTTPAPIError('Signature invalid', 403)

def checkAK(apiKey, signature, timestamp, path, query):
    minfo = HelperMaKaCInfo.getMaKaCInfoInstance()
    apiMode = minfo.getAPIMode()
    if not apiKey:
        if apiMode in (API_MODE_ONLYKEY, API_MODE_ONLYKEY_SIGNED, API_MODE_ALL_SIGNED):
            raise HTTPAPIError('API key is missing', 403)
        return None, True
    akh = APIKeyHolder()
    if not akh.hasKey(apiKey):
        raise HTTPAPIError('Invalid API key', 403)
    ak = akh.getById(apiKey)
    if ak.isBlocked():
        raise HTTPAPIError('API key is blocked', 403)
    # Signature validation
    onlyPublic = False
    if signature:
        validateSignature(ak, minfo, signature, timestamp, path, query)
    elif apiMode == API_MODE_ALL_SIGNED:
        raise HTTPAPIError('Signature missing', 403)
    elif apiMode in (API_MODE_SIGNED, API_MODE_ONLYKEY_SIGNED):
        onlyPublic = True
    return ak, onlyPublic

def buildAW(ak, onlyPublic=False):
    aw = AccessWrapper()
    aw.setIP(str(request.remote_addr))
    if ak and not onlyPublic:
        # If we have an authenticated request, require HTTPS
        minfo = HelperMaKaCInfo.getMaKaCInfoInstance()
        # Dirty hack: Google calendar converts HTTP API requests from https to http
        # Therefore, not working with Indico setup (requiring https for HTTP API authenticated)
        if not request.is_secure and minfo.isAPIHTTPSRequired() and request.user_agent.browser != 'google':
            raise HTTPAPIError('HTTPS is required', 403)
        aw.setUser(ak.getUser())
    return aw

def handler(prefix, path):
    path = posixpath.join('/', prefix, path)
    ContextManager.destroy()
    clearCache()  # init fossil cache
    logger = Logger.get('httpapi')
    if request.method == 'POST':
        # Convert POST data to a query string
        queryParams = dict((key, value.encode('utf-8')) for key, value in request.form.iteritems())
        query = urllib.urlencode(queryParams)
    else:
        # Parse the actual query string
        queryParams = dict((key, value.encode('utf-8')) for key, value in request.args.iteritems())
        query = request.query_string

    dbi = DBMgr.getInstance()
    dbi.startRequest()
    minfo = HelperMaKaCInfo.getMaKaCInfoInstance()
    if minfo.getRoomBookingModuleActive():
        Factory.getDALManager().connect()

    apiKey = get_query_parameter(queryParams, ['ak', 'apikey'], None)
    cookieAuth = get_query_parameter(queryParams, ['ca', 'cookieauth'], 'no') == 'yes'
    signature = get_query_parameter(queryParams, ['signature'])
    timestamp = get_query_parameter(queryParams, ['timestamp'], 0, integer=True)
    noCache = get_query_parameter(queryParams, ['nc', 'nocache'], 'no') == 'yes'
    pretty = get_query_parameter(queryParams, ['p', 'pretty'], 'no') == 'yes'
    onlyPublic = get_query_parameter(queryParams, ['op', 'onlypublic'], 'no') == 'yes'
    onlyAuthed = get_query_parameter(queryParams, ['oa', 'onlyauthed'], 'no') == 'yes'
    oauthToken = 'oauth_token' in queryParams
    # Check if OAuth data is supplied in the Authorization header
    if not oauthToken and request.headers.get('Authorization') is not None:
        oauthToken = 'oauth_token' in request.headers.get('Authorization')

    # Get our handler function and its argument and response type
    hook, dformat = HTTPAPIHook.parseRequest(path, queryParams)
    if hook is None or dformat is None:
        raise NotFound

    # Disable caching if we are not just retrieving data (or the hook requires it)
    if request.method == 'POST' or hook.NO_CACHE:
        noCache = True

    ak = error = result = None
    ts = int(time.time())
    typeMap = {}
    responseUtil = ResponseUtil()
    try:
        used_session = None
        if cookieAuth:
            used_session = session
            if not used_session.user:  # ignore guest sessions
                used_session = None

        if apiKey or oauthToken or not used_session:
            if not oauthToken:
                # Validate the API key (and its signature)
                ak, enforceOnlyPublic = checkAK(apiKey, signature, timestamp, path, query)
                if enforceOnlyPublic:
                    onlyPublic = True
                # Create an access wrapper for the API key's user
                aw = buildAW(ak, onlyPublic)
            else:  # Access Token (OAuth)
                at = OAuthUtils.OAuthCheckAccessResource()
                aw = buildAW(at, onlyPublic)
            # Get rid of API key in cache key if we did not impersonate a user
            if ak and aw.getUser() is None:
                cacheKey = normalizeQuery(path, query,
                                          remove=('_', 'ak', 'apiKey', 'signature', 'timestamp', 'nc', 'nocache',
                                                  'oa', 'onlyauthed'))
            else:
                cacheKey = normalizeQuery(path, query,
                                          remove=('_', 'signature', 'timestamp', 'nc', 'nocache', 'oa', 'onlyauthed'))
                if signature:
                    # in case the request was signed, store the result under a different key
                    cacheKey = 'signed_' + cacheKey
        else:
            # We authenticated using a session cookie.
            if Config.getInstance().getCSRFLevel() >= 2:
                token = request.headers.get('X-CSRF-Token', get_query_parameter(queryParams, ['csrftoken']))
                if used_session.csrf_protected and used_session.csrf_token != token:
                    raise HTTPAPIError('Invalid CSRF token', 403)
            aw = AccessWrapper()
            if not onlyPublic:
                aw.setUser(used_session.user)
            userPrefix = 'user-' + used_session.user.getId() + '_'
            cacheKey = userPrefix + normalizeQuery(path, query,
                                                   remove=('_', 'nc', 'nocache', 'ca', 'cookieauth', 'oa', 'onlyauthed',
                                                           'csrftoken'))

        # Bail out if the user requires authentication but is not authenticated
        if onlyAuthed and not aw.getUser():
            raise HTTPAPIError('Not authenticated', 403)

        addToCache = not hook.NO_CACHE
        cache = GenericCache('HTTPAPI')
        cacheKey = RE_REMOVE_EXTENSION.sub('', cacheKey)
        if not noCache:
            obj = cache.get(cacheKey)
            if obj is not None:
                result, extra, ts, complete, typeMap = obj
                addToCache = False
        if result is None:
            ContextManager.set("currentAW", aw)
            # Perform the actual exporting
            res = hook(aw)
            if isinstance(res, tuple) and len(res) == 4:
                result, extra, complete, typeMap = res
            else:
                result, extra, complete, typeMap = res, {}, True, {}
        if result is not None and addToCache:
            ttl = HelperMaKaCInfo.getMaKaCInfoInstance().getAPICacheTTL()
            cache.set(cacheKey, (result, extra, ts, complete, typeMap), ttl)
    except HTTPAPIError, e:
        error = e
        if e.getCode():
            responseUtil.status = e.getCode()
            if responseUtil.status == 405:
                responseUtil.headers['Allow'] = 'GET' if request.method == 'POST' else 'POST'
    except OAuthError, e:
        error = e
        if e.getCode():
            responseUtil.status = e.getCode()

    if result is None and error is None:
        # TODO: usage page
        raise NotFound
    else:
        if ak and error is None:
            # Commit only if there was an API key and no error
            for _retry in xrange(10):
                dbi.sync()
                if minfo.getRoomBookingModuleActive():
                    Factory.getDALManager().sync()
                normPath, normQuery = normalizeQuery(path, query, remove=('signature', 'timestamp'), separate=True)
                ak.used(request.remote_addr, normPath, normQuery, not onlyPublic)
                try:
                    if minfo.getRoomBookingModuleActive():
                        Factory.getDALManager().disconnect()
                    dbi.endRequest(True)
                except ConflictError:
                    pass  # retry
                else:
                    break
        else:
            # No need to commit stuff if we didn't use an API key
            # (nothing was written)
            if minfo.getRoomBookingModuleActive():
                Factory.getDALManager().rollback()
                Factory.getDALManager().disconnect()
            dbi.endRequest(False)

        # Log successful POST api requests
        if error is None and request.method == 'POST':
            logger.info('API request: %s?%s' % (path, query))

        serializer = Serializer.create(dformat, pretty=pretty, typeMap=typeMap,
                                       **queryParams)
        if error:
            if not serializer.schemaless:
                # if our serializer has a specific schema (HTML, ICAL, etc...)
                # use JSON, since it is universal
                serializer = Serializer.create('json')

            result = fossilize(error)
        else:
            if serializer.encapsulate:
                result = fossilize(HTTPAPIResult(result, path, query, ts, complete, extra), IHTTPAPIExportResultFossil)
                del result['_fossil']

        try:
            data = serializer(result)
            serializer.set_headers(responseUtil)
            return responseUtil.make_response(data)
        except:
            logger.exception('Serialization error in request %s?%s' % (path, query))
            raise
<|endoftext|>


<commit_before>#!/usr/bin/env python

# Copyright 2016 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.

from flask import Flask, jsonify, request, Response
from random import choice
from library import get_board

app = Flask(__name__)

def _score(num_player, num_opponent):
    score = 0
    if num_opponent == 0:
        if num_player == 4:
            score += 1000
        else:
            score += num_player
    if num_player == 0:
        if num_opponent >= 4:
            score += 800
        elif num_opponent >= 3:
            score += 500
        else:
            score += num_opponent
    return score

def scoring_model(board_id, player):
    board = get_board(board_id)
    board_size = len(board)
    score = [[0 for y in range(board_size)]
                for x in range(board_size)]
    if player == 1:
        opponent = 2
    else:
        opponent = 1
    for y in range(board_size):
        for x in range(board_size):
            if board[y][x] != 0:
                score[y][x] = -1

            # check to right
            if x + 4 < board_size:
                num_player = sum(board[y][x+dx] == player for dx in range(5))
                num_opponent = sum(board[y][x+dx] == opponent for dx in range(5))
                for dx in range(5):
                    if board[y][x+dx] == 0:
                        score[y][x+dx] += _score(num_player, num_opponent)

            # check to down
            if y + 4 < board_size:
                num_player = sum(board[y+dy][x] == player for dy in range(5))
                num_opponent = sum(board[y+dy][x] == opponent for dy in range(5))
                for dy in range(5):
                    if board[y+dy][x] == 0:
                        score[y+dy][x] += _score(num_player, num_opponent)

            # check to right-down
            if x + 4 < board_size and y + 4 < board_size:
                num_player = sum(board[y+d][x+d] == player for d in range(5))
                num_opponent = sum(board[y+d][x+d] == opponent for d in range(5))
                for d in range(5):
                    if board[y+d][x+d] == 0:
                        score[y+d][x+d] += _score(num_player, num_opponent)

            # check to left-down
            if x - 4 >= 0 and y + 4 < board_size:
                num_player = sum(board[y+d][x-d] == player for d in range(5))
                num_opponent = sum(board[y+d][x-d] == opponent for d in range(5))
                for d in range(5):
                    if board[y+d][x-d] == 0:
                        score[y+d][x-d] += _score(num_player, num_opponent)

    max_score = max(sum(score, []))
    candidates = []
    for y in range(len(board)):
        for x in range(len(board)):
            if score[y][x] == max_score and board[y][x] == 0:
                candidates.append((x,y))
    if len(candidates) == 0:
        return None

    return choice(candidates)

@app.route('/api/v1/brain/<int:id>/<int:player>', methods=['GET'])
def get_move_api(id, player):
    x, y = scoring_model(id, player)
    response = jsonify({'x': x, 'y': y})
    response.status_code = 200
    return response

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8081, debug=True)

<commit_msg>Adjust scoring conditions to be more greedy.<commit_after>#!/usr/bin/env python

# Copyright 2016 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.

from flask import Flask, jsonify, request, Response
from random import choice
from library import get_board

app = Flask(__name__)

def _score(num_player, num_opponent):
    score = 0
    if num_opponent == 0:
        if num_player == 4:
            score += 10**5
        elif num_player == 3:
            score += 10**3
        else:
            score += num_player
    if num_player == 0:
        if num_opponent == 4:
            score += 10**4
        elif num_opponent == 3:
            score += 10**2
        else:
            score += num_opponent
    return score

def scoring_model(board_id, player):
    board = get_board(board_id)
    board_size = len(board)
    score = [[0 for y in range(board_size)]
                for x in range(board_size)]
    if player == 1:
        opponent = 2
    else:
        opponent = 1
    for y in range(board_size):
        for x in range(board_size):
            if board[y][x] != 0:
                score[y][x] = -1

            # check to right
            if x + 4 < board_size:
                num_player = sum(board[y][x+dx] == player for dx in range(5))
                num_opponent = sum(board[y][x+dx] == opponent for dx in range(5))
                for dx in range(5):
                    if board[y][x+dx] == 0:
                        score[y][x+dx] += _score(num_player, num_opponent)

            # check to down
            if y + 4 < board_size:
                num_player = sum(board[y+dy][x] == player for dy in range(5))
                num_opponent = sum(board[y+dy][x] == opponent for dy in range(5))
                for dy in range(5):
                    if board[y+dy][x] == 0:
                        score[y+dy][x] += _score(num_player, num_opponent)

            # check to right-down
            if x + 4 < board_size and y + 4 < board_size:
                num_player = sum(board[y+d][x+d] == player for d in range(5))
                num_opponent = sum(board[y+d][x+d] == opponent for d in range(5))
                for d in range(5):
                    if board[y+d][x+d] == 0:
                        score[y+d][x+d] += _score(num_player, num_opponent)

            # check to left-down
            if x - 4 >= 0 and y + 4 < board_size:
                num_player = sum(board[y+d][x-d] == player for d in range(5))
                num_opponent = sum(board[y+d][x-d] == opponent for d in range(5))
                for d in range(5):
                    if board[y+d][x-d] == 0:
                        score[y+d][x-d] += _score(num_player, num_opponent)

    max_score = max(sum(score, []))
    candidates = []
    for y in range(len(board)):
        for x in range(len(board)):
            if score[y][x] == max_score and board[y][x] == 0:
                candidates.append((x,y))
    if len(candidates) == 0:
        return None

    return choice(candidates)

@app.route('/api/v1/brain/<int:id>/<int:player>', methods=['GET'])
def get_move_api(id, player):
    x, y = scoring_model(id, player)
    response = jsonify({'x': x, 'y': y})
    response.status_code = 200
    return response

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8081, debug=True)

<|endoftext|>


<commit_before>import re
import sys
import string
import os
from sklearn.cluster import KMeans
from sklearn.ensemble import RandomForestClassifier
import numpy as np
from argparse import Namespace

def find_training_genome(trainingFlag, INSTALLATION_DIR):
    try:
        f = open(os.path.join(INSTALLATION_DIR, 'data/trainingGenome_list.txt'), 'r')
    except:
        print('cannot open ' + os.path.join(INSTALLATION_DIR, 'data/trainingGenome_list.txt'))
        return ''

    for line in f:
        temp = re.split('\t',line.strip())
        if int(temp[0]) == trainingFlag:
            f.close()
            return temp[1].strip()
    return ''

def call_randomforest(**kwargs):
    output_dir = kwargs['output_dir']
    trainingFile = kwargs['training_set']
    bin_path = os.path.join(os.path.dirname(os.path.dirname(os.path.relpath(__file__))),'bin')
    infile = os.path.join(output_dir, "testSet.txt")
    outfile = os.path.join(output_dir, "classify.tsv")
    train_data = np.genfromtxt(fname=trainingFile, delimiter="\t", skip_header=1, filling_values=1) # why not fill missing values with 0?
    test_data = np.genfromtxt(fname=infile, delimiter="\t", skip_header=1, filling_values=1)
    # Przemek's comment
    # by default 10 until version 0.22 where default is 100
    # number of estimators also implies the precision of probabilities, generally 1/n_estimators
    # in R's randomForest it's 500 and the usage note regarding number of trees to grow says:
    # "This should not be set to too small a number, to ensure that every input row gets predicted at least a few times."
    clf = RandomForestClassifier(n_estimators = kwargs['randomforest_trees'])
    clf.fit(train_data[:, :-1], train_data[:, -1].astype('int'))
    np.savetxt(outfile, clf.predict_proba(test_data)[:,1])

    # cmd = "Rscript " + bin_path + "/randomForest.r " + trainingFile + " " + infile + " " + outfile
    # os.system(cmd)

def my_sort(orf_list):
     n = len(orf_list)
     i = 1
     while( i <= n ):
          j = i + 1
          while( j < n ):
               flag = 0
               #direction for both
               if( orf_list[i]['start'] < orf_list[i]['stop'] ):
                    dir_i = 1
               else:
                    dir_i = -1
               if( orf_list[j]['start'] < orf_list[j]['stop'] ):
                    dir_j = 1
               else:
                    dir_j = -1

               #check whether swap need or not
               if dir_i == dir_j:
                    if orf_list[i]['start']>orf_list[j]['start']:
                         flag = 1
               else:
                    if dir_i == 1:
                         if orf_list[i]['start']>orf_list[j]['stop']:
                              flag = 1
                    else:
                         if orf_list[i]['stop']>orf_list[j]['start']:
                              flag = 1
               #swap
               if flag == 1:
                    temp = orf_list[i]
                    orf_list[i] = orf_list[j]
                    orf_list[j] = temp
               j = j+1
          i = i+1
     return orf_list

def find_mean(all_len):
     sum = 0.0
     for i in all_len:
          sum = sum + i
     return float(sum)/len(all_len)

def calc_pp(func):
    x = 0
    func = func.replace('-',' ')
    func = func.replace(',',' ')
    a = re.split(' ',func)
    if (
       'phage'         in a or
       'lysin'         in a or
       'endolysin'     in a or
       'holin'         in a or
       'capsid'        in a or
       'tail'          in a or
       'bacteriophage' in a or
       'prophage'      in a or
       'portal'        in a or
       'terminase'     in a or
       'tapemeasure'   in a or
       'baseplate'     in a or
       'virion'        in a or
       'antirepressor' in a or
       'excisionase'   in a or
       'mobile element protein' == func or
       re.search(r"\b%s\b" % "tape measure", func) or
       re.search(r"\b%s\b" % "Cro-like repressor", func) or
       re.search(r"\b%s\b" % "CI-like repressor", func) or
       re.search(r"\b%s\b" % "rIIA lysis", func) or
       re.search(r"\b%s\b" % "rI lysis", func) or
       re.search(r"\b%s\b" % "rIIB lysis", func) or
       re.search(r"\b%s\b" % "base plate", func)
       ):
           x = 1
    elif ('unknown' in func) or ('hypothetical' in func):
           x = 0.5
    else:
           x = 0
    if 'recombinase' in func or 'integrase' in func:
           x = 1.5
    if ('phage' in func) and ('shock' in func):
           x = 0
    if "dna binding domain" in func:
           x = 0
    return x

def calc_function_3files(organism):
    my_func = {}
    x = 0 #no need it for computation.. just a flag in "except"
    try:
        f_fun = open(organism+'/proposed_non_ff_functions','r')
        for line in f_fun:
            temp = re.split('\t',line.strip())
            if len(temp)>=2:
                my_func[temp[0]] = temp[1]
        f_fun.close()
    except:
        x = x + 1
    try:
        f_fun = open(organism+'/proposed_functions','r')
        for line in f_fun:
            temp = re.split('\t',line.strip())
            if len(temp)>=2:
                my_func[temp[0]] = temp[1]
        f_fun.close()
    except:
        x = x + 1
    try:
        f_fun = open(organism+'/assigned_functions','r')
        for line in f_fun:
            temp = re.split('\t',line.strip())
            if len(temp)>=2:
                my_func[temp[0]] = temp[1]
        f_fun.close()
    except:
        x = x + 1

    return my_func

def input_bactpp(**kwargs):
    print(kwargs)
    exit()

    #bact_file = organism+'/Features/peg/tbl'
    #try:
    #    fh = open(bact_file,'r')
    #except:
    #    print('cant open file- assigned functions/tbl file:',organism)
    #    return {}
    #my_func = calc_function_3files(organism)
    all_orf_list = {}
    for i in fh:
        temp = re.split('\t',i.strip())
        temp1 = re.split('_',temp[1])
        if ',' in temp[1]:
            ttemp = re.split(',',temp[1])
            temp[1] = ttemp[len(ttemp)-1]
        temp1 = re.split('_',temp[1])
        contig = temp[1][:temp[1][:temp[1].rfind('_')].rfind('_')]
        start = int(temp1[len(temp1)-2])
        stop = int(temp1[len(temp1)-1])
        #save info for sorting orf
        if contig in all_orf_list:
            x = len(all_orf_list[contig]) + 1
        else:
            x = 1
            all_orf_list[contig]={}
        all_orf_list[contig][x]={}
        all_orf_list[contig][x]['fig'] = temp[0]
        all_orf_list[contig][x]['contig'] = str(contig)
        all_orf_list[contig][x]['start'] = start
        all_orf_list[contig][x]['stop'] = stop
        if temp[0] in my_func:
            all_orf_list[contig][x]['function'] = my_func[temp[0]]
            all_orf_list[contig][x]['pp'] = calc_pp(my_func[temp[0]].lower(),INSTALLATION_DIR)
        else:
            all_orf_list[contig][x]['function'] = "-"
            all_orf_list[contig][x]['pp'] = 0.5
    fh.close()
    all = {}
    index = 1
    for mycontig in all_orf_list:
        orf_list = my_sort(all_orf_list[mycontig])
        i = 1
        while i <= len(orf_list):
            all[index] = {}
            all[index]['fig'] = orf_list[i]['fig']
            all[index]['function'] = orf_list[i]['function']
            all[index]['contig'] = orf_list[i]['contig']
            all[index]['start'] = orf_list[i]['start']
            all[index]['stop'] = orf_list[i]['stop']
            all[index]['rank'] = 0.0
            all[index]['status'] = 0
            all[index]['pp'] = orf_list[i]['pp']
            i = i+1
            index = index+1
    return all

def make_initial_tbl(**kwargs): #organismPath, output_dir, window, INSTALLATION_DIR):
    self = Namespace(**kwargs)
    x = []
    for entry in self.record:
        for feature in entry.get_features('CDS'):
            all = {}
            all['fig'] = feature.id
            all['function'] = feature.function
            all['contig'] = entry.id
            all['start'] = feature.start
            all['stop'] = feature.stop
            all['rank'] = 0.0
            all['status'] = 0
            all['pp'] = calc_pp(feature.function)
            x.append(all)
    try:
        infile = open(os.path.join(self.output_dir, 'classify.tsv'), 'r')
        outfile = open(os.path.join(self.output_dir, 'initial_tbl.tsv'), 'w')
    except:
        sys.exit('ERROR: Cannot open classify.tsv in make_initial_tbl')
    #x = input_bactpp(**kwargs)
    j = 1
    ranks = [[] for n in range(len(x))]
    for line in infile:
        val = float(line.strip())
        for k in range(j-int(self.window_size/2), j+int(self.window_size/2)):
            if k <= 0 or k >= len(x) or j >= len(x) or x[k]['contig'] != x[j]['contig']:
                continue
            ranks[k].append(val)
        j += 1
    infile.close()
    #calculate threshold
    y = []
    j = 1
    while j < len(x):
        x[j]['rank'] = sum(ranks[j]) / len(ranks[j])
        x[j]['extra'] =  ranks[j]
        y.append(x[j]['rank'])
        #y.append([x[j]['rank']])
        j = j+1
    #threshold = max(y)/2
    y2 = np.array(y).reshape(-1, 1)
    km = KMeans(n_clusters = 2)
    km.fit(y2)
    centers = km.cluster_centers_
    threshold = max(centers[0][0], centers[1][0])
    """
    Note added by Rob:
    At this point we have the classifications for each ORF and we want to take a sliding window and decide where the phage should
    start. We have two calculations for a threshold for the rank: either the kmeans centers and finding things above the larger center
    or just a plain threshold.

    """
    j = 1
    outfile.write('fig_no\tfunction\tcontig\tstart\tstop\tposition\trank\tmy_status\tpp\tFinal_status\tstart of attL\tend of attL\tstart of attR\tend of attR\tsequence of attL\tsequence of attR\tReason for att site\n')
    while j < len(x):
        if x[j]['rank'] > threshold:
            x[j]['status'] = 1
        outfile.write(str(x[j]['fig']))
        outfile.write('\t')
        outfile.write(str(x[j]['function']))
        outfile.write('\t')
        outfile.write(str(x[j]['contig']))
        outfile.write('\t')
        outfile.write(str(x[j]['start']))
        outfile.write('\t')
        outfile.write(str(x[j]['stop']))
        outfile.write('\t')
        outfile.write(str(j))
        outfile.write('\t')
        outfile.write(str(x[j]['rank']))
        outfile.write('\t')
        outfile.write(str(x[j]['status']))
        outfile.write('\t')
        outfile.write(str(x[j]['pp']))
        outfile.write('\n')
        j = j+1
    outfile.close()
<commit_msg>converting to pkg_resource stream<commit_after>import re
import sys
import string
import os
from sklearn.cluster import KMeans
from sklearn.ensemble import RandomForestClassifier
import numpy as np
from argparse import Namespace

def find_training_genome(trainingFlag, INSTALLATION_DIR):
    try:
        # f = open(os.path.join(INSTALLATION_DIR, 'data/trainingGenome_list.txt'), 'r')
        f = pkg_resources.resource_stream('PhiSpyModules', 'data/trainingGenome_list.txt')
    except:
        print('cannot open resource stream training genomes at data/trainingGenome_list.txt')
        return ''

    for line in f:
        temp = re.split('\t',line.decode().strip())
        if int(temp[0]) == trainingFlag:
            f.close()
            return temp[1].strip()
    return ''

def call_randomforest(**kwargs):
    output_dir = kwargs['output_dir']
    trainingFile = kwargs['training_set']
    bin_path = os.path.join(os.path.dirname(os.path.dirname(os.path.relpath(__file__))),'bin')
    infile = os.path.join(output_dir, "testSet.txt")
    outfile = os.path.join(output_dir, "classify.tsv")
    #train_data = np.genfromtxt(fname=trainingFile, delimiter="\t", skip_header=1, filling_values=1) # why not fill missing values with 0?
    # convert this to run from pip
    if not pkg_resources.resource_exists('PhiSpyModules', trainingFile):
        sys.stderr.write("FATAL: Can not find data file {}\n".format(trainingFile))
        sys.exit(-1)
    strm = pkg_resources.resource_stream('PhiSpyModules', trainingFile)
    train_data = np.genfromtxt(TextIOWrapper(strm), delimiter="\t", skip_header=1, filling_values=1)
    test_data = np.genfromtxt(fname=infile, delimiter="\t", skip_header=1, filling_values=1)
    # Przemek's comment
    # by default 10 until version 0.22 where default is 100
    # number of estimators also implies the precision of probabilities, generally 1/n_estimators
    # in R's randomForest it's 500 and the usage note regarding number of trees to grow says:
    # "This should not be set to too small a number, to ensure that every input row gets predicted at least a few times."
    clf = RandomForestClassifier(n_estimators = kwargs['randomforest_trees'])
    clf.fit(train_data[:, :-1], train_data[:, -1].astype('int'))
    np.savetxt(outfile, clf.predict_proba(test_data)[:,1])

    # cmd = "Rscript " + bin_path + "/randomForest.r " + trainingFile + " " + infile + " " + outfile
    # os.system(cmd)

def my_sort(orf_list):
     n = len(orf_list)
     i = 1
     while( i <= n ):
          j = i + 1
          while( j < n ):
               flag = 0
               #direction for both
               if( orf_list[i]['start'] < orf_list[i]['stop'] ):
                    dir_i = 1
               else:
                    dir_i = -1
               if( orf_list[j]['start'] < orf_list[j]['stop'] ):
                    dir_j = 1
               else:
                    dir_j = -1

               #check whether swap need or not
               if dir_i == dir_j:
                    if orf_list[i]['start']>orf_list[j]['start']:
                         flag = 1
               else:
                    if dir_i == 1:
                         if orf_list[i]['start']>orf_list[j]['stop']:
                              flag = 1
                    else:
                         if orf_list[i]['stop']>orf_list[j]['start']:
                              flag = 1
               #swap
               if flag == 1:
                    temp = orf_list[i]
                    orf_list[i] = orf_list[j]
                    orf_list[j] = temp
               j = j+1
          i = i+1
     return orf_list

def find_mean(all_len):
     sum = 0.0
     for i in all_len:
          sum = sum + i
     return float(sum)/len(all_len)

def calc_pp(func):
    x = 0
    func = func.replace('-',' ')
    func = func.replace(',',' ')
    a = re.split(' ',func)
    if (
       'phage'         in a or
       'lysin'         in a or
       'endolysin'     in a or
       'holin'         in a or
       'capsid'        in a or
       'tail'          in a or
       'bacteriophage' in a or
       'prophage'      in a or
       'portal'        in a or
       'terminase'     in a or
       'tapemeasure'   in a or
       'baseplate'     in a or
       'virion'        in a or
       'antirepressor' in a or
       'excisionase'   in a or
       'mobile element protein' == func or
       re.search(r"\b%s\b" % "tape measure", func) or
       re.search(r"\b%s\b" % "Cro-like repressor", func) or
       re.search(r"\b%s\b" % "CI-like repressor", func) or
       re.search(r"\b%s\b" % "rIIA lysis", func) or
       re.search(r"\b%s\b" % "rI lysis", func) or
       re.search(r"\b%s\b" % "rIIB lysis", func) or
       re.search(r"\b%s\b" % "base plate", func)
       ):
           x = 1
    elif ('unknown' in func) or ('hypothetical' in func):
           x = 0.5
    else:
           x = 0
    if 'recombinase' in func or 'integrase' in func:
           x = 1.5
    if ('phage' in func) and ('shock' in func):
           x = 0
    if "dna binding domain" in func:
           x = 0
    return x

def calc_function_3files(organism):
    my_func = {}
    x = 0 #no need it for computation.. just a flag in "except"
    try:
        f_fun = open(organism+'/proposed_non_ff_functions','r')
        for line in f_fun:
            temp = re.split('\t',line.strip())
            if len(temp)>=2:
                my_func[temp[0]] = temp[1]
        f_fun.close()
    except:
        x = x + 1
    try:
        f_fun = open(organism+'/proposed_functions','r')
        for line in f_fun:
            temp = re.split('\t',line.strip())
            if len(temp)>=2:
                my_func[temp[0]] = temp[1]
        f_fun.close()
    except:
        x = x + 1
    try:
        f_fun = open(organism+'/assigned_functions','r')
        for line in f_fun:
            temp = re.split('\t',line.strip())
            if len(temp)>=2:
                my_func[temp[0]] = temp[1]
        f_fun.close()
    except:
        x = x + 1

    return my_func

def input_bactpp(**kwargs):
    print(kwargs)
    exit()

    #bact_file = organism+'/Features/peg/tbl'
    #try:
    #    fh = open(bact_file,'r')
    #except:
    #    print('cant open file- assigned functions/tbl file:',organism)
    #    return {}
    #my_func = calc_function_3files(organism)
    all_orf_list = {}
    for i in fh:
        temp = re.split('\t',i.strip())
        temp1 = re.split('_',temp[1])
        if ',' in temp[1]:
            ttemp = re.split(',',temp[1])
            temp[1] = ttemp[len(ttemp)-1]
        temp1 = re.split('_',temp[1])
        contig = temp[1][:temp[1][:temp[1].rfind('_')].rfind('_')]
        start = int(temp1[len(temp1)-2])
        stop = int(temp1[len(temp1)-1])
        #save info for sorting orf
        if contig in all_orf_list:
            x = len(all_orf_list[contig]) + 1
        else:
            x = 1
            all_orf_list[contig]={}
        all_orf_list[contig][x]={}
        all_orf_list[contig][x]['fig'] = temp[0]
        all_orf_list[contig][x]['contig'] = str(contig)
        all_orf_list[contig][x]['start'] = start
        all_orf_list[contig][x]['stop'] = stop
        if temp[0] in my_func:
            all_orf_list[contig][x]['function'] = my_func[temp[0]]
            all_orf_list[contig][x]['pp'] = calc_pp(my_func[temp[0]].lower(),INSTALLATION_DIR)
        else:
            all_orf_list[contig][x]['function'] = "-"
            all_orf_list[contig][x]['pp'] = 0.5
    fh.close()
    all = {}
    index = 1
    for mycontig in all_orf_list:
        orf_list = my_sort(all_orf_list[mycontig])
        i = 1
        while i <= len(orf_list):
            all[index] = {}
            all[index]['fig'] = orf_list[i]['fig']
            all[index]['function'] = orf_list[i]['function']
            all[index]['contig'] = orf_list[i]['contig']
            all[index]['start'] = orf_list[i]['start']
            all[index]['stop'] = orf_list[i]['stop']
            all[index]['rank'] = 0.0
            all[index]['status'] = 0
            all[index]['pp'] = orf_list[i]['pp']
            i = i+1
            index = index+1
    return all

def make_initial_tbl(**kwargs): #organismPath, output_dir, window, INSTALLATION_DIR):
    self = Namespace(**kwargs)
    x = []
    for entry in self.record:
        for feature in entry.get_features('CDS'):
            all = {}
            all['fig'] = feature.id
            all['function'] = feature.function
            all['contig'] = entry.id
            all['start'] = feature.start
            all['stop'] = feature.stop
            all['rank'] = 0.0
            all['status'] = 0
            all['pp'] = calc_pp(feature.function)
            x.append(all)
    try:
        infile = open(os.path.join(self.output_dir, 'classify.tsv'), 'r')
        outfile = open(os.path.join(self.output_dir, 'initial_tbl.tsv'), 'w')
    except:
        sys.exit('ERROR: Cannot open classify.tsv in make_initial_tbl')
    #x = input_bactpp(**kwargs)
    j = 1
    ranks = [[] for n in range(len(x))]
    for line in infile:
        val = float(line.strip())
        for k in range(j-int(self.window_size/2), j+int(self.window_size/2)):
            if k <= 0 or k >= len(x) or j >= len(x) or x[k]['contig'] != x[j]['contig']:
                continue
            ranks[k].append(val)
        j += 1
    infile.close()
    #calculate threshold
    y = []
    j = 1
    while j < len(x):
        x[j]['rank'] = sum(ranks[j]) / len(ranks[j])
        x[j]['extra'] =  ranks[j]
        y.append(x[j]['rank'])
        #y.append([x[j]['rank']])
        j = j+1
    #threshold = max(y)/2
    y2 = np.array(y).reshape(-1, 1)
    km = KMeans(n_clusters = 2)
    km.fit(y2)
    centers = km.cluster_centers_
    threshold = max(centers[0][0], centers[1][0])
    """
    Note added by Rob:
    At this point we have the classifications for each ORF and we want to take a sliding window and decide where the phage should
    start. We have two calculations for a threshold for the rank: either the kmeans centers and finding things above the larger center
    or just a plain threshold.

    """
    j = 1
    outfile.write('fig_no\tfunction\tcontig\tstart\tstop\tposition\trank\tmy_status\tpp\tFinal_status\tstart of attL\tend of attL\tstart of attR\tend of attR\tsequence of attL\tsequence of attR\tReason for att site\n')
    while j < len(x):
        if x[j]['rank'] > threshold:
            x[j]['status'] = 1
        outfile.write(str(x[j]['fig']))
        outfile.write('\t')
        outfile.write(str(x[j]['function']))
        outfile.write('\t')
        outfile.write(str(x[j]['contig']))
        outfile.write('\t')
        outfile.write(str(x[j]['start']))
        outfile.write('\t')
        outfile.write(str(x[j]['stop']))
        outfile.write('\t')
        outfile.write(str(j))
        outfile.write('\t')
        outfile.write(str(x[j]['rank']))
        outfile.write('\t')
        outfile.write(str(x[j]['status']))
        outfile.write('\t')
        outfile.write(str(x[j]['pp']))
        outfile.write('\n')
        j = j+1
    outfile.close()
<|endoftext|>


<commit_before>#!/usr/bin/env python
"""
   [PlantUML][] Extension for [Python-Markdown][]
   ==============================================

   This plugin implements a block extension which can be used to specify a [PlantUML][] diagram which will be
   converted into an image and inserted in the document.

   Syntax:

      ::uml:: [format="png|svg"] [classes="class1 class2 ..."] [alt="text for alt"]
          PlantUML script diagram
      ::end-uml::

   Example:

      ::uml:: format="png" classes="uml myDiagram" alt="My super diagram"
          Goofy ->  MickeyMouse: calls
          Goofy <-- MickeyMouse: responds
      ::end-uml::

   Options are optional, but if present must be specified in the order format, classes, alt.
   The option value may be enclosed in single or double quotes.

   Installation
   ------------
   You need to install [PlantUML][] (see the site for details) and [Graphviz][] 2.26.3 or later.
   The plugin expects a program `plantuml` in the classpath. If not installed by your package
   manager, you can create a shell script and place it somewhere in the classpath. For example,
   save te following into `/usr/local/bin/plantuml` (supposing [PlantUML][] installed into
   `/opt/plantuml`):

       #!/bin/bash
       java -jar /opt/plantuml/plantuml.jar ${@}

   For [Gentoo Linux][Gentoo] there is an ebuild at http://gpo.zugaina.org/dev-util/plantuml/RDep: you can download
   the ebuild and the `files` subfolder or you can add the `zugaina` repository with [layman][]
   (reccomended).

   [Python-Markdown]: http://pythonhosted.org/Markdown/
   [PlantUML]: http://plantuml.sourceforge.net/
   [Graphviz]: http://www.graphviz.org
   [Gentoo]: http://www.gentoo.org
   [layman]: http://wiki.gentoo.org/wiki/Layman
"""

import os
import re
import base64
import tempfile
from subprocess import Popen, PIPE
from zlib import adler32
import logging
import markdown
from markdown.util import etree, AtomicString

#logger = logging.getLogger('MARKDOWN')
#logger.setLevel(logging.DEBUG)

# For details see https://pythonhosted.org/Markdown/extensions/api.html#blockparser
class PlantUMLBlockProcessor(markdown.blockprocessors.BlockProcessor):
    # Regular expression inspired by the codehilite Markdown plugin
    RE = re.compile(r'''\s*(?P<delimiter>(::uml::)|(```(plant)?uml))
                        \s*(format=(?P<quot>"|')(?P<format>\w+)(?P=quot))?
                        \s*(classes=(?P<quot1>"|')(?P<classes>[\w\s]+)(?P=quot1))?
                        \s*(alt=(?P<quot2>"|')(?P<alt>[\w\s"']+)(?P=quot2))?
                        \s*(title=(?P<quot3>"|')(?P<title>[\w\s"']+)(?P=quot3))?
                    ''', re.VERBOSE+re.UNICODE)
    # Regular expression for identify end of UML script
    RE_END1 = re.compile(r'.*::end-uml::')
    RE_END2 = re.compile(r'.*```$')

    def test(self, parent, block):
        return self.RE.search(block)

    def run(self, parent, blocks):
        block = blocks.pop(0)
        text = block

        # Parse configuration params
        m = self.RE.search(block)
        delimiter = m.group('delimiter')
        imgformat = m.group('format') if m.group('format') else self.config['format']
        classes = m.group('classes') if m.group('classes') else self.config['classes']
        alt = m.group('alt') if m.group('alt') else self.config['alt']
        title = m.group('title') if m.group('title') else self.config['title']

        # Read blocks until end marker found
        end_re = self.RE_END1 if delimiter == '::uml::' else self.RE_END2

        while blocks and not end_re.search(block):
            block = blocks.pop(0)
            text += '\n' + block
        else:
            if not blocks:
                raise RuntimeError("UML block not closed")

        # Remove block header and footer
        text = re.sub(self.RE, "", re.sub(end_re, "", text))
        text = "\n".join(text.split('\n'))
        diagram = self.generate_uml_image(text, imgformat)

        p = etree.SubElement(parent, 'p')
        if imgformat == 'png':
            data = 'data:image/png;base64,{0}'.format(
                base64.b64encode(diagram).decode('ascii')
            )
            img = etree.SubElement(p, 'img')
            img.attrib['src'    ] = data
            img.attrib['classes'] = classes
            img.attrib['alt'    ] = alt
            img.attrib['title'  ] = title
        elif imgformat == 'svg':
            # Firefox handles only base64 encoded SVGs
            data = 'data:image/svg+xml;base64,{0}'.format(
                base64.b64encode(diagram).decode('ascii')
            )
            img = etree.SubElement(p, 'img')
            img.attrib['src'    ] = data
            img.attrib['classes'] = classes
            img.attrib['alt'    ] = alt
            img.attrib['title'  ] = title
        elif imgformat == 'txt':
            #logger.debug(diagram)
            pre = etree.SubElement(parent, 'pre')
            code = etree.SubElement(pre, 'code')
            code.attrib['class'] = 'text'
            code.text = AtomicString(diagram)

    @staticmethod
    def generate_uml_image(plantuml_code, imgformat):
        if imgformat == 'png':
            outopt = "-tpng"
        elif imgformat == 'svg':
            outopt = "-tsvg"
        elif imgformat == 'txt':
            outopt = "-ttxt"
        else:
            logger.error("Bad uml image format '"+imgformat+"', using png")
            outopt = "-tpng"

        plantuml_code = plantuml_code.encode('utf8')

        cmdline = ['plantuml', '-p', outopt ]

        try:
            p = Popen(cmdline, stdin=PIPE, stdout=PIPE, stderr=PIPE)
            out, err = p.communicate(input=plantuml_code)
        except Exception as exc:
            raise Exception('Failed to run plantuml: %s' % exc)
        else:
            if p.returncode == 0:
                return out
            else:
                raise RuntimeError('Error in "uml" directive: %s' % err)

# For details see https://pythonhosted.org/Markdown/extensions/api.html#extendmarkdown
class PlantUMLMarkdownExtension(markdown.Extension):
    # For details see https://pythonhosted.org/Markdown/extensions/api.html#configsettings
    def __init__(self, *args, **kwargs):
        self.config = {
            'classes': ["uml", "Space separated list of classes for the generated image. Defaults to 'uml'."],
            'alt': ["uml diagram", "Text to show when image is not available. Defaults to 'uml diagram'"],
            'format': ["png", "Format of image to generate (png, svg or txt). Defaults to 'png'."],
            'title': ["", "Tooltip for the diagram"]
        }

        super(PlantUMLMarkdownExtension, self).__init__(*args, **kwargs)

    def extendMarkdown(self, md, md_globals):
        blockprocessor = PlantUMLBlockProcessor(md.parser)
        blockprocessor.config = self.getConfigs()
        md.parser.blockprocessors.add('plantuml', blockprocessor, '>code')

def makeExtension(*args, **kwargs):
    return PlantUMLMarkdownExtension(*args, **kwargs)
<commit_msg>Fixed one-block diagrams parsing (fixes #9, #10)<commit_after>#!/usr/bin/env python
"""
   [PlantUML][] Extension for [Python-Markdown][]
   ==============================================

   This plugin implements a block extension which can be used to specify a [PlantUML][] diagram which will be
   converted into an image and inserted in the document.

   Syntax:

      ::uml:: [format="png|svg"] [classes="class1 class2 ..."] [alt="text for alt"]
          PlantUML script diagram
      ::end-uml::

   Example:

      ::uml:: format="png" classes="uml myDiagram" alt="My super diagram"
          Goofy ->  MickeyMouse: calls
          Goofy <-- MickeyMouse: responds
      ::end-uml::

   Options are optional, but if present must be specified in the order format, classes, alt.
   The option value may be enclosed in single or double quotes.

   Installation
   ------------
   You need to install [PlantUML][] (see the site for details) and [Graphviz][] 2.26.3 or later.
   The plugin expects a program `plantuml` in the classpath. If not installed by your package
   manager, you can create a shell script and place it somewhere in the classpath. For example,
   save te following into `/usr/local/bin/plantuml` (supposing [PlantUML][] installed into
   `/opt/plantuml`):

       #!/bin/bash
       java -jar /opt/plantuml/plantuml.jar ${@}

   For [Gentoo Linux][Gentoo] there is an ebuild at http://gpo.zugaina.org/dev-util/plantuml/RDep: you can download
   the ebuild and the `files` subfolder or you can add the `zugaina` repository with [layman][]
   (reccomended).

   [Python-Markdown]: http://pythonhosted.org/Markdown/
   [PlantUML]: http://plantuml.sourceforge.net/
   [Graphviz]: http://www.graphviz.org
   [Gentoo]: http://www.gentoo.org
   [layman]: http://wiki.gentoo.org/wiki/Layman
"""

import re
import base64
from subprocess import Popen, PIPE
import logging
import markdown
from markdown.util import etree, AtomicString

#logger = logging.getLogger('MARKDOWN')
#logger.setLevel(logging.DEBUG)

# For details see https://pythonhosted.org/Markdown/extensions/api.html#blockparser
class PlantUMLBlockProcessor(markdown.blockprocessors.BlockProcessor):
    # Regular expression inspired by the codehilite Markdown plugin
    RE = re.compile(r'''\s*(?P<delimiter>(::uml::)|(```(plant)?uml))
                        \s*(format=(?P<quot>"|')(?P<format>\w+)(?P=quot))?
                        \s*(classes=(?P<quot1>"|')(?P<classes>[\w\s]+)(?P=quot1))?
                        \s*(alt=(?P<quot2>"|')(?P<alt>[\w\s"']+)(?P=quot2))?
                        \s*(title=(?P<quot3>"|')(?P<title>[\w\s"']+)(?P=quot3))?
                    ''', re.VERBOSE+re.UNICODE)
    # Regular expression for identify end of UML script
    RE_END1 = re.compile(r'.*::end-uml::')
    RE_END2 = re.compile(r'.*```$')

    def test(self, parent, block):
        return self.RE.search(block)

    def run(self, parent, blocks):
        block = blocks.pop(0)
        text = block

        # Parse configuration params
        m = self.RE.search(block)
        delimiter = m.group('delimiter')
        imgformat = m.group('format') if m.group('format') else self.config['format']
        classes = m.group('classes') if m.group('classes') else self.config['classes']
        alt = m.group('alt') if m.group('alt') else self.config['alt']
        title = m.group('title') if m.group('title') else self.config['title']

        # Read blocks until end marker found
        end_re = self.RE_END1 if delimiter == '::uml::' else self.RE_END2

        while blocks and not end_re.search(block):
            block = blocks.pop(0)
            text += '\n' + block
        else:
            if not blocks and not end_re.search(block):
                raise RuntimeError("UML block not closed")

        # Remove block header and footer
        text = re.sub(self.RE, "", re.sub(end_re, "", text))
        text = "\n".join(text.split('\n'))
        diagram = self.generate_uml_image(text, imgformat)

        p = etree.SubElement(parent, 'p')
        if imgformat == 'png':
            data = 'data:image/png;base64,{0}'.format(
                base64.b64encode(diagram).decode('ascii')
            )
            img = etree.SubElement(p, 'img')
            img.attrib['src'    ] = data
            img.attrib['classes'] = classes
            img.attrib['alt'    ] = alt
            img.attrib['title'  ] = title
        elif imgformat == 'svg':
            # Firefox handles only base64 encoded SVGs
            data = 'data:image/svg+xml;base64,{0}'.format(
                base64.b64encode(diagram).decode('ascii')
            )
            img = etree.SubElement(p, 'img')
            img.attrib['src'    ] = data
            img.attrib['classes'] = classes
            img.attrib['alt'    ] = alt
            img.attrib['title'  ] = title
        elif imgformat == 'txt':
            #logger.debug(diagram)
            pre = etree.SubElement(parent, 'pre')
            code = etree.SubElement(pre, 'code')
            code.attrib['class'] = 'text'
            code.text = AtomicString(diagram)

    @staticmethod
    def generate_uml_image(plantuml_code, imgformat):
        if imgformat == 'png':
            outopt = "-tpng"
        elif imgformat == 'svg':
            outopt = "-tsvg"
        elif imgformat == 'txt':
            outopt = "-ttxt"
        else:
            logger.error("Bad uml image format '"+imgformat+"', using png")
            outopt = "-tpng"

        plantuml_code = plantuml_code.encode('utf8')

        cmdline = ['plantuml', '-p', outopt ]

        try:
            p = Popen(cmdline, stdin=PIPE, stdout=PIPE, stderr=PIPE)
            out, err = p.communicate(input=plantuml_code)
        except Exception as exc:
            raise Exception('Failed to run plantuml: %s' % exc)
        else:
            if p.returncode == 0:
                return out
            else:
                raise RuntimeError('Error in "uml" directive: %s' % err)

# For details see https://pythonhosted.org/Markdown/extensions/api.html#extendmarkdown
class PlantUMLMarkdownExtension(markdown.Extension):
    # For details see https://pythonhosted.org/Markdown/extensions/api.html#configsettings
    def __init__(self, *args, **kwargs):
        self.config = {
            'classes': ["uml", "Space separated list of classes for the generated image. Defaults to 'uml'."],
            'alt': ["uml diagram", "Text to show when image is not available. Defaults to 'uml diagram'"],
            'format': ["png", "Format of image to generate (png, svg or txt). Defaults to 'png'."],
            'title': ["", "Tooltip for the diagram"]
        }

        super(PlantUMLMarkdownExtension, self).__init__(*args, **kwargs)

    def extendMarkdown(self, md, md_globals):
        blockprocessor = PlantUMLBlockProcessor(md.parser)
        blockprocessor.config = self.getConfigs()
        md.parser.blockprocessors.add('plantuml', blockprocessor, '>code')

def makeExtension(*args, **kwargs):
    return PlantUMLMarkdownExtension(*args, **kwargs)
<|endoftext|>


<commit_before>import json
import os.path
import string
from random import randint

SONG_MODEL = dict(
    title = str(),
    play_url = str(),
    source_url = str(),
    duration = int(),
    extractor = str(),
    extra_info = dict()
)

def _random_id():
    bound = len(string.ascii_lowercase)
    return ''.join(
            string.ascii_lowercase[randint(0, bound-1)] for i in range(bound))

class Playlist(object):

    def __init__(self, store=':memory:'):
        self.store = store
        self.playlist = list()

        if self.store != ':memory:':
            if os.path.exists(store):
                with open(store, 'r') as fh:
                    self.playlist = json.loads(fh.read())

    def _update_store(self):
        if self.store != ':memory:':
            with open(self.store, 'w') as fh:
                fh.write(json.dumps(self.playlist))

    def add_song(self, song):
        """Adds a song to the playlist.

        Returns the song's new ID.
        """
        song['id'] = _random_id()
        self.playlist.append(song)
        self._update_store()

        return song['id']

    def del_song(self, song_id):
        for idx, song in enumerate(self.playlist):
            if song_id == self.playlist['id']:
                del self.playlist[idx]
                return

    def move_song(self, idx1, idx2):
        song = self.playlist[idx1]
        del self.playlist[idx1]
        self.playlist.insert(idx2, song)

    def get_playlist(self):
        return self.playlist
<commit_msg>set length of song id to 10<commit_after>import json
import os.path
import string
from random import randint

SONG_MODEL = dict(
    title = str(),
    play_url = str(),
    source_url = str(),
    duration = int(),
    extractor = str(),
    extra_info = dict()
)

def _random_id():
    bound = len(string.ascii_lowercase)
    return ''.join(
            string.ascii_lowercase[randint(0, bound-1)] for i in range(10))

class Playlist(object):

    def __init__(self, store=':memory:'):
        self.store = store
        self.playlist = list()

        if self.store != ':memory:':
            if os.path.exists(store):
                with open(store, 'r') as fh:
                    self.playlist = json.loads(fh.read())

    def _update_store(self):
        if self.store != ':memory:':
            with open(self.store, 'w') as fh:
                fh.write(json.dumps(self.playlist))

    def add_song(self, song):
        """Adds a song to the playlist.

        Returns the song's new ID.
        """
        song['id'] = _random_id()
        self.playlist.append(song)
        self._update_store()

        return song['id']

    def del_song(self, song_id):
        for idx, song in enumerate(self.playlist):
            if song_id == self.playlist['id']:
                del self.playlist[idx]
                return

    def move_song(self, idx1, idx2):
        song = self.playlist[idx1]
        del self.playlist[idx1]
        self.playlist.insert(idx2, song)

    def get_playlist(self):
        return self.playlist
<|endoftext|>