<commit_before>from typing import Optional, Callable, Any, Union, Iterable, Sequence
from asyncio import get_event_loop, coroutine, iscoroutinefunction
from collections import defaultdict
import asyncio
import time

from .util import qualified_name, asynchronous
from .event import EventSource, Event

__all__ = ('Resource', 'ResourceEvent', 'ResourceConflict', 'ResourceNotFound',
           'ContextFinishEvent', 'Context')

class Resource:
    """
    Contains the resource value or its creator callable, plus some metadata.

    :ivar str alias: alias of the resource
    :ivar Sequence[str] types: type names the resource was registered with
    :ivar str context_attr: the context attribute of the resource
    :ivar Callable[['Context'], Any] creator: callable to create the value
                                              (in case of a lazy resource)
    """

    __slots__ = 'value', 'types', 'alias', 'context_attr', 'creator'

    def __init__(self, value, types: Sequence[str], alias: str, context_attr: Optional[str],
                 creator: Callable[['Context'], Any]=None):
        self.value = value
        self.types = types
        self.alias = alias
        self.context_attr = context_attr
        self.creator = creator

    def get_value(self, ctx: 'Context'):
        if self.value is None and self.creator is not None:
            self.value = self.creator(ctx)
            if self.context_attr:
                setattr(ctx, self.context_attr, self.value)

        return self.value

    def __repr__(self):
        return '{0.__class__.__name__}({0})'.format(self)

    def __str__(self):
        return ('types={0.types!r}, alias={0.alias!r}, value={0.value!r}, '
                'context_attr={0.context_attr!r}, creator={1}'
                .format(self, qualified_name(self.creator) if self.creator else None))

class ResourceEvent(Event):
    """
    Dispatched when a resource has been published to or removed from a context.

    :ivar Context source: the relevant context
    :ivar Resource resource: the resource that was published or removed
    """

    __slots__ = 'resource'

    def __init__(self, source: 'Context', topic: str, resource: Resource):
        super().__init__(source, topic)
        self.resource = resource

class ResourceConflict(Exception):
    """
    Raised when a new resource that is being published conflicts with an existing resource or
    context variable.
    """

class ResourceNotFound(LookupError):
    """Raised when a resource request cannot be fulfilled within the allotted time."""

    def __init__(self, type: str, alias: str):
        super().__init__(type, alias)
        self.type = type
        self.alias = alias

    def __str__(self):
        return 'no matching resource was found for type={0.type!r} alias={0.alias!r}'.format(self)

class ContextFinishEvent(Event):
    """
    Dispatched when a context has served its purpose and is being torn down.

    :ivar BaseException exception: the exception that caused the context to finish (or ``None``)
    """

    __slots__ = 'exception'

    def __init__(self, source: 'Context', topic: str, exception: Optional[BaseException]):
        super().__init__(source, topic)
        self.exception = exception

class Context(EventSource):
    """
    Contexts give request handlers and callbacks access to resources.

    Contexts are stacked in a way that accessing an attribute that is not present in the current
    context causes the attribute to be looked up in the parent instance and so on, until the
    attribute is found (or ``AttributeError`` is raised).

    Supported events:
      * finished (:class:`~asphalt.core.event.Event`): the context has served its purpose and is
        being discarded
      * resource_published (:class:`ResourceEvent`): a resource has been published in this context
      * resource_removed (:class:`ResourceEvent`): a resource has been removed from this context

    :param parent: the parent context, if any
    :param default_timeout: default timeout for :meth:`request_resource` if omitted from the
                            call arguments
    """

    def __init__(self, parent: 'Context'=None, default_timeout: int=10):
        super().__init__()
        self._register_topics({
            'finished': ContextFinishEvent,
            'resource_published': ResourceEvent,
            'resource_removed': ResourceEvent
        })

        self._parent = parent
        self._resources = defaultdict(dict)  # type: Dict[str, Dict[str, Resource]]
        self._resource_creators = {}  # type: Dict[str, Callable[[Context], Any]
        self.default_timeout = default_timeout

        # Forward resource events from the parent(s)
        if parent is not None:
            parent.add_listener('resource_published', self.dispatch)
            parent.add_listener('resource_removed', self.dispatch)

    def __getattr__(self, name):
        creator = self._resource_creators.get(name)
        if creator is not None:
            value = creator(self)
            setattr(self, name, value)
            return value

        if self._parent is not None:
            return getattr(self._parent, name)

        raise AttributeError('no such context variable: {}'.format(name))

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        get_event_loop().run_until_complete(self.dispatch('finished', exc_val))

    @coroutine
    def __aenter__(self):
        return self

    @coroutine
    def __aexit__(self, exc_type, exc_val, exc_tb):
        yield from self.dispatch('finished', exc_val)

    @coroutine
    def _publish_resource(self, value, alias: str, context_attr: str,
                          types: Iterable[Union[str, type]],
                          creator: Optional[Callable[['Context'], Any]]):
        assert isinstance(alias, str) and alias, 'alias must be a nonempty string'
        assert context_attr is None or isinstance(context_attr, str),\
            'context_attr must be a nonempty string or None'

        if isinstance(types, (str, type)):
            types = (types,)

        # Check for alias conflicts with existing resources
        types = tuple(t if isinstance(t, str) else qualified_name(t) for t in types)
        for typename in types:
            if alias in self._resources[typename]:
                raise ResourceConflict(
                    'this context has an existing resource of type {} using the alias "{}"'
                    .format(typename, alias))

        # Check for context attribute conflicts
        if context_attr:
            # Check that there is no existing attribute by that name
            if context_attr in dir(self):
                raise ResourceConflict(
                    'this context already has an attribute "{}"'.format(context_attr))

            # Check that there is no existing lazy resource using the same context attribute
            if context_attr in self._resource_creators:
                raise ResourceConflict(
                    'this context has an existing lazy resource using the attribute "{}"'
                    .format(context_attr))

        # Register the resource
        resource = Resource(value, types, alias, context_attr, creator)
        for typename in types:
            self._resources[typename][resource.alias] = resource

        if creator is not None and context_attr is not None:
            self._resource_creators[context_attr] = creator

        # Add the resource as an attribute of this context if context_attr is defined
        if creator is None and resource.context_attr:
            setattr(self, context_attr, value)

        yield from self.dispatch('resource_published', resource)
        return resource

    @asynchronous
    def publish_resource(
            self, value, alias: str='default', context_attr: str=None, *,
            types: Union[Union[str, type], Iterable[Union[str, type]]]=()) -> Resource:
        """
        Publishes a resource and dispatches a ``resource_published`` event.

        :param value: the actual resource value
        :param alias: name of this resource (unique among all its registered types)
        :param context_attr: name of the context attribute this resource will be accessible as
        :param types: type(s) to register the resource as (omit to use the type of ``value``)
        :return: the resource handle
        :raises ResourceConflict: if the resource conflicts with an existing one in any way
        """

        assert value is not None, 'value must not be None'
        if not types:
            types = [type(value)]

        return self._publish_resource(value, alias, context_attr, types, None)

    @asynchronous
    def publish_lazy_resource(self, creator: Callable[['Context'], Any],
                              types: Union[Union[str, type], Iterable[Union[str, type]]],
                              alias: str='default', context_attr: str=None) -> Resource:
        """
        Publishes a "lazy" or "contextual" resource and dispatches a ``resource_published`` event.
        Instead of a concrete resource value, you supply a creator callable which is called with a
        context object as its argument when the resource is being requested either via
        :meth:`request_resource` or by context attribute access.
        The return value of the creator callable will be cached so the creator will only be called
        once per context instance.

        .. note:: The creator callable can **NOT** be a coroutine function, as coroutines cannot
        be run as a side effect of attribute access.

        :param creator: a callable taking a context instance as argument
        :param types: type(s) to register the resource as
        :param context_attr: name of the context attribute this resource will be accessible as
        :return: the resource handle
        :raises ResourceConflict: if there is an existing resource creator for the given
                                  types or context variable
        """

        assert callable(creator), 'creator must be callable'
        assert not iscoroutinefunction(creator), 'creator cannot be a coroutine function'
        return self._publish_resource(None, alias, context_attr, types, creator)

    @asynchronous
    def remove_resource(self, resource: Resource):
        """
        Removes the given resource from the collection and dispatches a ``resource_removed`` event.

        :param resource: the resource to be removed
        :raises LookupError: the given resource was not in the collection
        """

        try:
            for typename in resource.types:
                del self._resources[typename][resource.alias]
        except KeyError:
            raise LookupError('{!r} not found in this context'.format(resource)) from None

        # Remove the creator from the resource creators
        if resource.creator is not None:
            del self._resource_creators[resource.context_attr]

        # Remove the attribute from this context
        if resource.context_attr and resource.context_attr in self.__dict__:
            delattr(self, resource.context_attr)

        yield from self.dispatch('resource_removed', resource)

    def _get_resource(self, resource_type: str, alias: str) -> Optional[Resource]:
        resource = self._resources.get(resource_type, {}).get(alias)
        if resource is None and self._parent is not None:
            resource = self._parent._get_resource(resource_type, alias)

        return resource

    @asynchronous
    def request_resource(self, type: Union[str, type], alias: str='default', *,
                         timeout: Union[int, float, None]=None, optional: bool=False):
        """
        Requests a resource matching the given type and alias.
        If no such resource was found, this method will wait ``timeout`` seconds for it to become
        available.

        :param type: type of the requested resource
        :param alias: alias of the requested resource
        :param timeout: the timeout (in seconds; omit to use the default timeout)
        :param optional: if ``True``, return None instead of raising an exception if no matching \
                         resource becomes available within the timeout period
        :return: the value contained by the requested resource
                 (**NOT** a :class:`Resource` instance)
        :raises ResourceNotFound: if the requested resource does not become available in the \
                                  allotted time
        """

        if not type:
            raise ValueError('type must be a type or a nonempty string')
        if not alias:
            raise ValueError('alias must be a nonempty string')

        timeout = timeout if timeout is not None else self.default_timeout
        assert timeout >= 0, 'timeout must be a positive integer'

        resource_type = qualified_name(type) if not isinstance(type, str) else type
        handle = event = start_time = None
        resource = self._get_resource(resource_type, alias)
        while resource is None:
            if not handle:
                event = asyncio.Event()
                start_time = time.monotonic()
                handle = self.add_listener('resource_published', lambda e: event.set())
            try:
                delay = timeout - (time.monotonic() - start_time) if timeout is not None else None
                yield from asyncio.wait_for(event.wait(), delay)
            except asyncio.TimeoutError:
                self.remove_listener(handle)
                if optional:
                    return None
                else:
                    raise ResourceNotFound(resource_type, alias)

            resource = self._get_resource(resource_type, alias)

        if handle:
            self.remove_listener(handle)

        return resource.get_value(self)
<commit_msg>Refactored request_resource() for better readability and efficiency<commit_after>from asyncio.futures import Future

from typing import Optional, Callable, Any, Union, Iterable, Sequence
from asyncio import get_event_loop, coroutine, iscoroutinefunction
from collections import defaultdict
import asyncio

from .util import qualified_name, asynchronous
from .event import EventSource, Event

__all__ = ('Resource', 'ResourceEvent', 'ResourceConflict', 'ResourceNotFound',
           'ContextFinishEvent', 'Context')

class Resource:
    """
    Contains the resource value or its creator callable, plus some metadata.

    :ivar str alias: alias of the resource
    :ivar Sequence[str] types: type names the resource was registered with
    :ivar str context_attr: the context attribute of the resource
    :ivar Callable[['Context'], Any] creator: callable to create the value
                                              (in case of a lazy resource)
    """

    __slots__ = 'value', 'types', 'alias', 'context_attr', 'creator'

    def __init__(self, value, types: Sequence[str], alias: str, context_attr: Optional[str],
                 creator: Callable[['Context'], Any]=None):
        self.value = value
        self.types = types
        self.alias = alias
        self.context_attr = context_attr
        self.creator = creator

    def get_value(self, ctx: 'Context'):
        if self.value is None and self.creator is not None:
            self.value = self.creator(ctx)
            if self.context_attr:
                setattr(ctx, self.context_attr, self.value)

        return self.value

    def __repr__(self):
        return '{0.__class__.__name__}({0})'.format(self)

    def __str__(self):
        return ('types={0.types!r}, alias={0.alias!r}, value={0.value!r}, '
                'context_attr={0.context_attr!r}, creator={1}'
                .format(self, qualified_name(self.creator) if self.creator else None))

class ResourceEvent(Event):
    """
    Dispatched when a resource has been published to or removed from a context.

    :ivar Context source: the relevant context
    :ivar Resource resource: the resource that was published or removed
    """

    __slots__ = 'resource'

    def __init__(self, source: 'Context', topic: str, resource: Resource):
        super().__init__(source, topic)
        self.resource = resource

class ResourceConflict(Exception):
    """
    Raised when a new resource that is being published conflicts with an existing resource or
    context variable.
    """

class ResourceNotFound(LookupError):
    """Raised when a resource request cannot be fulfilled within the allotted time."""

    def __init__(self, type: str, alias: str):
        super().__init__(type, alias)
        self.type = type
        self.alias = alias

    def __str__(self):
        return 'no matching resource was found for type={0.type!r} alias={0.alias!r}'.format(self)

class ContextFinishEvent(Event):
    """
    Dispatched when a context has served its purpose and is being torn down.

    :ivar BaseException exception: the exception that caused the context to finish (or ``None``)
    """

    __slots__ = 'exception'

    def __init__(self, source: 'Context', topic: str, exception: Optional[BaseException]):
        super().__init__(source, topic)
        self.exception = exception

class Context(EventSource):
    """
    Contexts give request handlers and callbacks access to resources.

    Contexts are stacked in a way that accessing an attribute that is not present in the current
    context causes the attribute to be looked up in the parent instance and so on, until the
    attribute is found (or ``AttributeError`` is raised).

    Supported events:
      * finished (:class:`~asphalt.core.event.Event`): the context has served its purpose and is
        being discarded
      * resource_published (:class:`ResourceEvent`): a resource has been published in this context
      * resource_removed (:class:`ResourceEvent`): a resource has been removed from this context

    :param parent: the parent context, if any
    :param default_timeout: default timeout for :meth:`request_resource` if omitted from the
                            call arguments
    """

    def __init__(self, parent: 'Context'=None, default_timeout: int=10):
        super().__init__()
        self._register_topics({
            'finished': ContextFinishEvent,
            'resource_published': ResourceEvent,
            'resource_removed': ResourceEvent
        })

        self._parent = parent
        self._resources = defaultdict(dict)  # type: Dict[str, Dict[str, Resource]]
        self._resource_creators = {}  # type: Dict[str, Callable[[Context], Any]
        self.default_timeout = default_timeout

    def __getattr__(self, name):
        creator = self._resource_creators.get(name)
        if creator is not None:
            value = creator(self)
            setattr(self, name, value)
            return value

        if self._parent is not None:
            return getattr(self._parent, name)

        raise AttributeError('no such context variable: {}'.format(name))

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        get_event_loop().run_until_complete(self.dispatch('finished', exc_val))

    @coroutine
    def __aenter__(self):
        return self

    @coroutine
    def __aexit__(self, exc_type, exc_val, exc_tb):
        yield from self.dispatch('finished', exc_val)

    @coroutine
    def _publish_resource(self, value, alias: str, context_attr: str,
                          types: Iterable[Union[str, type]],
                          creator: Optional[Callable[['Context'], Any]]):
        assert isinstance(alias, str) and alias, 'alias must be a nonempty string'
        assert context_attr is None or isinstance(context_attr, str),\
            'context_attr must be a nonempty string or None'

        if isinstance(types, (str, type)):
            types = (types,)

        # Check for alias conflicts with existing resources
        types = tuple(t if isinstance(t, str) else qualified_name(t) for t in types)
        for typename in types:
            if alias in self._resources[typename]:
                raise ResourceConflict(
                    'this context has an existing resource of type {} using the alias "{}"'
                    .format(typename, alias))

        # Check for context attribute conflicts
        if context_attr:
            # Check that there is no existing attribute by that name
            if context_attr in dir(self):
                raise ResourceConflict(
                    'this context already has an attribute "{}"'.format(context_attr))

            # Check that there is no existing lazy resource using the same context attribute
            if context_attr in self._resource_creators:
                raise ResourceConflict(
                    'this context has an existing lazy resource using the attribute "{}"'
                    .format(context_attr))

        # Register the resource
        resource = Resource(value, types, alias, context_attr, creator)
        for typename in types:
            self._resources[typename][resource.alias] = resource

        if creator is not None and context_attr is not None:
            self._resource_creators[context_attr] = creator

        # Add the resource as an attribute of this context if context_attr is defined
        if creator is None and resource.context_attr:
            setattr(self, context_attr, value)

        yield from self.dispatch('resource_published', resource)
        return resource

    @asynchronous
    def publish_resource(
            self, value, alias: str='default', context_attr: str=None, *,
            types: Union[Union[str, type], Iterable[Union[str, type]]]=()) -> Resource:
        """
        Publishes a resource and dispatches a ``resource_published`` event.

        :param value: the actual resource value
        :param alias: name of this resource (unique among all its registered types)
        :param context_attr: name of the context attribute this resource will be accessible as
        :param types: type(s) to register the resource as (omit to use the type of ``value``)
        :return: the resource handle
        :raises ResourceConflict: if the resource conflicts with an existing one in any way
        """

        assert value is not None, 'value must not be None'
        if not types:
            types = [type(value)]

        return self._publish_resource(value, alias, context_attr, types, None)

    @asynchronous
    def publish_lazy_resource(self, creator: Callable[['Context'], Any],
                              types: Union[Union[str, type], Iterable[Union[str, type]]],
                              alias: str='default', context_attr: str=None) -> Resource:
        """
        Publishes a "lazy" or "contextual" resource and dispatches a ``resource_published`` event.
        Instead of a concrete resource value, you supply a creator callable which is called with a
        context object as its argument when the resource is being requested either via
        :meth:`request_resource` or by context attribute access.
        The return value of the creator callable will be cached so the creator will only be called
        once per context instance.

        .. note:: The creator callable can **NOT** be a coroutine function, as coroutines cannot
        be run as a side effect of attribute access.

        :param creator: a callable taking a context instance as argument
        :param types: type(s) to register the resource as
        :param context_attr: name of the context attribute this resource will be accessible as
        :return: the resource handle
        :raises ResourceConflict: if there is an existing resource creator for the given
                                  types or context variable
        """

        assert callable(creator), 'creator must be callable'
        assert not iscoroutinefunction(creator), 'creator cannot be a coroutine function'
        return self._publish_resource(None, alias, context_attr, types, creator)

    @asynchronous
    def remove_resource(self, resource: Resource):
        """
        Removes the given resource from the collection and dispatches a ``resource_removed`` event.

        :param resource: the resource to be removed
        :raises LookupError: the given resource was not in the collection
        """

        try:
            for typename in resource.types:
                del self._resources[typename][resource.alias]
        except KeyError:
            raise LookupError('{!r} not found in this context'.format(resource)) from None

        # Remove the creator from the resource creators
        if resource.creator is not None:
            del self._resource_creators[resource.context_attr]

        # Remove the attribute from this context
        if resource.context_attr and resource.context_attr in self.__dict__:
            delattr(self, resource.context_attr)

        yield from self.dispatch('resource_removed', resource)

    @asynchronous
    def request_resource(self, type: Union[str, type], alias: str='default', *,
                         timeout: Union[int, float, None]=None, optional: bool=False):
        """
        Requests a resource matching the given type and alias.
        If no such resource was found, this method will wait ``timeout`` seconds for it to become
        available.

        :param type: type of the requested resource
        :param alias: alias of the requested resource
        :param timeout: the timeout (in seconds; omit to use the default timeout)
        :param optional: if ``True``, return None instead of raising an exception if no matching \
                         resource becomes available within the timeout period
        :return: the value contained by the requested resource
                 (**NOT** a :class:`Resource` instance)
        :raises ResourceNotFound: if the requested resource does not become available in the \
                                  allotted time
        """

        if not type:
            raise ValueError('type must be a type or a nonempty string')
        if not alias:
            raise ValueError('alias must be a nonempty string')

        resource_type = qualified_name(type) if not isinstance(type, str) else type
        timeout = timeout if timeout is not None else self.default_timeout
        assert timeout >= 0, 'timeout must be a positive integer'

        # Build a context chain from this context and its parents
        context_chain = [self]
        while context_chain[-1]._parent:
            context_chain.append(context_chain[-1]._parent)

        # First try to look up the resource in the context chain
        for ctx in context_chain:
            resource = ctx._resources.get(resource_type, {}).get(alias)
            if resource is not None:
                return resource.get_value(self)

        # Listen to resource publish events in the whole chain and wait for the right kind of
        # resource to be published
        def resource_listener(event: ResourceEvent):
            if event.resource.alias == alias and resource_type in event.resource.types:
                future.set_result(event.resource)

        future = Future()
        listeners = [ctx.add_listener('resource_published', resource_listener) for
                     ctx in context_chain]
        try:
            resource = yield from asyncio.wait_for(future, timeout)
        except asyncio.TimeoutError:
            if optional:
                return None
            else:
                raise ResourceNotFound(resource_type, alias)
        else:
            return resource.get_value(self)
        finally:
            for listener in listeners:
                listener.remove()
<|endoftext|>


<commit_before># 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 oslo_config import cfg

from keystone.conf import utils

jws_public_key_repository = cfg.StrOpt(
    'jws_public_key_repository',
    default='/etc/keystone/jws-keys/public',
    help=utils.fmt("""
Directory containing public keys for validating JWS token signatures. This
directory must exist in order for keystone's server process to start. It must
also be readable by keystone's server process. It must contain at least one
public key that corresponds to a private key in `keystone.conf [jwt_tokens]
jws_private_key_repository`. This option is only applicable in deployments
issuing JWS tokens and setting `keystone.conf [tokens] provider = jws`.
"""))
jws_private_key_repository = cfg.StrOpt(
    'jws_private_key_repository',
    default='/etc/keystone/jws-keys/private',
    help=utils.fmt("""
Directory containing private keys for signing JWS tokens. This directory must
exist in order for keystone's server process to start. It must also be readable
by keystone's server process. It must contain at least one private key that
corresponds to a public key in `keystone.conf [jwt_tokens]
jws_public_key_repository`. In the event there are multiple private keys in
this directory, keystone will use a key named `private.pem` to sign tokens. In
the future, keystone may support the ability to sign tokens with multiple
private keys. For now, only a key named `private.pem` within this directory is
required to issue JWS tokens. This option is only applicable in deployments
issuing JWS tokens and setting `keystone.conf [tokens] provider = jws`.
"""))

GROUP_NAME = __name__.split('.')[-1]
ALL_OPTS = [
    jws_public_key_repository,
    jws_private_key_repository
]

def register_opts(conf):
    conf.register_opts(ALL_OPTS, group=GROUP_NAME)

def list_opts():
    return {GROUP_NAME: ALL_OPTS}
<commit_msg>Fix documentation typo<commit_after># 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 oslo_config import cfg

from keystone.conf import utils

jws_public_key_repository = cfg.StrOpt(
    'jws_public_key_repository',
    default='/etc/keystone/jws-keys/public',
    help=utils.fmt("""
Directory containing public keys for validating JWS token signatures. This
directory must exist in order for keystone's server process to start. It must
also be readable by keystone's server process. It must contain at least one
public key that corresponds to a private key in `keystone.conf [jwt_tokens]
jws_private_key_repository`. This option is only applicable in deployments
issuing JWS tokens and setting `keystone.conf [token] provider = jws`.
"""))
jws_private_key_repository = cfg.StrOpt(
    'jws_private_key_repository',
    default='/etc/keystone/jws-keys/private',
    help=utils.fmt("""
Directory containing private keys for signing JWS tokens. This directory must
exist in order for keystone's server process to start. It must also be readable
by keystone's server process. It must contain at least one private key that
corresponds to a public key in `keystone.conf [jwt_tokens]
jws_public_key_repository`. In the event there are multiple private keys in
this directory, keystone will use a key named `private.pem` to sign tokens. In
the future, keystone may support the ability to sign tokens with multiple
private keys. For now, only a key named `private.pem` within this directory is
required to issue JWS tokens. This option is only applicable in deployments
issuing JWS tokens and setting `keystone.conf [token] provider = jws`.
"""))

GROUP_NAME = __name__.split('.')[-1]
ALL_OPTS = [
    jws_public_key_repository,
    jws_private_key_repository
]

def register_opts(conf):
    conf.register_opts(ALL_OPTS, group=GROUP_NAME)

def list_opts():
    return {GROUP_NAME: ALL_OPTS}
<|endoftext|>


<commit_before>"""
Django settings for biosys project.
"""
from confy import env, database
import os
import sys
from unipath import Path

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = Path(__file__).ancestor(3)
PROJECT_DIR = os.path.join(BASE_DIR, 'biosys')
# Add PROJECT_DIR to the system path.
sys.path.insert(0, PROJECT_DIR)
# Add PROJECT_DIR/apps to the system path.
sys.path.insert(0, os.path.join(PROJECT_DIR, 'apps'))

# Security settings
DEBUG = env('DEBUG', False)
SECRET_KEY = env('SECRET_KEY')
CSRF_COOKIE_SECURE = env('CSRF_COOKIE_SECURE', False)
SESSION_COOKIE_SECURE = env('SESSION_COOKIE_SECURE', False)
if not DEBUG:
    # Localhost, UAT and Production hosts
    ALLOWED_HOSTS = [
        'localhost',
        '127.0.0.1',
        'biosys.dpaw.wa.gov.au',
        'biosys.dpaw.wa.gov.au.',
        'biosys-uat.dpaw.wa.gov.au',
        'biosys-uat.dpaw.wa.gov.au.',
    ]

# Application definition
# The variables below are added to all responses in biosys/context_processors.py
SITE_TITLE = 'BioSys - WA Biological Survey Database'
APPLICATION_VERSION_NO = '1.0'

INSTALLED_APPS = (
    'grappelli',  # Must be before django.contrib.admin
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.gis',
    'django.contrib.postgres',
    # Third-party apps.
    'django_extensions',
    'envelope',
    'reversion',
    'tastypie',
    'webtemplate_dpaw',
    'django_wsgiserver',
)

PROJECT_APPS = (
    'main',
    'species',
    'vegetation',
    'animals',
    'upload',
    'download',
    'publish',
)

INSTALLED_APPS += PROJECT_APPS

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'reversion.middleware.RevisionMiddleware',
    'dpaw_utils.middleware.SSOLoginMiddleware',
)

ROOT_URLCONF = 'biosys.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            os.path.join(PROJECT_DIR, 'templates'),
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                "django.contrib.auth.context_processors.auth",
                "django.core.context_processors.debug",
                "django.core.context_processors.i18n",
                "django.core.context_processors.media",
                "django.core.context_processors.static",
                "django.core.context_processors.tz",
                "django.contrib.messages.context_processors.messages",
                "django.core.context_processors.request",
                "biosys.context_processors.standard",
            ],
        },
    },
]

WSGI_APPLICATION = 'biosys.wsgi.application'

# Database
DATABASES = {'default': database.config()}

# Internationalization
LANGUAGE_CODE = 'en-au'
TIME_ZONE = 'Australia/Perth'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Set the formats that will be displayed in date fields
# If USE_L10N == True, then locale-dictated format has higher precedence.
DATE_FORMAT = '%d/%m/%Y'      # O5/10/2006
# Set the formats that will be accepted in date input fields
DATE_INPUT_FORMATS = (
    '%d/%m/%Y',             # '25/10/2006'
    '%Y-%m-%d',             # '2006-10-25'
    '%Y_%m_%d',             # '2006_10_25'
)

# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
# Absolute path to the directory static files should be collected to.
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
# Ensure that the media directory exists:
if not os.path.exists(os.path.join(BASE_DIR, 'media')):
    os.mkdir(os.path.join(BASE_DIR, 'media'))
# Absolute filesystem path to the directory that will hold user-uploaded files.
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
MEDIA_URL = '/media/'

# Additional locations of static files
STATICFILES_DIRS = (
    os.path.join(PROJECT_DIR, 'static'),
)

# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)

LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '/'

# Logging settings
# Ensure that the logs directory exists:
LOG_FOLDER = os.path.join(BASE_DIR, 'logs')
if not os.path.exists(LOG_FOLDER):
    os.mkdir(LOG_FOLDER)
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'precise': {
            'format': '{%(asctime)s.%(msecs)d}  %(message)s [%(levelname)s %(name)s]',
            'datefmt': '%H:%M:%S'
        },
        'default': {
            'format': '%(asctime)s %(levelname)-8s [%(name)-15s] %(message)s',
            'datefmt': '%Y/%m/%d %H:%M:%S',
        }
    },
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler'
        },
        'file': {
            'level': 'WARNING',
            'class': 'logging.handlers.TimedRotatingFileHandler',
            'filename': os.path.join(LOG_FOLDER, 'biosys.log'),
            'when': 'midnight',
            'backupCount': 2,
            'formatter': 'default',
        },
        'console': {
            'level': 'WARNING',
            'class': 'logging.StreamHandler',
            'formatter': 'precise',
        },
    },
    'loggers': {
        '': {
            'handlers': ['file', 'console'],
            'level': 'WARNING',
            'propagate': True
        },
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
    }
}

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
)

# .local.py overrides all the common settings.
try:
    from .local import *
except ImportError:
    pass

# if len(sys.argv) > 1 and 'test' in sys.argv[1]:
#     from .testing import *

# django-tastypie settings
TASTYPIE_ALLOW_MISSING_SLASH = True
TASTYPIE_DATETIME_FORMATTING = 'iso-8601-strict'
TASTYPIE_DEFAULT_FORMATS = ['json', 'html']
API_LIMIT_PER_PAGE = 0

# Grappelli settings
GRAPPELLI_ADMIN_TITLE = SITE_TITLE + ' administration'

# envelop extensions
ENVELOPE_EMAIL_RECIPIENTS = ['biosys@DPaW.wa.gov.au']
ENVELOPE_USE_HTML_EMAIL = False

# Email settings
EMAIL_HOST = env('EMAIL_HOST', 'email.host')
EMAIL_PORT = env('EMAIL_PORT', 25)
<commit_msg>Removed django_wsgiserver from INSTALLED_APPS (no longer in reqs).<commit_after>"""
Django settings for biosys project.
"""
from confy import env, database
import os
import sys
from unipath import Path

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = Path(__file__).ancestor(3)
PROJECT_DIR = os.path.join(BASE_DIR, 'biosys')
# Add PROJECT_DIR to the system path.
sys.path.insert(0, PROJECT_DIR)
# Add PROJECT_DIR/apps to the system path.
sys.path.insert(0, os.path.join(PROJECT_DIR, 'apps'))

# Security settings
DEBUG = env('DEBUG', False)
SECRET_KEY = env('SECRET_KEY')
CSRF_COOKIE_SECURE = env('CSRF_COOKIE_SECURE', False)
SESSION_COOKIE_SECURE = env('SESSION_COOKIE_SECURE', False)
if not DEBUG:
    # Localhost, UAT and Production hosts
    ALLOWED_HOSTS = [
        'localhost',
        '127.0.0.1',
        'biosys.dpaw.wa.gov.au',
        'biosys.dpaw.wa.gov.au.',
        'biosys-uat.dpaw.wa.gov.au',
        'biosys-uat.dpaw.wa.gov.au.',
    ]

# Application definition
# The variables below are added to all responses in biosys/context_processors.py
SITE_TITLE = 'BioSys - WA Biological Survey Database'
APPLICATION_VERSION_NO = '1.0'

INSTALLED_APPS = (
    'grappelli',  # Must be before django.contrib.admin
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.gis',
    'django.contrib.postgres',
    # Third-party apps.
    'django_extensions',
    'envelope',
    'reversion',
    'tastypie',
    'webtemplate_dpaw',
)

PROJECT_APPS = (
    'main',
    'species',
    'vegetation',
    'animals',
    'upload',
    'download',
    'publish',
)

INSTALLED_APPS += PROJECT_APPS

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'reversion.middleware.RevisionMiddleware',
    'dpaw_utils.middleware.SSOLoginMiddleware',
)

ROOT_URLCONF = 'biosys.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            os.path.join(PROJECT_DIR, 'templates'),
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                "django.contrib.auth.context_processors.auth",
                "django.core.context_processors.debug",
                "django.core.context_processors.i18n",
                "django.core.context_processors.media",
                "django.core.context_processors.static",
                "django.core.context_processors.tz",
                "django.contrib.messages.context_processors.messages",
                "django.core.context_processors.request",
                "biosys.context_processors.standard",
            ],
        },
    },
]

WSGI_APPLICATION = 'biosys.wsgi.application'

# Database
DATABASES = {'default': database.config()}

# Internationalization
LANGUAGE_CODE = 'en-au'
TIME_ZONE = 'Australia/Perth'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Set the formats that will be displayed in date fields
# If USE_L10N == True, then locale-dictated format has higher precedence.
DATE_FORMAT = '%d/%m/%Y'      # O5/10/2006
# Set the formats that will be accepted in date input fields
DATE_INPUT_FORMATS = (
    '%d/%m/%Y',             # '25/10/2006'
    '%Y-%m-%d',             # '2006-10-25'
    '%Y_%m_%d',             # '2006_10_25'
)

# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
# Absolute path to the directory static files should be collected to.
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
# Ensure that the media directory exists:
if not os.path.exists(os.path.join(BASE_DIR, 'media')):
    os.mkdir(os.path.join(BASE_DIR, 'media'))
# Absolute filesystem path to the directory that will hold user-uploaded files.
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
MEDIA_URL = '/media/'

# Additional locations of static files
STATICFILES_DIRS = (
    os.path.join(PROJECT_DIR, 'static'),
)

# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)

LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '/'

# Logging settings
# Ensure that the logs directory exists:
LOG_FOLDER = os.path.join(BASE_DIR, 'logs')
if not os.path.exists(LOG_FOLDER):
    os.mkdir(LOG_FOLDER)
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'precise': {
            'format': '{%(asctime)s.%(msecs)d}  %(message)s [%(levelname)s %(name)s]',
            'datefmt': '%H:%M:%S'
        },
        'default': {
            'format': '%(asctime)s %(levelname)-8s [%(name)-15s] %(message)s',
            'datefmt': '%Y/%m/%d %H:%M:%S',
        }
    },
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler'
        },
        'file': {
            'level': 'WARNING',
            'class': 'logging.handlers.TimedRotatingFileHandler',
            'filename': os.path.join(LOG_FOLDER, 'biosys.log'),
            'when': 'midnight',
            'backupCount': 2,
            'formatter': 'default',
        },
        'console': {
            'level': 'WARNING',
            'class': 'logging.StreamHandler',
            'formatter': 'precise',
        },
    },
    'loggers': {
        '': {
            'handlers': ['file', 'console'],
            'level': 'WARNING',
            'propagate': True
        },
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
    }
}

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
)

# .local.py overrides all the common settings.
try:
    from .local import *
except ImportError:
    pass

# if len(sys.argv) > 1 and 'test' in sys.argv[1]:
#     from .testing import *

# django-tastypie settings
TASTYPIE_ALLOW_MISSING_SLASH = True
TASTYPIE_DATETIME_FORMATTING = 'iso-8601-strict'
TASTYPIE_DEFAULT_FORMATS = ['json', 'html']
API_LIMIT_PER_PAGE = 0

# Grappelli settings
GRAPPELLI_ADMIN_TITLE = SITE_TITLE + ' administration'

# envelop extensions
ENVELOPE_EMAIL_RECIPIENTS = ['biosys@DPaW.wa.gov.au']
ENVELOPE_USE_HTML_EMAIL = False

# Email settings
EMAIL_HOST = env('EMAIL_HOST', 'email.host')
EMAIL_PORT = env('EMAIL_PORT', 25)
<|endoftext|>


<commit_before>from django.shortcuts import render

from faq.models import Faq

def faq(request):
    faq = Faq.objects.all()
    return render(request, "faq/index.dtl", {'faq' : faq})
<commit_msg>Fixed header issue on FAQ page.<commit_after>from dnollkse.viewHelper import render

from faq.models import Faq

def faq(request):
    faq = Faq.objects.all()
    return render(request, "faq/index.dtl", {'faq' : faq})
<|endoftext|>


<commit_before>from os import mkdir
from os import listdir
from os import link
from os import unlink
from os import symlink
from os import readlink
from os import rmdir
from os import stat
from os import chmod
from errno import EEXIST as FileExists
from errno import EISDIR as DirectoryExists
from hashlib import md5
from os.path import stat as statc
from os.path import normpath
from os.path import split
from os.path import isabs
from os.path import exists
from os.path import isdir
from shutil import copyfile
from os.path import isfile, islink, sep
from func_prototypes import typed, returned

_BLOCKSIZE = 65536

@returned(basestring)
@typed(basestring)
def _decodePath(name):
  if type(name) == str: # leave unicode ones alone
    try:
      name = name.decode('utf8')
    except:
      name = name.decode('windows-1252')
  return name

class Path:
  def __init__(self, path, frame=None):
    if isinstance(path, basestring):
      if isabs(path):
        self._path = normpath(path)
      else:
        if frame is not None:
          assert isinstance(frame, Path)
          self._path = frame.join(path)._path
        else:
          raise ValueError("Frame is required when building relative paths: %s" % path)
    else:
      self._path = path._path

  def __unicode__(self):
    return u'%s' % self._path

  def __str__(self):
    return unicode(self).encode('utf-8')

  def __repr__(self):
    return str(self)

  def mkdir(self):
    try:
      mkdir(self._path)
    except OSError as e:
      if e.errno == FileExists:
        pass
      elif e.errno == DirectoryExists:
        pass
      else:
        raise e

  # Returns the parent of self. If self is root ('/'), parent returns None.
  # You much check the output of parent before using the value.
  # Notcie that parent of root in the shell is '/', so this is a semantic difference
  # between us and POSIX.
  def parent(self):
    if self._path == sep:
      return None
    else:
      return Path(split(self._path)[0])

  def parents(self):
    paths = [self]
    path = self
    parent = path.parent()
    while parent is not None:
      paths.append(parent)
      path = parent
      parent = path.parent()
    return reversed(paths)

  def relative_to(self, relative, leading_sep=True):
    assert isinstance(relative, Path)
    if leading_sep == True:
      prefix = sep
    else:
      prefix = ""
    self_parents = list(self.parents())
    if relative in self_parents:
      relative_str = relative._path
      return prefix + self._path[len(relative_str)+1:]
    relative_parents = list(reversed(list(relative.parents())))
    if self in relative_parents:
      backups = relative_parents.index(self) - 1
      assert backups >= 0
      assert leading_sep == False, "Leading seperator is meaningless with backtracking"
      return "/".join([".."]*backups)
    raise ValueError("Relationship between %s and %s is complex" % (self, relative))

  def exists(self):
    return exists(self._path)

  def readlink(self, frame):
    return Path(readlink(self._path), frame)

  def link(self, dst):
    assert isinstance(dst, Path)
    link(dst._path, self._path)

  def symlink(self, dst):
    assert isinstance(dst, Path)
    symlink(dst._path, self._path)

  def copy(self, dst):
    assert isinstance(dst, Path)
    copyfile(self._path, dst._path)

  #TODO I'd like to have a way of cleaning up
  # empty directories recursvely, only if this
  # unlink succeeds.
  def unlink(self, clean=None):
    unlink(self._path)
    if clean is not None:
      parent = self.parent()
      parent._cleanup(clean)

  def rmdir(self, clean=None):
    rmdir(self._path)
    if clean is not None:
      parent = self.parent()
      parent._cleanup(clean)

  """Called on the parent of file or directory after a removal
  (if cleanup as asked for). Recuses cleanup until it reaches terminus.
  """
  def _cleanup(self, terminus):
    assert isinstance(terminus, Path)
    assert terminus in self.parents()
    if self == terminus:
      return
    if len(list(self.dir_gen())) == 0:
      self.rmdir(terminus)

  def islink(self):
    return islink(self._path)

  def isdir(self):
    return isdir(self._path)

  def isfile(self):
    return isfile(self._path)

  def checksum(self):
    hasher = md5()
    with self.open('rb') as fd:
      buf = fd.read(_BLOCKSIZE)
      while len(buf) > 0:
        hasher.update(buf)
        buf = fd.read(_BLOCKSIZE)
      return hasher.hexdigest()

  def __cmp__(self, other):
    assert isinstance(other, Path)
    self_parts = self._path.split(sep)
    other_parts = other._path.split(sep)
    return cmp(self_parts, other_parts)

  def __hash__(self):
    return hash(self._path)

  def join(self, child):
    assert isinstance(child, basestring)
    output = Path( self._path + sep + child)
    return output

  #TODO Should this also be able to generate raw basestrings?
  """Generates the set of Paths under this directory"""
  def dir_gen(self):
    assert self.isdir(), "%s is not a directory" % self._path
    names = listdir(self._path)
    for name in names:
      child = self.join(name)
      yield child

  def entries(self, exclude=[]):
    if isinstance(exclude, Path):
      exclude = [exclude]
    for excluded in exclude:
      assert isinstance(excluded, Path)
    if self in exclude:
      pass
    elif self.islink():
      yield (self, "link")
    elif self.isfile():
      yield (self, "file")
    elif self.isdir():
      yield (self, "dir")
      for dir_entry in self.dir_gen():
        for x in dir_entry.entries(exclude):
          yield x
    else:
      raise ValueError("%s is not a file/dir/link" % self)

  def open(self, mode):
    return open(self._path, mode)

  def stat(self):
    return stat(self._path)

  def chmod(self, mode):
    return chmod(self._path, mode)

@returned(bool)
@typed(Path)
def target_exists(link):
  assert link.islink()
  target = link.readlink(link.parent())
  return target.exists()

def find_in_seq(name, seq):
  assert isinstance(name, basestring)
  for i in seq:
    assert isinstance(i, Path)
    path = i.join(name)
    if path.exists():
      return path
  return None

@typed(Path)
def ensure_absent(path):
  if path.exists():
    if path.isdir():
      for child in path.dir_gen():
        ensure_absent(child)
      path.rmdir()
    else:
      path.unlink()
  else:
    pass # No work to do.

@typed(Path)
def ensure_dir(path):
  if path.exists():
    if path.isdir():
      pass # There is nothing to do.
    else:
      path.unlink()
      path.mkdir()
  else:
    assert path != _ROOT, "Path is root, which must be a directory"
    parent = path.parent()
    assert parent != path, "Path and parent were the same!"
    ensure_dir(parent)
    path.mkdir()

@typed(Path, Path)
def ensure_link(path, orig):
  assert orig.exists()
  parent = path.parent()
  assert parent != path, "Path and parent were the same!"
  ensure_dir(parent)
  ensure_absent(path)
  path.link(orig)

@typed(Path)
def ensure_readonly(path):
  mode = path.stat().st_mode
  read_only = mode & ~statc.S_IWUSR & ~statc.S_IWGRP & ~statc.S_IWOTH
  path.chmod(read_only)

@typed(Path, Path)
def ensure_copy(path, orig):
  assert orig.exists()
  parent = path.parent()
  assert parent != path, "Path and parent were the same!"
  ensure_dir(parent)
  ensure_absent(path)
  print "***", path, orig
  orig.copy(path)

@typed(Path, Path)
def ensure_symlink(path, orig):
  assert orig.exists()
  ensure_symlink_unsafe(path, orig._path)

@typed(Path, basestring)
def ensure_symlink_unsafe(path, orig):
  parent = path.parent()
  assert parent != path, "Path and parent were the same!"
  ensure_dir(parent)
  ensure_absent(path)
  symlink(orig, path._path)

"""
Creates/Deletes directories. Does whatever is required inorder
to make and open a file with the mode previded.

Mode settings to consider are:
 O_CREAT         create file if it does not exist
 O_TRUNC         truncate size to 0
 O_EXCL          error if O_CREAT and the file exists
"""
def ensure_file(path, mode):
  assert isinstance(path, Path)
  parent = path.parent()
  assert parent != path, "Path and parent were the same!"
  ensure_dir(parent)
  fd = path.open(mode)
  return fd

_ROOT = Path(sep)

<commit_msg>Make frame optional to readlink. Now we have a way of saying that we don’t have an inherit frame.  Relative links will cause exception to be thrown. <commit_after>from os import mkdir
from os import listdir
from os import link
from os import unlink
from os import symlink
from os import readlink
from os import rmdir
from os import stat
from os import chmod
from errno import EEXIST as FileExists
from errno import EISDIR as DirectoryExists
from hashlib import md5
from os.path import stat as statc
from os.path import normpath
from os.path import split
from os.path import isabs
from os.path import exists
from os.path import isdir
from shutil import copyfile
from os.path import isfile, islink, sep
from func_prototypes import typed, returned

_BLOCKSIZE = 65536

@returned(basestring)
@typed(basestring)
def _decodePath(name):
  if type(name) == str: # leave unicode ones alone
    try:
      name = name.decode('utf8')
    except:
      name = name.decode('windows-1252')
  return name

class Path:
  def __init__(self, path, frame=None):
    if isinstance(path, basestring):
      if isabs(path):
        self._path = normpath(path)
      else:
        if frame is not None:
          assert isinstance(frame, Path)
          self._path = frame.join(path)._path
        else:
          raise ValueError("Frame is required when building relative paths: %s" % path)
    else:
      self._path = path._path

  def __unicode__(self):
    return u'%s' % self._path

  def __str__(self):
    return unicode(self).encode('utf-8')

  def __repr__(self):
    return str(self)

  def mkdir(self):
    try:
      mkdir(self._path)
    except OSError as e:
      if e.errno == FileExists:
        pass
      elif e.errno == DirectoryExists:
        pass
      else:
        raise e

  # Returns the parent of self. If self is root ('/'), parent returns None.
  # You much check the output of parent before using the value.
  # Notcie that parent of root in the shell is '/', so this is a semantic difference
  # between us and POSIX.
  def parent(self):
    if self._path == sep:
      return None
    else:
      return Path(split(self._path)[0])

  def parents(self):
    paths = [self]
    path = self
    parent = path.parent()
    while parent is not None:
      paths.append(parent)
      path = parent
      parent = path.parent()
    return reversed(paths)

  def relative_to(self, relative, leading_sep=True):
    assert isinstance(relative, Path)
    if leading_sep == True:
      prefix = sep
    else:
      prefix = ""
    self_parents = list(self.parents())
    if relative in self_parents:
      relative_str = relative._path
      return prefix + self._path[len(relative_str)+1:]
    relative_parents = list(reversed(list(relative.parents())))
    if self in relative_parents:
      backups = relative_parents.index(self) - 1
      assert backups >= 0
      assert leading_sep == False, "Leading seperator is meaningless with backtracking"
      return "/".join([".."]*backups)
    raise ValueError("Relationship between %s and %s is complex" % (self, relative))

  def exists(self):
    return exists(self._path)

  def readlink(self, frame=None):
    return Path(readlink(self._path), frame)

  def link(self, dst):
    assert isinstance(dst, Path)
    link(dst._path, self._path)

  def symlink(self, dst):
    assert isinstance(dst, Path)
    symlink(dst._path, self._path)

  def copy(self, dst):
    assert isinstance(dst, Path)
    copyfile(self._path, dst._path)

  #TODO I'd like to have a way of cleaning up
  # empty directories recursvely, only if this
  # unlink succeeds.
  def unlink(self, clean=None):
    unlink(self._path)
    if clean is not None:
      parent = self.parent()
      parent._cleanup(clean)

  def rmdir(self, clean=None):
    rmdir(self._path)
    if clean is not None:
      parent = self.parent()
      parent._cleanup(clean)

  """Called on the parent of file or directory after a removal
  (if cleanup as asked for). Recuses cleanup until it reaches terminus.
  """
  def _cleanup(self, terminus):
    assert isinstance(terminus, Path)
    assert terminus in self.parents()
    if self == terminus:
      return
    if len(list(self.dir_gen())) == 0:
      self.rmdir(terminus)

  def islink(self):
    return islink(self._path)

  def isdir(self):
    return isdir(self._path)

  def isfile(self):
    return isfile(self._path)

  def checksum(self):
    hasher = md5()
    with self.open('rb') as fd:
      buf = fd.read(_BLOCKSIZE)
      while len(buf) > 0:
        hasher.update(buf)
        buf = fd.read(_BLOCKSIZE)
      return hasher.hexdigest()

  def __cmp__(self, other):
    assert isinstance(other, Path)
    self_parts = self._path.split(sep)
    other_parts = other._path.split(sep)
    return cmp(self_parts, other_parts)

  def __hash__(self):
    return hash(self._path)

  def join(self, child):
    assert isinstance(child, basestring)
    output = Path( self._path + sep + child)
    return output

  #TODO Should this also be able to generate raw basestrings?
  """Generates the set of Paths under this directory"""
  def dir_gen(self):
    assert self.isdir(), "%s is not a directory" % self._path
    names = listdir(self._path)
    for name in names:
      child = self.join(name)
      yield child

  def entries(self, exclude=[]):
    if isinstance(exclude, Path):
      exclude = [exclude]
    for excluded in exclude:
      assert isinstance(excluded, Path)
    if self in exclude:
      pass
    elif self.islink():
      yield (self, "link")
    elif self.isfile():
      yield (self, "file")
    elif self.isdir():
      yield (self, "dir")
      for dir_entry in self.dir_gen():
        for x in dir_entry.entries(exclude):
          yield x
    else:
      raise ValueError("%s is not a file/dir/link" % self)

  def open(self, mode):
    return open(self._path, mode)

  def stat(self):
    return stat(self._path)

  def chmod(self, mode):
    return chmod(self._path, mode)

@returned(bool)
@typed(Path)
def target_exists(link):
  assert link.islink()
  target = link.readlink(link.parent())
  return target.exists()

def find_in_seq(name, seq):
  assert isinstance(name, basestring)
  for i in seq:
    assert isinstance(i, Path)
    path = i.join(name)
    if path.exists():
      return path
  return None

@typed(Path)
def ensure_absent(path):
  if path.exists():
    if path.isdir():
      for child in path.dir_gen():
        ensure_absent(child)
      path.rmdir()
    else:
      path.unlink()
  else:
    pass # No work to do.

@typed(Path)
def ensure_dir(path):
  if path.exists():
    if path.isdir():
      pass # There is nothing to do.
    else:
      path.unlink()
      path.mkdir()
  else:
    assert path != _ROOT, "Path is root, which must be a directory"
    parent = path.parent()
    assert parent != path, "Path and parent were the same!"
    ensure_dir(parent)
    path.mkdir()

@typed(Path, Path)
def ensure_link(path, orig):
  assert orig.exists()
  parent = path.parent()
  assert parent != path, "Path and parent were the same!"
  ensure_dir(parent)
  ensure_absent(path)
  path.link(orig)

@typed(Path)
def ensure_readonly(path):
  mode = path.stat().st_mode
  read_only = mode & ~statc.S_IWUSR & ~statc.S_IWGRP & ~statc.S_IWOTH
  path.chmod(read_only)

@typed(Path, Path)
def ensure_copy(path, orig):
  assert orig.exists()
  parent = path.parent()
  assert parent != path, "Path and parent were the same!"
  ensure_dir(parent)
  ensure_absent(path)
  print "***", path, orig
  orig.copy(path)

@typed(Path, Path)
def ensure_symlink(path, orig):
  assert orig.exists()
  ensure_symlink_unsafe(path, orig._path)

@typed(Path, basestring)
def ensure_symlink_unsafe(path, orig):
  parent = path.parent()
  assert parent != path, "Path and parent were the same!"
  ensure_dir(parent)
  ensure_absent(path)
  symlink(orig, path._path)

"""
Creates/Deletes directories. Does whatever is required inorder
to make and open a file with the mode previded.

Mode settings to consider are:
 O_CREAT         create file if it does not exist
 O_TRUNC         truncate size to 0
 O_EXCL          error if O_CREAT and the file exists
"""
def ensure_file(path, mode):
  assert isinstance(path, Path)
  parent = path.parent()
  assert parent != path, "Path and parent were the same!"
  ensure_dir(parent)
  fd = path.open(mode)
  return fd

_ROOT = Path(sep)

<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import sys
from collections import namedtuple
from contextlib import contextmanager
from keyword import iskeyword

from . import buffering
from .ast import AST
from .exceptions import (FailedCut, FailedLookahead, FailedParse,
                         FailedSemantics, OptionSucceeded)

__all__ = ['ParseInfo', 'ParseContext']

ParseInfo = namedtuple('ParseInfo', ['buffer', 'rule', 'pos', 'endpos'])

def safe_name(s):
    if iskeyword(s):
        return s + '_'
    return s

def is_list(o):
    return type(o) == list

class Closure(list):
    pass

class ParseContext(object):
    def __init__(self,
                 semantics=None,
                 parseinfo=False,
                 trace=False,
                 encoding='utf-8',
                 comments_re=None,
                 whitespace=None,
                 ignorecase=False,
                 nameguard=True,
                 memoize_lookaheads=True,
                 **kwargs):
        super(ParseContext, self).__init__()

        self._buffer = None
        self.semantics = semantics
        self.encoding = encoding
        self.parseinfo = parseinfo
        self.trace = trace

        self.comments_re = comments_re
        self.whitespace = whitespace
        self.ignorecase = ignorecase
        self.nameguard = nameguard

        self._ast_stack = [AST()]
        self._concrete_stack = [None]
        self._rule_stack = []
        self._cut_stack = [False]
        self._memoization_cache = dict()
        self._last_node = None
        self._state = None
        self._lookahead = 0
        self._memoize_lookaheads = memoize_lookaheads

    def _clear_cache(self):
        self._memoization_cache = dict()

    def _reset(self, text=None,
               filename=None,
               semantics=None,
               trace=None,
               comments_re=None,
               whitespace=None,
               ignorecase=None,
               nameguard=None,
               memoize_lookaheads=None,
              **kwargs):
        if ignorecase is None:
            ignorecase = self.ignorecase
        if nameguard is None:
            nameguard = self.nameguard
        if memoize_lookaheads is not None:
            self._memoize_lookaheads = memoize_lookaheads
        if isinstance(text, buffering.Buffer):
            buffer = text
        else:

            buffer = buffering.Buffer(text,
                                      filename=filename,
                                      comments_re=comments_re or self.comments_re,
                                      whitespace=whitespace or self.whitespace,
                                      ignorecase=ignorecase,
                                      nameguard=nameguard,
                                      **kwargs)
        self._buffer = buffer
        if trace is not None:
            self.trace = trace
        if semantics is not None:
            self.semantics = semantics
        self._ast_stack = [AST()]
        self._concrete_stack = [None]
        self._rule_stack = []
        self._cut_stack = [False]
        self._memoization_cache = dict()
        if semantics is not None:
            self.semantics = semantics

    def goto(self, pos):
        self._buffer.goto(pos)

    @property
    def last_node(self):
        return self._last_node

    @last_node.setter
    def last_node(self, value):
        self._last_node = value

    @property
    def _pos(self):
        return self._buffer.pos

    def _goto(self, pos):
        self._buffer.goto(pos)

    def _next_token(self):
        self._buffer.next_token()

    @property
    def ast(self):
        return self._ast_stack[-1]

    @ast.setter
    def ast(self, value):
        self._ast_stack[-1] = value

    def _push_ast(self):
        self._push_cst()
        self._ast_stack.append(AST())

    def _pop_ast(self):
        self._pop_cst()
        return self._ast_stack.pop()

    def _add_ast_node(self, name, node, force_list=False):
        if name is not None:  # and node:
            self.ast.add(name, node, force_list)
        return node

    @property
    def cst(self):
        return self._concrete_stack[-1]

    @cst.setter
    def cst(self, value):
        self._concrete_stack[-1] = value

    def _push_cst(self):
        self._concrete_stack.append(None)

    def _pop_cst(self):
        return self._concrete_stack.pop()

    def _add_cst_node(self, node):
        if node is None:
            return
        previous = self.cst
        if previous is None:
            if is_list(node):
                node = node[:]  # copy it
            self.cst = node
        elif is_list(previous):
            previous.append(node)
        else:
            self.cst = [previous, node]

    def _extend_cst(self, node):
        if node is None:
            return
        previous = self.cst
        if previous is None:
            if isinstance(node, list):
                node = node[:]  # copy it
            self.cst = node
        elif is_list(node):
            if is_list(previous):
                previous.extend(node)
            else:
                self.cst = [previous] + node
        elif is_list(previous):
            previous.append(node)
        else:
            self.cst = [previous, node]

    def _copy_cst(self):
        cst = self.cst
        if cst is None:
            return None
        elif isinstance(cst, list):
            return cst[:]
        else:
            return cst

    def _is_cut_set(self):
        return self._cut_stack[-1]

    def _cut(self):
        self._cut_stack[-1] = True

        # Kota Mizushima et al say that we can throw away
        # memos for previous positions in the buffer under
        # certain circumstances, without affecting the linearity
        # of PEG parsing.
        #   http://goo.gl/VaGpj
        #
        # We adopt the heuristic of always dropping the cache for
        # positions less than the current cut position. It remains to
        # be proven if doing it this way affects linearity. Empirically,
        # it hasn't.
        cutpos = self._pos
        cache = self._memoization_cache
        cutkeys = [(p, n, s) for p, n, s in cache.keys() if p < cutpos]
        for key in cutkeys:
            del cache[key]

    def _push_cut(self):
        self._cut_stack.append(False)

    def _pop_cut(self):
        return self._cut_stack.pop()

    def _enter_lookahead(self):
        self._lookahead += 1

    def _leave_lookahead(self):
        self._lookahead -= 1

    def _memoize_lookahead(self):
        return self._memoize_lookaheads or self._lookahead == 0

    def _rulestack(self):
        stack = '.'.join(self._rule_stack)
        if len(stack) > 60:
            stack = '...' + stack[-60:]
        return stack

    def _find_rule(self, name):
        return None

    def _find_semantic_rule(self, name):
        if self.semantics is None:
            return None

        result = getattr(self.semantics, name, None)
        if callable(result):
            return result

        result = getattr(self.semantics, '_default', None)
        if callable(result):
            return result

    def _trace(self, msg, *params):
        if self.trace:
            msg = msg % params
            print(msg.encode('utf-8'), file=sys.stderr)

    def _trace_event(self, event):
        if self.trace:
            self._trace('%s \n%s   \n%s \n\t%s \n',
                        event,
                        self._buffer.line_info().filename,
                        self._rulestack(),
                        self._buffer.lookahead()
                        )

    def _trace_match(self, token, name=None):
        if self.trace:
            name = name if name else ''
            self._trace('MATCHED <%s> /%s/\n\t%s', token, name, self._buffer.lookahead())

    def _error(self, item, etype=FailedParse):
        raise etype(self._buffer, item)

    def _fail(self):
        self._error('fail')

    def _call(self, rule, name, *params, **kwparams):
        self._rule_stack.append(name)
        pos = self._pos
        try:
            self._trace_event('ENTER ')
            self._last_node = None
            node, newpos, newstate = self._invoke_rule(rule, name, *params, **kwparams)
            self._goto(newpos)
            self._state = newstate
            self._trace_event('SUCCESS')
            self._add_cst_node(node)
            self._last_node = node
            return node
        except FailedParse:
            self._trace_event('FAILED')
            self._goto(pos)
            raise
        finally:
            self._rule_stack.pop()

    def _invoke_rule(self, rule, name, *params, **kwparams):
        pos = self._pos
        state = self._state
        key = (pos, rule, state)
        cache = self._memoization_cache

        if key in cache:
            result = cache[key]
            if isinstance(result, Exception):
                raise result
            return result

        self._push_ast()
        try:
            if name[0].islower():
                self._next_token()
            rule(self)
            node = self.ast
            if not node:
                node = self.cst
            elif '@' in node:
                node = node['@']  # override the AST
            elif self.parseinfo:
                node.add('_parseinfo', ParseInfo(self._buffer, name, pos, self._pos))
            semantic_rule = self._find_semantic_rule(name)
            if semantic_rule:
                try:
                    node = semantic_rule(node, *params, **kwparams)
                except FailedSemantics as e:
                    self._error(str(e), FailedParse)
            result = (node, self._pos, self._state)
            if self._memoize_lookahead():
                cache[key] = result
            return result
        except Exception as e:
            if self._memoize_lookahead():
                cache[key] = e
            raise
        finally:
            self._pop_ast()

    @contextmanager
    def _try(self):
        p = self._pos
        s = self._state
        ast_copy = self.ast.copy()
        self._push_ast()
        self.last_node = None
        try:
            self.ast = ast_copy
            yield
            ast = self.ast
            cst = self.cst
        except:
            self._goto(p)
            self._state = s
            raise
        finally:
            self._pop_ast()
        self.ast = ast
        self._extend_cst(cst)
        self.last_node = cst

    @contextmanager
    def _option(self):
        self.last_node = None
        self._push_cut()
        try:
            with self._try():
                yield
            raise OptionSucceeded()
        except FailedCut:
            raise
        except FailedParse as e:
            if self._is_cut_set():
                raise FailedCut(e)
        finally:
            self._pop_cut()

    @contextmanager
    def _choice(self):
        self.last_node = None
        with self._try():
            try:
                yield
            except OptionSucceeded:
                pass

    @contextmanager
    def _optional(self):
        self.last_node = None
        with self._choice():
            with self._option():
                yield

    @contextmanager
    def _group(self):
        self._push_cst()
        try:
            yield
            cst = self.cst
        finally:
            self._pop_cst()
        self._extend_cst(cst)
        self.last_node = cst

    @contextmanager
    def _if(self):
        p = self._pos
        s = self._state
        self._push_ast()
        self._enter_lookahead()
        try:
            yield
        finally:
            self._leave_lookahead()
            self._goto(p)
            self._state = s
            self._pop_ast()  # simply discard
            self.last_node = None

    @contextmanager
    def _ifnot(self):
        try:
            with self._if():
                yield
        except FailedParse:
            pass
        else:
            self._error('', etype=FailedLookahead)

    def _repeater(self, f):
        while True:
            self._push_cut()
            try:
                p = self._pos
                with self._try():
                    f()
                if self._pos == p:
                    self._error('empty closure')
            except FailedCut:
                raise
            except FailedParse as e:
                if self._is_cut_set():
                    raise FailedCut(e)
                break
            finally:
                self._pop_cut()

    def _closure(self, block):
        self._push_cst()
        try:
            self.cst = []
            self._repeater(block)
            cst = Closure(self.cst)
        finally:
            self._pop_cst()
        self._add_cst_node(cst)
        self.last_node = cst
        return cst

    def _positive_closure(self, block):
        self._push_cst()
        try:
            self.cst = []
            with self._try():
                block()
            self._repeater(block)
            cst = Closure(self.cst)
        finally:
            self._pop_cst()
        self._add_cst_node(cst)
        self.last_node = cst
        return cst
<commit_msg>Fix trace output in Python 3.4.<commit_after># -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import sys
from collections import namedtuple
from contextlib import contextmanager
from keyword import iskeyword

from . import buffering
from .ast import AST
from .exceptions import (FailedCut, FailedLookahead, FailedParse,
                         FailedSemantics, OptionSucceeded)

__all__ = ['ParseInfo', 'ParseContext']

ParseInfo = namedtuple('ParseInfo', ['buffer', 'rule', 'pos', 'endpos'])

def safe_name(s):
    if iskeyword(s):
        return s + '_'
    return s

def is_list(o):
    return type(o) == list

class Closure(list):
    pass

class ParseContext(object):
    def __init__(self,
                 semantics=None,
                 parseinfo=False,
                 trace=False,
                 encoding='utf-8',
                 comments_re=None,
                 whitespace=None,
                 ignorecase=False,
                 nameguard=True,
                 memoize_lookaheads=True,
                 **kwargs):
        super(ParseContext, self).__init__()

        self._buffer = None
        self.semantics = semantics
        self.encoding = encoding
        self.parseinfo = parseinfo
        self.trace = trace

        self.comments_re = comments_re
        self.whitespace = whitespace
        self.ignorecase = ignorecase
        self.nameguard = nameguard

        self._ast_stack = [AST()]
        self._concrete_stack = [None]
        self._rule_stack = []
        self._cut_stack = [False]
        self._memoization_cache = dict()
        self._last_node = None
        self._state = None
        self._lookahead = 0
        self._memoize_lookaheads = memoize_lookaheads

    def _clear_cache(self):
        self._memoization_cache = dict()

    def _reset(self, text=None,
               filename=None,
               semantics=None,
               trace=None,
               comments_re=None,
               whitespace=None,
               ignorecase=None,
               nameguard=None,
               memoize_lookaheads=None,
              **kwargs):
        if ignorecase is None:
            ignorecase = self.ignorecase
        if nameguard is None:
            nameguard = self.nameguard
        if memoize_lookaheads is not None:
            self._memoize_lookaheads = memoize_lookaheads
        if isinstance(text, buffering.Buffer):
            buffer = text
        else:

            buffer = buffering.Buffer(text,
                                      filename=filename,
                                      comments_re=comments_re or self.comments_re,
                                      whitespace=whitespace or self.whitespace,
                                      ignorecase=ignorecase,
                                      nameguard=nameguard,
                                      **kwargs)
        self._buffer = buffer
        if trace is not None:
            self.trace = trace
        if semantics is not None:
            self.semantics = semantics
        self._ast_stack = [AST()]
        self._concrete_stack = [None]
        self._rule_stack = []
        self._cut_stack = [False]
        self._memoization_cache = dict()
        if semantics is not None:
            self.semantics = semantics

    def goto(self, pos):
        self._buffer.goto(pos)

    @property
    def last_node(self):
        return self._last_node

    @last_node.setter
    def last_node(self, value):
        self._last_node = value

    @property
    def _pos(self):
        return self._buffer.pos

    def _goto(self, pos):
        self._buffer.goto(pos)

    def _next_token(self):
        self._buffer.next_token()

    @property
    def ast(self):
        return self._ast_stack[-1]

    @ast.setter
    def ast(self, value):
        self._ast_stack[-1] = value

    def _push_ast(self):
        self._push_cst()
        self._ast_stack.append(AST())

    def _pop_ast(self):
        self._pop_cst()
        return self._ast_stack.pop()

    def _add_ast_node(self, name, node, force_list=False):
        if name is not None:  # and node:
            self.ast.add(name, node, force_list)
        return node

    @property
    def cst(self):
        return self._concrete_stack[-1]

    @cst.setter
    def cst(self, value):
        self._concrete_stack[-1] = value

    def _push_cst(self):
        self._concrete_stack.append(None)

    def _pop_cst(self):
        return self._concrete_stack.pop()

    def _add_cst_node(self, node):
        if node is None:
            return
        previous = self.cst
        if previous is None:
            if is_list(node):
                node = node[:]  # copy it
            self.cst = node
        elif is_list(previous):
            previous.append(node)
        else:
            self.cst = [previous, node]

    def _extend_cst(self, node):
        if node is None:
            return
        previous = self.cst
        if previous is None:
            if isinstance(node, list):
                node = node[:]  # copy it
            self.cst = node
        elif is_list(node):
            if is_list(previous):
                previous.extend(node)
            else:
                self.cst = [previous] + node
        elif is_list(previous):
            previous.append(node)
        else:
            self.cst = [previous, node]

    def _copy_cst(self):
        cst = self.cst
        if cst is None:
            return None
        elif isinstance(cst, list):
            return cst[:]
        else:
            return cst

    def _is_cut_set(self):
        return self._cut_stack[-1]

    def _cut(self):
        self._cut_stack[-1] = True

        # Kota Mizushima et al say that we can throw away
        # memos for previous positions in the buffer under
        # certain circumstances, without affecting the linearity
        # of PEG parsing.
        #   http://goo.gl/VaGpj
        #
        # We adopt the heuristic of always dropping the cache for
        # positions less than the current cut position. It remains to
        # be proven if doing it this way affects linearity. Empirically,
        # it hasn't.
        cutpos = self._pos
        cache = self._memoization_cache
        cutkeys = [(p, n, s) for p, n, s in cache.keys() if p < cutpos]
        for key in cutkeys:
            del cache[key]

    def _push_cut(self):
        self._cut_stack.append(False)

    def _pop_cut(self):
        return self._cut_stack.pop()

    def _enter_lookahead(self):
        self._lookahead += 1

    def _leave_lookahead(self):
        self._lookahead -= 1

    def _memoize_lookahead(self):
        return self._memoize_lookaheads or self._lookahead == 0

    def _rulestack(self):
        stack = '.'.join(self._rule_stack)
        if len(stack) > 60:
            stack = '...' + stack[-60:]
        return stack

    def _find_rule(self, name):
        return None

    def _find_semantic_rule(self, name):
        if self.semantics is None:
            return None

        result = getattr(self.semantics, name, None)
        if callable(result):
            return result

        result = getattr(self.semantics, '_default', None)
        if callable(result):
            return result

    def _trace(self, msg, *params):
        if self.trace:
            msg = msg % params
            print(msg, file=sys.stderr)

    def _trace_event(self, event):
        if self.trace:
            self._trace('%s \n%s   \n%s \n\t%s \n',
                        event,
                        self._buffer.line_info().filename,
                        self._rulestack(),
                        self._buffer.lookahead()
                        )

    def _trace_match(self, token, name=None):
        if self.trace:
            name = name if name else ''
            self._trace('MATCHED <%s> /%s/\n\t%s', token, name, self._buffer.lookahead())

    def _error(self, item, etype=FailedParse):
        raise etype(self._buffer, item)

    def _fail(self):
        self._error('fail')

    def _call(self, rule, name, *params, **kwparams):
        self._rule_stack.append(name)
        pos = self._pos
        try:
            self._trace_event('ENTER ')
            self._last_node = None
            node, newpos, newstate = self._invoke_rule(rule, name, *params, **kwparams)
            self._goto(newpos)
            self._state = newstate
            self._trace_event('SUCCESS')
            self._add_cst_node(node)
            self._last_node = node
            return node
        except FailedParse:
            self._trace_event('FAILED')
            self._goto(pos)
            raise
        finally:
            self._rule_stack.pop()

    def _invoke_rule(self, rule, name, *params, **kwparams):
        pos = self._pos
        state = self._state
        key = (pos, rule, state)
        cache = self._memoization_cache

        if key in cache:
            result = cache[key]
            if isinstance(result, Exception):
                raise result
            return result

        self._push_ast()
        try:
            if name[0].islower():
                self._next_token()
            rule(self)
            node = self.ast
            if not node:
                node = self.cst
            elif '@' in node:
                node = node['@']  # override the AST
            elif self.parseinfo:
                node.add('_parseinfo', ParseInfo(self._buffer, name, pos, self._pos))
            semantic_rule = self._find_semantic_rule(name)
            if semantic_rule:
                try:
                    node = semantic_rule(node, *params, **kwparams)
                except FailedSemantics as e:
                    self._error(str(e), FailedParse)
            result = (node, self._pos, self._state)
            if self._memoize_lookahead():
                cache[key] = result
            return result
        except Exception as e:
            if self._memoize_lookahead():
                cache[key] = e
            raise
        finally:
            self._pop_ast()

    @contextmanager
    def _try(self):
        p = self._pos
        s = self._state
        ast_copy = self.ast.copy()
        self._push_ast()
        self.last_node = None
        try:
            self.ast = ast_copy
            yield
            ast = self.ast
            cst = self.cst
        except:
            self._goto(p)
            self._state = s
            raise
        finally:
            self._pop_ast()
        self.ast = ast
        self._extend_cst(cst)
        self.last_node = cst

    @contextmanager
    def _option(self):
        self.last_node = None
        self._push_cut()
        try:
            with self._try():
                yield
            raise OptionSucceeded()
        except FailedCut:
            raise
        except FailedParse as e:
            if self._is_cut_set():
                raise FailedCut(e)
        finally:
            self._pop_cut()

    @contextmanager
    def _choice(self):
        self.last_node = None
        with self._try():
            try:
                yield
            except OptionSucceeded:
                pass

    @contextmanager
    def _optional(self):
        self.last_node = None
        with self._choice():
            with self._option():
                yield

    @contextmanager
    def _group(self):
        self._push_cst()
        try:
            yield
            cst = self.cst
        finally:
            self._pop_cst()
        self._extend_cst(cst)
        self.last_node = cst

    @contextmanager
    def _if(self):
        p = self._pos
        s = self._state
        self._push_ast()
        self._enter_lookahead()
        try:
            yield
        finally:
            self._leave_lookahead()
            self._goto(p)
            self._state = s
            self._pop_ast()  # simply discard
            self.last_node = None

    @contextmanager
    def _ifnot(self):
        try:
            with self._if():
                yield
        except FailedParse:
            pass
        else:
            self._error('', etype=FailedLookahead)

    def _repeater(self, f):
        while True:
            self._push_cut()
            try:
                p = self._pos
                with self._try():
                    f()
                if self._pos == p:
                    self._error('empty closure')
            except FailedCut:
                raise
            except FailedParse as e:
                if self._is_cut_set():
                    raise FailedCut(e)
                break
            finally:
                self._pop_cut()

    def _closure(self, block):
        self._push_cst()
        try:
            self.cst = []
            self._repeater(block)
            cst = Closure(self.cst)
        finally:
            self._pop_cst()
        self._add_cst_node(cst)
        self.last_node = cst
        return cst

    def _positive_closure(self, block):
        self._push_cst()
        try:
            self.cst = []
            with self._try():
                block()
            self._repeater(block)
            cst = Closure(self.cst)
        finally:
            self._pop_cst()
        self._add_cst_node(cst)
        self.last_node = cst
        return cst
<|endoftext|>