<commit_before># -*- coding: utf-8 -*-
'''
:maintainer: Evan Borgstrom <evan@borgstrom.ca>

Python renderer that includes a Pythonic Object based interface

Let's take a look at how you use pyobjects in a state file. Here's a quick
example that ensures the ``/tmp`` directory is in the correct state.

.. code-block:: python
   :linenos:
    #!pyobjects

    File.managed("/tmp", user='root', group='root', mode='1777')

Nice and Pythonic!

By using the "shebang" syntax to switch to the pyobjects renderer we can now
write our state data using an object based interface that should feel at home
to python developers. You can import any module and do anything that you'd
like (with caution, importing sqlalchemy, django or other large frameworks has
not been tested yet). Using the pyobjects renderer is exactly the same as
using the built-in Python renderer with the exception that pyobjects takes
care of creating an object for each of the available states on the minion.
Each state is represented by an object that is the capitalized version of it's
name (ie. ``File``, ``Service``, ``User``, etc), and these objects expose all
of their available state functions (ie. ``File.managed``,  ``Service.running``,
etc).

Context Managers and requisites
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
How about something a little more complex. Here we're going to get into the
core of what makes pyobjects the best way to write states.

.. code-block:: python
   :linenos:
    #!pyobjects

    with Pkg.installed("nginx"):
        Service.running("nginx", enable=True)

        with Service("nginx", "watch_in"):
            File.managed("/etc/nginx/conf.d/mysite.conf",
                         owner='root', group='root', mode='0444',
                         source='salt://nginx/mysite.conf')

The objects that are returned from each of the magic method calls are setup to
be used a Python context managers (``with``) and when you use them as such all
declarations made within the scope will **automatically** use the enclosing
state as a requisite!

The above could have also been written use direct requisite statements as.

.. code-block:: python
   :linenos:
    #!pyobjects

    Pkg.installed("nginx")
    Service.running("nginx", enable=True, require=Pkg("nginx"))
    File.managed("/etc/nginx/conf.d/mysite.conf",
                 owner='root', group='root', mode='0444',
                 source='salt://nginx/mysite.conf',
                 watch_in=Service("nginx"))

You can use the direct requisite statement for referencing states that are
generated outside of the current file.

.. code-block:: python
   :linenos:
    #!pyobjects

    # some-other-package is defined in some other state file
    Pkg.installed("nginx", require=Pkg("some-other-package"))

The last thing that direct requisites provide is the ability to select which
of the SaltStack requisites you want to use (require, require_in, watch,
watch_in, use & use_in) when using the requisite as a context manager.

.. code-block:: python
   :linenos:
    #!pyobjects

    with Service("my-service", "watch_in"):
        ...

The above example would cause all declarations inside the scope of the context
manager to automatically have their ``watch_in`` set to
``Service("my-service")``.

Including and Extending
^^^^^^^^^^^^^^^^^^^^^^^

To include other states use the Include() function. It takes one name per
state to include.

To extend another state use the Extend() function on the name when creating
a state.

.. code-block:: python
   :linenos:
    #!pyobjects

    Include('http', 'ssh')

    Service.running(Extend('apache'),
                    watch=[{'file': '/etc/httpd/extra/httpd-vhosts.conf'}])

Pillar data
^^^^^^^^^^^
Pyobjects provides a shortcut function for calling ``salt['pillar.get']`` that
helps maintain the readability of your state files. It can be accessed using
the ``Pillar`` object.

The following lines are functionally equivalent:

.. code-block:: python
   :linenos:
    #!pyobjects

    value = Pillar('foo:bar:baz', 'qux')
    value = salt['pillar.get']('foo:bar:baz', 'qux')

SaltObject
^^^^^^^^^^
In the spirit of the object interface for creating state data pyobjects also
provides a simple object interface to the ``__salt__`` object.

An object named ``Salt`` exists in scope for your sls files and will dispatch
its attributes to the ``__salt__`` dictionary.

The following lines are functionally equivalent:

.. code-block:: python
   :linenos:
    #!pyobjects

    ret = Salt.cmd.run(bar)
    ret = salt['cmd.run'](bar)

TODO
^^^^
* Interface for working with reactor files
'''

import logging

from salt.loader import states
from salt.utils.pyobjects import StateRegistry, SaltObject
from salt.utils.pyobjects import StateFactory  # pylint: disable=W0611
# DO NOT REMOVE THE DISABLE ABOVE, it's used in an exec call below

log = logging.getLogger(__name__)

def render(template, saltenv='base', sls='',
           tmplpath=None, rendered_sls=None,
           _states=None, **kwargs):

    _registry = StateRegistry()
    if _states is None:
        _states = states(__opts__, __salt__)

    # build our list of states and functions
    _st_funcs = {}
    for func in _states:
        (mod, func) = func.split(".")
        if mod not in _st_funcs:
            _st_funcs[mod] = []
        _st_funcs[mod].append(func)

    # create our StateFactory objects
    for mod in _st_funcs:
        _st_funcs[mod].sort()
        mod_upper = mod.capitalize()
        mod_cmd = "%s = StateFactory('%s', registry=_registry, valid_funcs=['%s'])" % (
            mod_upper, mod,
            "','".join(_st_funcs[mod])
        )
        exec(mod_cmd)

    # add our Include and Extend functions
    Include = _registry.include
    Extend = _registry.make_extend

    # for convenience
    try:
        pillar = __pillar__
        grains = __grains__
        salt = __salt__

        # create our SaltObject
        Salt = SaltObject(__salt__)

        # add a Pillar shortcut
        Pillar = __salt__['pillar.get']
    except NameError:
        pass

    exec(template.read())

    return _registry.salt_data()
<commit_msg>Ensure the scope of the exec calls is limited for both globals & locals<commit_after># -*- coding: utf-8 -*-
'''
:maintainer: Evan Borgstrom <evan@borgstrom.ca>

Python renderer that includes a Pythonic Object based interface

Let's take a look at how you use pyobjects in a state file. Here's a quick
example that ensures the ``/tmp`` directory is in the correct state.

.. code-block:: python
   :linenos:
    #!pyobjects

    File.managed("/tmp", user='root', group='root', mode='1777')

Nice and Pythonic!

By using the "shebang" syntax to switch to the pyobjects renderer we can now
write our state data using an object based interface that should feel at home
to python developers. You can import any module and do anything that you'd
like (with caution, importing sqlalchemy, django or other large frameworks has
not been tested yet). Using the pyobjects renderer is exactly the same as
using the built-in Python renderer with the exception that pyobjects takes
care of creating an object for each of the available states on the minion.
Each state is represented by an object that is the capitalized version of it's
name (ie. ``File``, ``Service``, ``User``, etc), and these objects expose all
of their available state functions (ie. ``File.managed``,  ``Service.running``,
etc).

Context Managers and requisites
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
How about something a little more complex. Here we're going to get into the
core of what makes pyobjects the best way to write states.

.. code-block:: python
   :linenos:
    #!pyobjects

    with Pkg.installed("nginx"):
        Service.running("nginx", enable=True)

        with Service("nginx", "watch_in"):
            File.managed("/etc/nginx/conf.d/mysite.conf",
                         owner='root', group='root', mode='0444',
                         source='salt://nginx/mysite.conf')

The objects that are returned from each of the magic method calls are setup to
be used a Python context managers (``with``) and when you use them as such all
declarations made within the scope will **automatically** use the enclosing
state as a requisite!

The above could have also been written use direct requisite statements as.

.. code-block:: python
   :linenos:
    #!pyobjects

    Pkg.installed("nginx")
    Service.running("nginx", enable=True, require=Pkg("nginx"))
    File.managed("/etc/nginx/conf.d/mysite.conf",
                 owner='root', group='root', mode='0444',
                 source='salt://nginx/mysite.conf',
                 watch_in=Service("nginx"))

You can use the direct requisite statement for referencing states that are
generated outside of the current file.

.. code-block:: python
   :linenos:
    #!pyobjects

    # some-other-package is defined in some other state file
    Pkg.installed("nginx", require=Pkg("some-other-package"))

The last thing that direct requisites provide is the ability to select which
of the SaltStack requisites you want to use (require, require_in, watch,
watch_in, use & use_in) when using the requisite as a context manager.

.. code-block:: python
   :linenos:
    #!pyobjects

    with Service("my-service", "watch_in"):
        ...

The above example would cause all declarations inside the scope of the context
manager to automatically have their ``watch_in`` set to
``Service("my-service")``.

Including and Extending
^^^^^^^^^^^^^^^^^^^^^^^

To include other states use the Include() function. It takes one name per
state to include.

To extend another state use the Extend() function on the name when creating
a state.

.. code-block:: python
   :linenos:
    #!pyobjects

    Include('http', 'ssh')

    Service.running(Extend('apache'),
                    watch=[{'file': '/etc/httpd/extra/httpd-vhosts.conf'}])

Pillar data
^^^^^^^^^^^
Pyobjects provides a shortcut function for calling ``salt['pillar.get']`` that
helps maintain the readability of your state files. It can be accessed using
the ``Pillar`` object.

The following lines are functionally equivalent:

.. code-block:: python
   :linenos:
    #!pyobjects

    value = Pillar('foo:bar:baz', 'qux')
    value = salt['pillar.get']('foo:bar:baz', 'qux')

SaltObject
^^^^^^^^^^
In the spirit of the object interface for creating state data pyobjects also
provides a simple object interface to the ``__salt__`` object.

An object named ``Salt`` exists in scope for your sls files and will dispatch
its attributes to the ``__salt__`` dictionary.

The following lines are functionally equivalent:

.. code-block:: python
   :linenos:
    #!pyobjects

    ret = Salt.cmd.run(bar)
    ret = salt['cmd.run'](bar)

TODO
^^^^
* Interface for working with reactor files
'''

import logging
import sys

from salt.loader import states
from salt.utils.pyobjects import StateRegistry, SaltObject
from salt.utils.pyobjects import StateFactory  # pylint: disable=W0611
# DO NOT REMOVE THE DISABLE ABOVE, it's used in an exec call below

log = logging.getLogger(__name__)

def render(template, saltenv='base', sls='',
           tmplpath=None, rendered_sls=None,
           _states=None, **kwargs):

    _globals = {}
    _locals = {}

    _registry = StateRegistry()
    if _states is None:
        _states = states(__opts__, __salt__)

    # build our list of states and functions
    _st_funcs = {}
    for func in _states:
        (mod, func) = func.split(".")
        if mod not in _st_funcs:
            _st_funcs[mod] = []
        _st_funcs[mod].append(func)

    # create our StateFactory objects
    _st_globals = {'StateFactory': StateFactory, '_registry': _registry}
    for mod in _st_funcs:
        _st_locals = {}
        _st_funcs[mod].sort()
        mod_upper = mod.capitalize()
        mod_cmd = "%s = StateFactory('%s', registry=_registry, valid_funcs=['%s'])" % (
            mod_upper, mod,
            "','".join(_st_funcs[mod])
        )
        if sys.version > 3:
            exec(mod_cmd, _st_globals, _st_locals)
        else:
            exec mod_cmd in _st_globals, _st_locals
        _globals[mod_upper] = _st_locals[mod_upper]

    # add our Include and Extend functions
    _globals['Include'] = _registry.include
    _globals['Extend'] = _registry.make_extend

    # for convenience
    try:
        _globals.update({
            # salt, pillar & grains all provide shortcuts or object interfaces
            'salt': SaltObject(__salt__),
            'pillar': __salt__['pillar.get'],
            'grains': __salt__['grains.get'],

            # the "dunder" formats are still available for direct use
            '__salt__': __salt__,
            '__pillar__': __pillar__,
            '__grains__': __grains__
        })
    except NameError:
        pass

    if sys.version > 3:
        exec(template.read(), _globals, _locals)
    else:
        exec template.read() in _globals, _locals

    return _registry.salt_data()
<|endoftext|>


<commit_before>import asyncio
import os
import binascii

import websockets
import libnacl
import libnacl.public

from . import util
from .exception import *
from .common import (
    KEY_LENGTH, COOKIE_LENGTH, RELAY_TIMEOUT, KEEP_ALIVE_TIMEOUT, KEEP_ALIVE_INTERVAL,
    ReceiverType, MessageType,
)
from .message import (
    unpack, AbstractMessage,
    ServerHelloMessage, ServerAuthMessage,
    NewResponderMessage, DropResponderMessage, SendErrorMessage,
    RawMessage,
)

class Path:
    __slots__ = ('_slots', 'log', 'initiator_key', 'number')

    def __init__(self, initiator_key, number):
        self._slots = {id_: None for id_ in range(0x01, 0xff + 1)}
        self.log = util.get_logger('path.{}'.format(number))
        self.initiator_key = initiator_key
        self.number = number

    @property
    def empty(self):
        return all((client is None for client in self._slots.values()))

    def get_initiator(self):
        """Return the initiator's :class:`Client` instance or `None`."""
        return self._slots.get(0x01)

    def set_initiator(self, initiator):
        """
        Set the initiator's :class:`Client` instance.

        Arguments:
            - `initiator`: A :class:`Client` instance.

        Return the previously set initiator or `None`.
        """
        previous_initiator = self._slots.get(0x01)
        self._slots[0x01] = initiator
        # Update initiator's log name
        initiator.update_log_name(0x01)
        # Return previous initiator
        return previous_initiator

    def get_responder(self, id_):
        """
        Return a responder's :class:`Client` instance or `None`.

        Arguments:
            - `id_`: The receiver identifier of the responder.

        Raises :exc:`ValueError` if `id_` is not a valid responder
        receiver identifier.
        """
        if not 0x01 < id_ <= 0xff:
            raise ValueError('Invalid responder identifier')
        return self._slots.get(id_)

    def get_responder_ids(self):
        """
        Return a list of responder's identifiers (slots).
        """
        return [id_ for id_ in self._slots.keys() if id_ > 0x01]

    def add_responder(self, responder):
        """
        Set a responder's :class:`Client` instance.

        Arguments:
            - `client`: A :class:`Client` instance.

        Raises :exc:`SlotsFullError` if no free slot exists on the path.

        Return the assigned slot identifier.
        """
        for id_, client in self._slots:
            if client is None:
                self._slots[id_] = responder
                # Update responder's log name
                responder.update_log_name(id_)
                # Return assigned slot id
                return id_
        raise SlotsFullError('No free slots on path')

class Client:
    __slots__ = (
        '_log', '_connection', '_client_key', '_server_key', '_box',
        'type', 'authenticated'
    )

    def __init__(self, connection, path_number, initiator_key, server_key=None):
        self._log = util.get_logger('path.{}.client'.format(path_number))
        self._connection = connection
        self._client_key = initiator_key
        self._server_key = server_key
        self._box = None
        self.type = None
        self.authenticated = False

    @property
    def server_key(self):
        """
        Return the server's :class:`libnacl.public.SecretKey` instance.
        """
        if self._server_key is None:
            self._server_key = libnacl.public.SecretKey()
        return self._server_key

    @property
    def box(self):
        """
        Return the :class:`libnacl.public.Box` instance.
        """
        if self._box is None:
            self._box = libnacl.public.Box(self.server_key, self._client_key)
        return self._box

    def set_client_key(self, public_key):
        """
        Set the public key of the client and update the internal box.

        Arguments:
            - `public_key`: A :class:`libnacl.public.PublicKey`.
        """
        self._client_key = public_key
        self._box = libnacl.public.Box(self.server_key, public_key)
        self._log.debug('Client key updated')

    def update_log_name(self, slot_id):
        """
        Update the logger's name by the assigned slot identifier.

        Arguments:
            - `slot_id`: The slot identifier of the client.
        """
        self._log.name += '.{}'.format(slot_id)

    def p2p_allowed(self, receiver_type):
        """
        Return `True` if :class:`RawMessage` instances are allowed and
        can be sent to the requested :class:`ReceiverType`.
        """
        return self.authenticated and self.type != receiver_type

    @asyncio.coroutine
    def send(self, message):
        """
        Disconnected
        """
        # Pack if not packed
        if isinstance(message, AbstractMessage):
            self._log.debug('Packing message')
            data = message.pack(self)
        else:
            data = message

        # Send data
        self._log.debug('Sending message')
        try:
            yield from self._connection.send(data)
        except websockets.ConnectionClosed as exc:
            self._log.debug('Connection closed while sending')
            raise Disconnected() from exc

    @asyncio.coroutine
    def receive(self):
        """
        Disconnected
        """
        # Receive data
        try:
            data = yield from self._connection.recv()
        except websockets.ConnectionClosed as exc:
            self._log.debug('Connection closed while receiving')
            raise Disconnected() from exc
        self._log.debug('Received message')

        # Unpack data and return
        self._log.debug('Unpacking message')
        return unpack(self, data)

    @asyncio.coroutine
    def ping(self):
        """
        Disconnected
        """
        self._log.debug('Sending ping')
        try:
            yield from self._connection.ping()
        except websockets.ConnectionClosed as exc:
            self._log.debug('Connection closed while pinging')
            raise Disconnected() from exc

class Protocol:
    PATH_LENGTH = KEY_LENGTH * 2

    def __init__(self, paths=None, loop=None):
        self._log = util.get_logger('protocol')
        self._loop = asyncio.get_event_loop() if loop is None else loop

        # Paths dict
        self._path_number = 0
        self._paths = {} if paths is None else paths

        # Set to None when closed
        self._closed_future = asyncio.Future(loop=self._loop)

    @asyncio.coroutine
    def _new_connection(self, connection, path):
        # TODO: As context manager? _clean_path on disconnect
        """
        SignalingError
        PathError
        Disconnected
        MessageError
        MessageFlowError
        SlotsFullError
        """
        self._log.debug('New connection')

        # Extract public key from path
        initiator_key = path.strip('/')

        # Validate key
        if len(initiator_key) != self.PATH_LENGTH:
            raise PathError('Invalid path length: {}'.format(len(initiator_key)))
        try:
            initiator_key = binascii.unhexlify(initiator_key)
        except binascii.Error as exc:
            raise PathError('Could not unhexlify path') from exc

        # Get path instance
        path = self._get_path(initiator_key)
        path.log.debug('New connection')

        # Create client instance
        client = Client(connection, path.number, initiator_key)

        # Do handshake
        path.log.debug('Starting handshake')
        yield from self._handshake(path, client)
        path.log.debug('Handshake completed')

        # Keep alive and poll for messages
        tasks = []
        if client.type == ReceiverType.initiator:
            path.log.debug('Starting runner for initiator {}', client)
            tasks.append(self._run_initiator(path, client))
        elif client.type == ReceiverType.responder:
            path.log.debug('Starting runner for responder {}', client)
            tasks.append(self._run_responder(path, client))
        else:
            raise ValueError('Invalid receiver type: {}'.format(client.type))
        path.log.debug('Starting keep-alive task for client {}', client)
        tasks.append(self._keep_alive(path, client))

        # Wait until complete
        tasks = [self._loop.create_task(coroutine) for coroutine in tasks]
        done, pending = yield from asyncio.wait(
            tasks, loop=self._loop, return_when=asyncio.FIRST_EXCEPTION)
        for task in done:
            exc = task.exception()
            if exc is not None:
                # Cancel pending tasks
                for pending_task in pending:
                    self._log.debug('Cancelling task {}', task)
                    pending_task.cancel()
                raise exc
            else:
                self._log.error('Task {} returned unexpectedly', task)
                raise SignalingError('Task returned too early')

    @asyncio.coroutine
    def _handshake(self, path, client):
        """
        Disconnected
        MessageError
        MessageFlowError
        SlotsFullError
        """
        # Send server-hello
        server_cookie = os.urandom(COOKIE_LENGTH)
        message = ServerHelloMessage.create(client.server_key.pk, server_cookie)
        path.log.debug('Sending server-hello')
        yield from client.send(message)

        # Receive client-hello or client-auth
        path.log.debug('Waiting for client-hello')
        message = yield from client.receive()
        if message.type == MessageType.client_auth:
            path.log.debug('Received client-auth')
            # Client is the initiator
            client.type = ReceiverType.initiator
            yield from self._handshake_initiator(path, client, message, server_cookie)
        elif message.type == MessageType.client_hello:
            path.log.debug('Received client-hello')
            # Client is a responder
            client.type = ReceiverType.responder
            yield from self._handshake_responder(path, client, message, server_cookie)

        else:
            error = "Expected 'client-hello' or 'client-auth', got '{}'"
            raise MessageFlowError(error.format(message.type))

    @asyncio.coroutine
    def _handshake_initiator(self, path, initiator, message, server_cookie):
        """
        Disconnected
        MessageError
        MessageFlowError
        """
        # Validate cookie
        path.log.debug('Validating cookie')
        if not util.consteq(message.server_cookie, server_cookie):
            raise MessageError('Cookies do not match')

        # Authenticated
        initiator.authenticated = True
        previous_initiator = path.set_initiator(initiator)
        # Drop previous initiator (we don't care about any exceptions)
        path.log.debug('Dropping previous initiator: {}', previous_initiator)
        self._loop.create_task(previous_initiator.close())

        # Send server-auth
        client_cookie = message.client_cookie
        responder_ids = path.get_responder_ids()
        message = ServerAuthMessage.create(client_cookie, responder_ids=responder_ids)
        path.log.debug('Sending server-auth including responder ids')
        yield from initiator.send(message)

    @asyncio.coroutine
    def _handshake_responder(self, path, responder, message, server_cookie):
        """
        Disconnected
        MessageError
        MessageFlowError
        SlotsFullError
        """
        # Set key on client
        responder.set_client_key(message.client_public_key)

        # Receive client-auth
        message = yield from responder.receive()
        if message.type != MessageType.client_auth:
            error = "Expected 'client-auth', got '{}'"
            raise MessageFlowError(error.format(message.type))

        # Validate cookie
        if not util.consteq(message.server_cookie, server_cookie):
            raise MessageError('Cookies do not match')

        # Authenticated
        responder.authenticated = True
        id_ = path.add_responder(responder)
        client_cookie = message.client_cookie

        # Send new-responder message if initiator is present
        initiator = path.get_initiator()
        if initiator is not None:
            message = NewResponderMessage.create(id_)
            path.log.debug('Sending new-responder to initiator')
            # TODO: Handle exceptions?
            self._loop.create_task(initiator.send(message))

        # Send server-auth
        message = ServerAuthMessage.create(client_cookie)
        path.log.debug('Sending server-auth without responder ids')
        yield from initiator.send(message)

    @asyncio.coroutine
    def _keep_alive(self, path, client):
        """
        Disconnected
        PingTimeoutError
        """
        while True:
            path.log.debug('Ping to {}', client)
            try:
                # Send ping
                yield from asyncio.wait_for(
                    client.ping(), KEEP_ALIVE_TIMEOUT, loop=self._loop)
            except asyncio.TimeoutError:
                raise PingTimeoutError(client)
            else:
                path.log.debug('Pong from {}', client)

            # Wait
            yield from asyncio.sleep(KEEP_ALIVE_INTERVAL, loop=self._loop)

    @asyncio.coroutine
    def _run_initiator(self, path, initiator):
        while not self._closed_future.done():
            # Receive relay message or drop-responder
            message = yield from initiator.receive()

            # Relay
            if isinstance(message, RawMessage):
                # Lookup responder
                responder = path.get_responder(message.receiver)
                # Send to responder
                coroutine = self._relay_message(path, initiator, responder, message)
                self._loop.create_task(coroutine)
            # Drop-responder
            elif message.type == MessageType.drop_responder:
                # Lookup responder
                responder = path.get_responder(message.responder_id)
                if responder is not None:
                    # Drop previous initiator (we don't care about any exceptions)
                    path.log.debug('Dropping responder: {}', responder)
                    self._loop.create_task(responder.close())
                else:
                    path.log.debug('Responder already dropped, nothing to do')
            else:
                error = "Expected relay message or 'drop-responder', got '{}'"
                raise MessageFlowError(error.format(message.type))

    @asyncio.coroutine
    def _run_responder(self, path, responder):
        while not self._closed_future.done():
            # Receive relay message
            message = yield from responder.receive()

            # Relay
            if isinstance(message, RawMessage):
                # Lookup initiator
                initiator = path.get_initiator()
                # Send to initiator
                coroutine = self._relay_message(path, responder, initiator, message)
                self._loop.create_task(coroutine)
            else:
                error = "Expected relay message, got '{}'"
                raise MessageFlowError(error.format(message.type))

    @asyncio.coroutine
    def _relay_message(self, path, sender, receiver, message):
        # Prepare message
        path.log.debug('Packing relay message')
        message_data = message.pack(sender)

        @asyncio.coroutine
        def send_error_message():
            path.log.debug('Relaying failed, reporting send-error to {}', sender)
            error = SendErrorMessage.create(libnacl.crypto_hash_sha256(message_data))
            # TODO: Handle exceptions, what if sender is gone?
            yield from sender.send(error)

        # Receiver not set? Send send-error to initiator
        if receiver is None:
            return (yield from send_error_message())

        path.log.debug('Sending relay message from {} to {}', sender, receiver)
        try:
            # Relay message to receiver
            future = receiver.send(message)
            yield from asyncio.wait_for(future, RELAY_TIMEOUT, loop=self._loop)
        except asyncio.TimeoutError:
            # Timed out or some other error, Send send-error to original sender
            path.log.debug('Sending relayed message timed out')
            yield from send_error_message()
        except Disconnected:
            path.log.debug('Receiver disconnected')
            yield from send_error_message()

    def _get_path(self, initiator_key):
        if self._paths.get(initiator_key) is None:
            self._path_number += 1
            self._paths[initiator_key] = Path(initiator_key, self._path_number)
            self._log.debug('Created new path: {}', self._path_number)
        return self._paths[initiator_key]

    def _clean_path(self, path):
        if path.empty:
            del self._paths[path.initiator_key]
            self._log.debug('Removed empty path: {}', path.number)
<commit_msg>Remove unused import<commit_after>import asyncio
import os
import binascii

import websockets
import libnacl
import libnacl.public

from . import util
from .exception import *
from .common import (
    KEY_LENGTH, COOKIE_LENGTH, RELAY_TIMEOUT, KEEP_ALIVE_TIMEOUT, KEEP_ALIVE_INTERVAL,
    ReceiverType, MessageType,
)
from .message import (
    unpack, AbstractMessage,
    ServerHelloMessage, ServerAuthMessage,
    NewResponderMessage, SendErrorMessage,
    RawMessage,
)

class Path:
    __slots__ = ('_slots', 'log', 'initiator_key', 'number')

    def __init__(self, initiator_key, number):
        self._slots = {id_: None for id_ in range(0x01, 0xff + 1)}
        self.log = util.get_logger('path.{}'.format(number))
        self.initiator_key = initiator_key
        self.number = number

    @property
    def empty(self):
        return all((client is None for client in self._slots.values()))

    def get_initiator(self):
        """Return the initiator's :class:`Client` instance or `None`."""
        return self._slots.get(0x01)

    def set_initiator(self, initiator):
        """
        Set the initiator's :class:`Client` instance.

        Arguments:
            - `initiator`: A :class:`Client` instance.

        Return the previously set initiator or `None`.
        """
        previous_initiator = self._slots.get(0x01)
        self._slots[0x01] = initiator
        # Update initiator's log name
        initiator.update_log_name(0x01)
        # Return previous initiator
        return previous_initiator

    def get_responder(self, id_):
        """
        Return a responder's :class:`Client` instance or `None`.

        Arguments:
            - `id_`: The receiver identifier of the responder.

        Raises :exc:`ValueError` if `id_` is not a valid responder
        receiver identifier.
        """
        if not 0x01 < id_ <= 0xff:
            raise ValueError('Invalid responder identifier')
        return self._slots.get(id_)

    def get_responder_ids(self):
        """
        Return a list of responder's identifiers (slots).
        """
        return [id_ for id_ in self._slots.keys() if id_ > 0x01]

    def add_responder(self, responder):
        """
        Set a responder's :class:`Client` instance.

        Arguments:
            - `client`: A :class:`Client` instance.

        Raises :exc:`SlotsFullError` if no free slot exists on the path.

        Return the assigned slot identifier.
        """
        for id_, client in self._slots:
            if client is None:
                self._slots[id_] = responder
                # Update responder's log name
                responder.update_log_name(id_)
                # Return assigned slot id
                return id_
        raise SlotsFullError('No free slots on path')

class Client:
    __slots__ = (
        '_log', '_connection', '_client_key', '_server_key', '_box',
        'type', 'authenticated'
    )

    def __init__(self, connection, path_number, initiator_key, server_key=None):
        self._log = util.get_logger('path.{}.client'.format(path_number))
        self._connection = connection
        self._client_key = initiator_key
        self._server_key = server_key
        self._box = None
        self.type = None
        self.authenticated = False

    @property
    def server_key(self):
        """
        Return the server's :class:`libnacl.public.SecretKey` instance.
        """
        if self._server_key is None:
            self._server_key = libnacl.public.SecretKey()
        return self._server_key

    @property
    def box(self):
        """
        Return the :class:`libnacl.public.Box` instance.
        """
        if self._box is None:
            self._box = libnacl.public.Box(self.server_key, self._client_key)
        return self._box

    def set_client_key(self, public_key):
        """
        Set the public key of the client and update the internal box.

        Arguments:
            - `public_key`: A :class:`libnacl.public.PublicKey`.
        """
        self._client_key = public_key
        self._box = libnacl.public.Box(self.server_key, public_key)
        self._log.debug('Client key updated')

    def update_log_name(self, slot_id):
        """
        Update the logger's name by the assigned slot identifier.

        Arguments:
            - `slot_id`: The slot identifier of the client.
        """
        self._log.name += '.{}'.format(slot_id)

    def p2p_allowed(self, receiver_type):
        """
        Return `True` if :class:`RawMessage` instances are allowed and
        can be sent to the requested :class:`ReceiverType`.
        """
        return self.authenticated and self.type != receiver_type

    @asyncio.coroutine
    def send(self, message):
        """
        Disconnected
        """
        # Pack if not packed
        if isinstance(message, AbstractMessage):
            self._log.debug('Packing message')
            data = message.pack(self)
        else:
            data = message

        # Send data
        self._log.debug('Sending message')
        try:
            yield from self._connection.send(data)
        except websockets.ConnectionClosed as exc:
            self._log.debug('Connection closed while sending')
            raise Disconnected() from exc

    @asyncio.coroutine
    def receive(self):
        """
        Disconnected
        """
        # Receive data
        try:
            data = yield from self._connection.recv()
        except websockets.ConnectionClosed as exc:
            self._log.debug('Connection closed while receiving')
            raise Disconnected() from exc
        self._log.debug('Received message')

        # Unpack data and return
        self._log.debug('Unpacking message')
        return unpack(self, data)

    @asyncio.coroutine
    def ping(self):
        """
        Disconnected
        """
        self._log.debug('Sending ping')
        try:
            yield from self._connection.ping()
        except websockets.ConnectionClosed as exc:
            self._log.debug('Connection closed while pinging')
            raise Disconnected() from exc

class Protocol:
    PATH_LENGTH = KEY_LENGTH * 2

    def __init__(self, paths=None, loop=None):
        self._log = util.get_logger('protocol')
        self._loop = asyncio.get_event_loop() if loop is None else loop

        # Paths dict
        self._path_number = 0
        self._paths = {} if paths is None else paths

        # Set to None when closed
        self._closed_future = asyncio.Future(loop=self._loop)

    @asyncio.coroutine
    def _new_connection(self, connection, path):
        # TODO: As context manager? _clean_path on disconnect
        """
        SignalingError
        PathError
        Disconnected
        MessageError
        MessageFlowError
        SlotsFullError
        """
        self._log.debug('New connection')

        # Extract public key from path
        initiator_key = path.strip('/')

        # Validate key
        if len(initiator_key) != self.PATH_LENGTH:
            raise PathError('Invalid path length: {}'.format(len(initiator_key)))
        try:
            initiator_key = binascii.unhexlify(initiator_key)
        except binascii.Error as exc:
            raise PathError('Could not unhexlify path') from exc

        # Get path instance
        path = self._get_path(initiator_key)
        path.log.debug('New connection')

        # Create client instance
        client = Client(connection, path.number, initiator_key)

        # Do handshake
        path.log.debug('Starting handshake')
        yield from self._handshake(path, client)
        path.log.debug('Handshake completed')

        # Keep alive and poll for messages
        tasks = []
        if client.type == ReceiverType.initiator:
            path.log.debug('Starting runner for initiator {}', client)
            tasks.append(self._run_initiator(path, client))
        elif client.type == ReceiverType.responder:
            path.log.debug('Starting runner for responder {}', client)
            tasks.append(self._run_responder(path, client))
        else:
            raise ValueError('Invalid receiver type: {}'.format(client.type))
        path.log.debug('Starting keep-alive task for client {}', client)
        tasks.append(self._keep_alive(path, client))

        # Wait until complete
        tasks = [self._loop.create_task(coroutine) for coroutine in tasks]
        done, pending = yield from asyncio.wait(
            tasks, loop=self._loop, return_when=asyncio.FIRST_EXCEPTION)
        for task in done:
            exc = task.exception()
            if exc is not None:
                # Cancel pending tasks
                for pending_task in pending:
                    self._log.debug('Cancelling task {}', task)
                    pending_task.cancel()
                raise exc
            else:
                self._log.error('Task {} returned unexpectedly', task)
                raise SignalingError('Task returned too early')

    @asyncio.coroutine
    def _handshake(self, path, client):
        """
        Disconnected
        MessageError
        MessageFlowError
        SlotsFullError
        """
        # Send server-hello
        server_cookie = os.urandom(COOKIE_LENGTH)
        message = ServerHelloMessage.create(client.server_key.pk, server_cookie)
        path.log.debug('Sending server-hello')
        yield from client.send(message)

        # Receive client-hello or client-auth
        path.log.debug('Waiting for client-hello')
        message = yield from client.receive()
        if message.type == MessageType.client_auth:
            path.log.debug('Received client-auth')
            # Client is the initiator
            client.type = ReceiverType.initiator
            yield from self._handshake_initiator(path, client, message, server_cookie)
        elif message.type == MessageType.client_hello:
            path.log.debug('Received client-hello')
            # Client is a responder
            client.type = ReceiverType.responder
            yield from self._handshake_responder(path, client, message, server_cookie)

        else:
            error = "Expected 'client-hello' or 'client-auth', got '{}'"
            raise MessageFlowError(error.format(message.type))

    @asyncio.coroutine
    def _handshake_initiator(self, path, initiator, message, server_cookie):
        """
        Disconnected
        MessageError
        MessageFlowError
        """
        # Validate cookie
        path.log.debug('Validating cookie')
        if not util.consteq(message.server_cookie, server_cookie):
            raise MessageError('Cookies do not match')

        # Authenticated
        initiator.authenticated = True
        previous_initiator = path.set_initiator(initiator)
        # Drop previous initiator (we don't care about any exceptions)
        path.log.debug('Dropping previous initiator: {}', previous_initiator)
        self._loop.create_task(previous_initiator.close())

        # Send server-auth
        client_cookie = message.client_cookie
        responder_ids = path.get_responder_ids()
        message = ServerAuthMessage.create(client_cookie, responder_ids=responder_ids)
        path.log.debug('Sending server-auth including responder ids')
        yield from initiator.send(message)

    @asyncio.coroutine
    def _handshake_responder(self, path, responder, message, server_cookie):
        """
        Disconnected
        MessageError
        MessageFlowError
        SlotsFullError
        """
        # Set key on client
        responder.set_client_key(message.client_public_key)

        # Receive client-auth
        message = yield from responder.receive()
        if message.type != MessageType.client_auth:
            error = "Expected 'client-auth', got '{}'"
            raise MessageFlowError(error.format(message.type))

        # Validate cookie
        if not util.consteq(message.server_cookie, server_cookie):
            raise MessageError('Cookies do not match')

        # Authenticated
        responder.authenticated = True
        id_ = path.add_responder(responder)
        client_cookie = message.client_cookie

        # Send new-responder message if initiator is present
        initiator = path.get_initiator()
        if initiator is not None:
            message = NewResponderMessage.create(id_)
            path.log.debug('Sending new-responder to initiator')
            # TODO: Handle exceptions?
            self._loop.create_task(initiator.send(message))

        # Send server-auth
        message = ServerAuthMessage.create(client_cookie)
        path.log.debug('Sending server-auth without responder ids')
        yield from initiator.send(message)

    @asyncio.coroutine
    def _keep_alive(self, path, client):
        """
        Disconnected
        PingTimeoutError
        """
        while True:
            path.log.debug('Ping to {}', client)
            try:
                # Send ping
                yield from asyncio.wait_for(
                    client.ping(), KEEP_ALIVE_TIMEOUT, loop=self._loop)
            except asyncio.TimeoutError:
                raise PingTimeoutError(client)
            else:
                path.log.debug('Pong from {}', client)

            # Wait
            yield from asyncio.sleep(KEEP_ALIVE_INTERVAL, loop=self._loop)

    @asyncio.coroutine
    def _run_initiator(self, path, initiator):
        while not self._closed_future.done():
            # Receive relay message or drop-responder
            message = yield from initiator.receive()

            # Relay
            if isinstance(message, RawMessage):
                # Lookup responder
                responder = path.get_responder(message.receiver)
                # Send to responder
                coroutine = self._relay_message(path, initiator, responder, message)
                self._loop.create_task(coroutine)
            # Drop-responder
            elif message.type == MessageType.drop_responder:
                # Lookup responder
                responder = path.get_responder(message.responder_id)
                if responder is not None:
                    # Drop previous initiator (we don't care about any exceptions)
                    path.log.debug('Dropping responder: {}', responder)
                    self._loop.create_task(responder.close())
                else:
                    path.log.debug('Responder already dropped, nothing to do')
            else:
                error = "Expected relay message or 'drop-responder', got '{}'"
                raise MessageFlowError(error.format(message.type))

    @asyncio.coroutine
    def _run_responder(self, path, responder):
        while not self._closed_future.done():
            # Receive relay message
            message = yield from responder.receive()

            # Relay
            if isinstance(message, RawMessage):
                # Lookup initiator
                initiator = path.get_initiator()
                # Send to initiator
                coroutine = self._relay_message(path, responder, initiator, message)
                self._loop.create_task(coroutine)
            else:
                error = "Expected relay message, got '{}'"
                raise MessageFlowError(error.format(message.type))

    @asyncio.coroutine
    def _relay_message(self, path, sender, receiver, message):
        # Prepare message
        path.log.debug('Packing relay message')
        message_data = message.pack(sender)

        @asyncio.coroutine
        def send_error_message():
            path.log.debug('Relaying failed, reporting send-error to {}', sender)
            error = SendErrorMessage.create(libnacl.crypto_hash_sha256(message_data))
            # TODO: Handle exceptions, what if sender is gone?
            yield from sender.send(error)

        # Receiver not set? Send send-error to initiator
        if receiver is None:
            return (yield from send_error_message())

        path.log.debug('Sending relay message from {} to {}', sender, receiver)
        try:
            # Relay message to receiver
            future = receiver.send(message)
            yield from asyncio.wait_for(future, RELAY_TIMEOUT, loop=self._loop)
        except asyncio.TimeoutError:
            # Timed out or some other error, Send send-error to original sender
            path.log.debug('Sending relayed message timed out')
            yield from send_error_message()
        except Disconnected:
            path.log.debug('Receiver disconnected')
            yield from send_error_message()

    def _get_path(self, initiator_key):
        if self._paths.get(initiator_key) is None:
            self._path_number += 1
            self._paths[initiator_key] = Path(initiator_key, self._path_number)
            self._log.debug('Created new path: {}', self._path_number)
        return self._paths[initiator_key]

    def _clean_path(self, path):
        if path.empty:
            del self._paths[path.initiator_key]
            self._log.debug('Removed empty path: {}', path.number)
<|endoftext|>


<commit_before>import click
import time
from itertools import takewhile
from subprocess import check_output

def get_vagrant_vms():
    status = vagrant('status')
    lines = status.splitlines()[2:]
    vms = takewhile(lambda x: x.split(), lines)
    vms = map(lambda x: x.split()[0], vms)
    return vms

def vboxmanage(*args):
    args = ('VBoxManage', ) + args
    p = check_output(args, shell=False)
    return p

def vagrant(*args):
    args = ('vagrant', ) + args
    p = check_output(args, shell=False)
    return p

@click.group()
def cli():
    pass

@cli.command()
@click.option('-n', default=None)
def take(n):
    now = time.time()
    if n is None:
        n = 'solar-%d' % now
    vms = get_vagrant_vms()
    for vm in vms:
        click.echo("Taking %s" % vm)
        snap = vboxmanage('snapshot', vm, 'take', n, '--live', '--description', 'solar: %d' % now)
        click.echo(snap)

@click.option('-n')
@cli.command()
def restore(n):
    vms = get_vagrant_vms()
    for vm in vms:
        click.echo("Restoring %s" % vm)
        snap = vboxmanage('snapshot', vm, 'restore', n)
        click.echo(snap)

# wanted to use list but it would
@cli.command()
def show():
    vms = get_vagrant_vms()
    for vm in vms:
        click.echo("VM: %s" % vm)
        snap = vboxmanage('snapshot', vm, 'list')
        click.echo(snap)
        click.echo('-' * 10)

@click.option('-n')
@cli.command()
def delete(n):
    vms = get_vagrant_vms()
    for vm in vms:
        click.echo('Removing %s from %s' % (n, vm))
        snap = vboxmanage('snapshot', vm, 'delete', n)
        click.echo(snap)

if __name__ == '__main__':
    cli()
<commit_msg>Snapshotter: fixes to 'show' list command<commit_after>import click
import time
from itertools import takewhile
from subprocess import check_output

def get_vagrant_vms():
    status = vagrant('status')
    lines = status.splitlines()[2:]
    vms = takewhile(lambda x: x.split(), lines)
    vms = map(lambda x: x.split()[0], vms)
    return vms

def vboxmanage(*args):
    args = ('VBoxManage', ) + args
    p = check_output(args, shell=False)
    return p

def vagrant(*args):
    args = ('vagrant', ) + args
    p = check_output(args, shell=False)
    return p

@click.group()
def cli():
    pass

@cli.command()
@click.option('-n', default=None)
def take(n):
    now = time.time()
    if n is None:
        n = 'solar-%d' % now
    vms = get_vagrant_vms()
    for vm in vms:
        click.echo("Taking %s" % vm)
        snap = vboxmanage('snapshot', vm, 'take', n, '--live', '--description', 'solar: %d' % now)
        click.echo(snap)

@click.option('-n')
@cli.command()
def restore(n):
    vms = get_vagrant_vms()
    for vm in vms:
        click.echo("Restoring %s" % vm)
        snap = vboxmanage('snapshot', vm, 'restore', n)
        click.echo(snap)

# wanted to use list but it would
@cli.command()
def show():
    vms = get_vagrant_vms()
    for vm in vms:
        msg = "[{vm}] {snap}"
        kwargs = {
            'vm': click.style(vm, fg='green'),
            'snap': '',
        }
        try:
            snap = vboxmanage('snapshot', vm, 'list')
            kwargs['snap'] = snap.split('\n')[0].replace('Name:', '').strip()
        except Exception:
            kwargs['snap'] = click.style(
                'This machine does not have any snapshots',
                fg='red'
            )
        click.echo(msg.format(**kwargs))
        click.echo('-' * 10)

@click.option('-n')
@cli.command()
def delete(n):
    vms = get_vagrant_vms()
    for vm in vms:
        click.echo('Removing %s from %s' % (n, vm))
        snap = vboxmanage('snapshot', vm, 'delete', n)
        click.echo(snap)

if __name__ == '__main__':
    cli()
<|endoftext|>


<commit_before>import os
import shutil
import tempfile
from optparse import make_option
from django.core.management.base import BaseCommand
from django.conf import settings
from django.template import Context, Template

# TODO: The (ab)use of __file__ makes me nervous. Should improve compatibility
#       with zipimport.
#
#       Also, I'd like to move application.py out of the WAR root. Need to check
#       if modjy can support a path relative to the war root to specify the
#       location of application.py.

class Command(BaseCommand):
    option_list = BaseCommand.option_list + (
        make_option('--include-java-libs', dest='include_java_libs', default='',
                    help='Comma separated list of java libraries (JAR files) to '
                         'include. Typically used for JDBC drivers '),
        make_option('--include-py-libs', dest='include_py_libs', default='',
                    help='Comma separated list of python libraries (directories '
                         'or JAR/ZIP files) to include'),
        make_option('--context-root', dest='context_root', default='',
                    help='Name of the context root for the application. If '
                         'unspecified, the project name is used. The context '
                          'root name is used as the name of the WAR file, and '
                          'as a prefix for some url-related settings, such as '
                          'MEDIA_URL')
    )
    help = ("Builds a WAR file for stand-alone deployment on a Java "
            "Servlet container")
    def handle(self, *args, **options):
        project_name = self.project_name()
        context_root = options['context_root'] or project_name
        exploded_war_dir = os.path.join(tempfile.mkdtemp(), project_name)
        print
        print "Assembling WAR on %s" % exploded_war_dir
        print
        self.copy_skel(exploded_war_dir)
        self.fill_templates(exploded_war_dir,
                            ['WEB-INF/web.xml', 'application.py'],
                            {'project_name': project_name,
                             'settings': settings})
        self.copy_jython(exploded_war_dir)
        self.copy_django(exploded_war_dir)
        self.copy_admin_media(exploded_war_dir)
        self.copy_project(exploded_war_dir)
        self.fix_project_settings(exploded_war_dir, context_root)
        self.copy_project_media(exploded_war_dir)
        self.copy_apps(exploded_war_dir)
        if options['include_java_libs']:
            for java_lib in options['include_java_libs'].split(','):
                self.copy_java_lib(exploded_war_dir, java_lib)
        if options['include_py_libs']:
            for py_lib in options['inclide_py_libs'].split(','):
                self.copy_py_lib(exploded_war_dir, py_lib)
        print "Finished."

    def copy_skel(self, exploded_war_dir):
        print "Copying WAR skeleton..."
        shutil.copytree(self._skel_directory(), exploded_war_dir)

    def _skel_directory(self):
        return os.path.join(os.path.dirname(__file__), 'war_skel')

    def fill_templates(self, exploded_war_dir, relative_file_names, vars):
        for relative_file_name in relative_file_names:
            file_name = os.path.join(*[exploded_war_dir] +
                                     relative_file_name.split('/'))
            template = Template(file(file_name).read())
            file(file_name, 'w').write(template.render(Context(vars)))

    def copy_jython(self, exploded_war_dir):
        jython_lib_path = os.path.dirname(os.path.abspath(os.__file__))
        jython_home = os.path.dirname(jython_lib_path)
        if jython_home.endswith('.jar'):
            # We are on a Jython stand-alone installation.
            self.copy_java_lib(exploded_war_dir, jython_home)
        else:
            # Standard installation: jython.jar inside jython_home
            self.copy_java_lib(exploded_war_dir,
                               os.path.join(jython_home, 'jython.jar'))
            # XXX: Right now (August 2008), on the asm branch in subversion,
            # jython.jar depends on a javalib/jarjar.jar file, containing the
            # runtime dependencies. In the future this step may not be needed
            self.copy_java_lib(exploded_war_dir,
                               os.path.join(jython_home, 'javalib', 'jarjar.jar'))
            self.copy_py_lib(exploded_war_dir, jython_lib_path)

    def copy_django(self, exploded_war_dir):
        import django
        django_dir = os.path.dirname(os.path.abspath(django.__file__))
        self.copy_py_lib(exploded_war_dir, django_dir)

    def copy_admin_media(self, exploded_war_dir):
        from django.contrib import admin
        self.copy_media(exploded_war_dir,
                        os.path.join(os.path.dirname(admin.__file__), 'media'),
                        os.path.join(*settings.ADMIN_MEDIA_PREFIX.split('/')))

    def copy_project(self, exploded_war_dir):
        self.copy_py_lib(exploded_war_dir, self.project_directory())

    def fix_project_settings(self, exploded_war_dir, context_root):
        fix_media = (settings.MEDIA_URL and
                     not settings.MEDIA_URL.startswith('http'))
        fix_admin_media =  (settings.ADMIN_MEDIA_PREFIX and
                            not settings.ADMIN_MEDIA_PREFIX.startswith('http'))
        if not fix_media and not fix_admin_media:
            return

        fix = """
# Added by django-jython. Fixes URL prefixes to include the context root:
"""
        if fix_media:
            fix += "MEDIA_URL='/%s%s'\n" % (context_root, settings.MEDIA_URL)
        if fix_admin_media:
            fix += "ADMIN_MEDIA_PREFIX='/%s%s'\n" % (context_root,
                                                     settings.ADMIN_MEDIA_PREFIX)

        settings_name = settings.SETTINGS_MODULE.split('.')[-1]
        deployed_settings = os.path.join(exploded_war_dir,'WEB-INF', 'lib-python',
                                         self.project_name(), self.project_name(),
                                         settings_name + '.py')
        if os.path.exists(deployed_settings):
            settings_file_modified = file(deployed_settings, 'a')
            settings_file_modified.write(fix)
            settings_file_modified.close()
        else:
            print """WARNING: settings module file not found inside the project
directory (maybe you have split settings into a package?)

You SHOULD manually prefix the ADMIN_MEDIA_PREFIX and/or MEDIA_URL settings on the
deployed settings file. You can append the following block at the end of the file:

# ---------------------------- Begin Snip ---------------------------------
%s
# ----------------------------- End Snip -----------------------------------
""" % fix

    def copy_project_media(self, exploded_war_dir):
        if not settings.MEDIA_ROOT:
            print ("WARNING: Not copying project media, since MEDIA_ROOT "
                   "is not defined")
            return
        if not settings.MEDIA_URL:
            print ("WARNING: Not copying project media, since MEDIA_URL "
                   "is not defined")
            return
        if settings.MEDIA_URL.startswith('http'):
            print ("WARNING: Not copying project media, since MEDIA_URL "
                   "is absolute (starts with 'http')")
        self.copy_media(exploded_war_dir,
                        settings.MEDIA_ROOT,
                        os.path.join(*settings.MEDIA_URL.split('/')))

    def copy_apps(self, exploded_war_dir):
        for app in settings.INSTALLED_APPS:
            if app.startswith('django.') or \
                   app.startswith(self.project_name() + '.'):
                continue # Already included
            app_first_dir = os.path.dirname(os.path.abspath(__import__(app).__file__))
            self.copy_py_lib(exploded_war_dir, app_first_dir)

    def copy_java_lib(self, exploded_war_dir, java_lib):
        # java_lib is a path to a JAR file
        dest_name = os.path.basename(java_lib)
        print "Copying %s..." % dest_name
        shutil.copy(java_lib,
                    os.path.join(exploded_war_dir,
                                 'WEB-INF', 'lib', dest_name))

    def copy_py_lib(self, exploded_war_dir, py_lib_dir):
        dest_name = os.path.basename(py_lib_dir)
        print "Copying %s..." % dest_name
        if dest_name != 'Lib':
            # Each python library goes into its own sys.path entry (Except Lib,
            # which is itself a sys.path entry. Maybe I should add some flag to
            # this method instead of special-casing Lib)
            os.mkdir(os.path.join(exploded_war_dir,
                                  'WEB-INF', 'lib-python', dest_name))
            dest_name = os.path.join(dest_name, dest_name)

        shutil.copytree(py_lib_dir,
                        os.path.join(exploded_war_dir,
                                     'WEB-INF', 'lib-python', dest_name))

    def copy_media(self, exploded_war_dir, src_dir, dest_relative_path):
        if dest_relative_path[-1] == '/':
            dest_relative_path = dest_relative_path[:-1]
        if os.path.sep in dest_relative_path:
            # We have to construct the directory hierarchy (without the last
            # level)
            d = exploded_war_dir
            for sub_dir in os.path.split(dest_relative_path)[:-1]:
                d = os.path.join(d, sub_dir)
                os.mkdir(d)
        print "Copying %s..." % dest_relative_path
        shutil.copytree(src_dir,
                        os.path.join(exploded_war_dir, dest_relative_path))

    def settings_module(self):
        return __import__(settings.SETTINGS_MODULE, {}, {},
                          (settings.SETTINGS_MODULE.split(".")[-1],))
    def project_directory(self):
        return os.path.dirname(self.settings_module().__file__)

    def project_name(self):
        return os.path.basename(self.project_directory())

<commit_msg>Final touch to the war management command: Now it actually generates the WAR file!<commit_after>import os
import shutil
import tempfile
import zipfile
from optparse import make_option
from django.core.management.base import BaseCommand
from django.conf import settings
from django.template import Context, Template

# TODO: The (ab)use of __file__ makes me nervous. Should improve compatibility
#       with zipimport.
#
#       Also, I'd like to move application.py out of the WAR root. Need to check
#       if modjy can support a path relative to the war root to specify the
#       location of application.py.

class Command(BaseCommand):
    option_list = BaseCommand.option_list + (
        make_option('--include-java-libs', dest='include_java_libs', default='',
                    help='Comma separated list of java libraries (JAR files) to '
                         'include. Typically used for JDBC drivers '),
        make_option('--include-py-libs', dest='include_py_libs', default='',
                    help='Comma separated list of python libraries (directories '
                         'or JAR/ZIP files) to include'),
        make_option('--context-root', dest='context_root', default='',
                    help='Name of the context root for the application. If '
                         'unspecified, the project name is used. The context '
                          'root name is used as the name of the WAR file, and '
                          'as a prefix for some url-related settings, such as '
                          'MEDIA_URL')
    )
    help = ("Builds a WAR file for stand-alone deployment on a Java "
            "Servlet container")

    def handle(self, *args, **options):
        project_name = self.project_name()
        context_root = options['context_root'] or project_name
        temp_dir = tempfile.mkdtemp()
        exploded_war_dir = os.path.join(temp_dir, project_name)
        print
        print "Assembling WAR on %s" % exploded_war_dir
        print
        self.copy_skel(exploded_war_dir)
        self.fill_templates(exploded_war_dir,
                            ['WEB-INF/web.xml', 'application.py'],
                            {'project_name': project_name,
                             'settings': settings})
        self.copy_jython(exploded_war_dir)
        self.copy_django(exploded_war_dir)
        self.copy_admin_media(exploded_war_dir)
        self.copy_project(exploded_war_dir)
        self.fix_project_settings(exploded_war_dir, context_root)
        self.copy_project_media(exploded_war_dir)
        self.copy_apps(exploded_war_dir)
        if options['include_java_libs']:
            for java_lib in options['include_java_libs'].split(','):
                self.copy_java_lib(exploded_war_dir, java_lib)
        if options['include_py_libs']:
            for py_lib in options['inclide_py_libs'].split(','):
                self.copy_py_lib(exploded_war_dir, py_lib)

        # I'm still unsure of wheter (by default) the WAR should be generated on
        # the parent directory of the project root or inside the generated
        # temporary directory.
        #
        # At least I'm sure I don't want to put it inside the project directory,
        # to avoid cluttering it, and to keep it simple the logic of copying the
        # project into the WAR (otherwise, it should special case the war file
        # itself)
        war_file_name = os.path.join(self.project_directory(),
                                     '..', context_root + '.war')
        self.war(exploded_war_dir, war_file_name)
        print "Cleaning %s..." % temp_dir
        shutil.rmtree(temp_dir)
        print """
Finished.

Now you can copy %s to whatever location you application server wants it.
""" % os.path.abspath(war_file_name)

    def copy_skel(self, exploded_war_dir):
        print "Copying WAR skeleton..."
        shutil.copytree(self._skel_directory(), exploded_war_dir)

    def _skel_directory(self):
        return os.path.join(os.path.dirname(__file__), 'war_skel')

    def fill_templates(self, exploded_war_dir, relative_file_names, vars):
        for relative_file_name in relative_file_names:
            file_name = os.path.join(*[exploded_war_dir] +
                                     relative_file_name.split('/'))
            template = Template(file(file_name).read())
            file(file_name, 'w').write(template.render(Context(vars)))

    def copy_jython(self, exploded_war_dir):
        jython_lib_path = os.path.dirname(os.path.abspath(os.__file__))
        jython_home = os.path.dirname(jython_lib_path)
        if jython_home.endswith('.jar'):
            # We are on a Jython stand-alone installation.
            self.copy_java_lib(exploded_war_dir, jython_home)
        else:
            # Standard installation: jython.jar inside jython_home
            self.copy_java_lib(exploded_war_dir,
                               os.path.join(jython_home, 'jython.jar'))
            # XXX: Right now (August 2008), on the asm branch in subversion,
            # jython.jar depends on a javalib/jarjar.jar file, containing the
            # runtime dependencies. In the future this step may not be needed
            self.copy_java_lib(exploded_war_dir,
                               os.path.join(jython_home, 'javalib', 'jarjar.jar'))
            self.copy_py_lib(exploded_war_dir, jython_lib_path)

    def copy_django(self, exploded_war_dir):
        import django
        django_dir = os.path.dirname(os.path.abspath(django.__file__))
        self.copy_py_lib(exploded_war_dir, django_dir)

    def copy_admin_media(self, exploded_war_dir):
        from django.contrib import admin
        self.copy_media(exploded_war_dir,
                        os.path.join(os.path.dirname(admin.__file__), 'media'),
                        os.path.join(*settings.ADMIN_MEDIA_PREFIX.split('/')))

    def copy_project(self, exploded_war_dir):
        self.copy_py_lib(exploded_war_dir, self.project_directory())

    def fix_project_settings(self, exploded_war_dir, context_root):
        fix_media = (settings.MEDIA_URL and
                     not settings.MEDIA_URL.startswith('http'))
        fix_admin_media =  (settings.ADMIN_MEDIA_PREFIX and
                            not settings.ADMIN_MEDIA_PREFIX.startswith('http'))
        if not fix_media and not fix_admin_media:
            return

        fix = """
# Added by django-jython. Fixes URL prefixes to include the context root:
"""
        if fix_media:
            fix += "MEDIA_URL='/%s%s'\n" % (context_root, settings.MEDIA_URL)
        if fix_admin_media:
            fix += "ADMIN_MEDIA_PREFIX='/%s%s'\n" % (context_root,
                                                     settings.ADMIN_MEDIA_PREFIX)

        settings_name = settings.SETTINGS_MODULE.split('.')[-1]
        deployed_settings = os.path.join(exploded_war_dir,'WEB-INF', 'lib-python',
                                         self.project_name(), self.project_name(),
                                         settings_name + '.py')
        if os.path.exists(deployed_settings):
            settings_file_modified = file(deployed_settings, 'a')
            settings_file_modified.write(fix)
            settings_file_modified.close()
        else:
            print """WARNING: settings module file not found inside the project
directory (maybe you have split settings into a package?)

You SHOULD manually prefix the ADMIN_MEDIA_PREFIX and/or MEDIA_URL settings on the
deployed settings file. You can append the following block at the end of the file:

# ---------------------------- Begin Snip ---------------------------------
%s
# ----------------------------- End Snip -----------------------------------
""" % fix

    def copy_project_media(self, exploded_war_dir):
        if not settings.MEDIA_ROOT:
            print ("WARNING: Not copying project media, since MEDIA_ROOT "
                   "is not defined")
            return
        if not settings.MEDIA_URL:
            print ("WARNING: Not copying project media, since MEDIA_URL "
                   "is not defined")
            return
        if settings.MEDIA_URL.startswith('http'):
            print ("WARNING: Not copying project media, since MEDIA_URL "
                   "is absolute (starts with 'http')")
        self.copy_media(exploded_war_dir,
                        settings.MEDIA_ROOT,
                        os.path.join(*settings.MEDIA_URL.split('/')))

    def copy_apps(self, exploded_war_dir):
        for app in settings.INSTALLED_APPS:
            if app.startswith('django.') or \
                   app.startswith(self.project_name() + '.'):
                continue # Already included
            app_first_dir = os.path.dirname(os.path.abspath(__import__(app).__file__))
            self.copy_py_lib(exploded_war_dir, app_first_dir)

    def copy_java_lib(self, exploded_war_dir, java_lib):
        # java_lib is a path to a JAR file
        dest_name = os.path.basename(java_lib)
        print "Copying %s..." % dest_name
        shutil.copy(java_lib,
                    os.path.join(exploded_war_dir,
                                 'WEB-INF', 'lib', dest_name))

    def copy_py_lib(self, exploded_war_dir, py_lib_dir):
        dest_name = os.path.basename(py_lib_dir)
        print "Copying %s..." % dest_name
        if dest_name != 'Lib':
            # Each python library goes into its own sys.path entry (Except Lib,
            # which is itself a sys.path entry. Maybe I should add some flag to
            # this method instead of special-casing Lib)
            os.mkdir(os.path.join(exploded_war_dir,
                                  'WEB-INF', 'lib-python', dest_name))
            dest_name = os.path.join(dest_name, dest_name)

        shutil.copytree(py_lib_dir,
                        os.path.join(exploded_war_dir,
                                     'WEB-INF', 'lib-python', dest_name))

    def copy_media(self, exploded_war_dir, src_dir, dest_relative_path):
        if dest_relative_path[-1] == '/':
            dest_relative_path = dest_relative_path[:-1]
        if os.path.sep in dest_relative_path:
            # We have to construct the directory hierarchy (without the last
            # level)
            d = exploded_war_dir
            for sub_dir in os.path.split(dest_relative_path)[:-1]:
                d = os.path.join(d, sub_dir)
                os.mkdir(d)
        print "Copying %s..." % dest_relative_path
        shutil.copytree(src_dir,
                        os.path.join(exploded_war_dir, dest_relative_path))

    def war(self, exploded_war_dir, war_file_name):
        # Make sure we are working with absolute paths
        exploded_war_dir = os.path.abspath(exploded_war_dir)
        war_file_name = os.path.abspath(war_file_name)

        print "Building WAR on %s..." % war_file_name
        war = zipfile.ZipFile(war_file_name, 'w',
                              compression=zipfile.ZIP_DEFLATED)
        def walker(arg, directory, files):
            # The following + 1 accounts for the path separator after the
            # directory name
            relative_dir = directory[len(exploded_war_dir) + 1:]
            for f in files:
                file_name = os.path.join(directory, f)
                zip_file_name = os.path.join(relative_dir, f)
                if not os.path.isfile(file_name):
                    continue
                war.write(file_name,
                          os.path.join(relative_dir, f),
                          zipfile.ZIP_DEFLATED)
        os.path.walk(exploded_war_dir, walker, None)
        war.close()

    def settings_module(self):
        return __import__(settings.SETTINGS_MODULE, {}, {},
                          (settings.SETTINGS_MODULE.split(".")[-1],))
    def project_directory(self):
        return os.path.dirname(self.settings_module().__file__)

    def project_name(self):
        return os.path.basename(self.project_directory())

<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
#
# mpc-hc documentation build configuration file, created by
# sphinx-quickstart on Sat Mar 17 17:15:28 2012.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

import sys, os

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))

# -- General configuration -----------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.ifconfig', 'sphinx.ext.extlinks']

extlinks = {'issue': ('https://sourceforge.net/apps/trac/mpc-hc/ticket/%s', 'Ticket #')}

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
source_encoding = 'utf-8'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'MPC-HC'
copyright = u'MPC-HC Team'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.6.4'
# The full version, including alpha/beta/rc tags.
release = '1.6.4.6052'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []

# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# Trim spaces before footnote references that are necessary for the reST
# parser to recognize the footnote, but do not look too nice in the output.
trim_footnote_reference_space = True

# -- Options for HTML output ---------------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'default'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
html_title = 'MPC-HC project'

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = 'logo.png'

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
html_favicon = 'favicon.ico'

html_add_permalinks = False

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, the reST sources are included in the HTML build as _sources/name.
# The default is True.
#
# Warning
# If this config value is set to False, the JavaScript search function will only
# display the titles of matching documents, and no excerpt from the matching contents.
html_copy_source = False

# If true, links to the reST sources are added to the pages.
html_show_sourcelink = False

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
html_show_sphinx = False

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
html_file_suffix = '.html'

# Output file base name for HTML help builder.
htmlhelp_basename = 'mpchc2doc'

# -- Options for LaTeX output --------------------------------------------------

latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',

# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',

# Additional stuff for the LaTeX preamble.
#'preamble': '',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
  ('index', 'mpc-hc.tex', u'MPC-HC Documentation',
   u'MPC-HC Team', 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False

# If true, show page references after internal links.
#latex_show_pagerefs = False

# If true, show URL addresses after external links.
#latex_show_urls = False

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_domain_indices = True

# -- Options for manual page output --------------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    ('index', 'mpc-hc', u'an advanced general-purpose video player',
     [u'MPC-HC Team'], 1)
]

# If true, show URL addresses after external links.
#man_show_urls = False

# -- Options for Texinfo output ------------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
  ('index', 'mpc-hc', u'MPC-HC Documentation', u'The mpc-hc Team',
   'mpc-hc', 'One line description of project.', 'Miscellaneous'),
]

# Documents to append as an appendix to all manuals.
#texinfo_appendices = []

# If false, no module index is generated.
#texinfo_domain_indices = True

# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
<commit_msg>conf.py cosmetics<commit_after># -*- coding: utf-8 -*-
#
# MPC-HC documentation build configuration file, created by
# sphinx-quickstart on Sat Mar 17 17:15:28 2012.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

import sys, os

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))

# -- General configuration -----------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.ifconfig', 'sphinx.ext.extlinks']

extlinks = {'issue': ('https://sourceforge.net/apps/trac/mpc-hc/ticket/%s', 'Ticket #')}

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
source_encoding = 'utf-8'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'MPC-HC'
copyright = u'MPC-HC Team'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.6.4'
# The full version, including alpha/beta/rc tags.
release = '1.6.4.6052'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []

# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# Trim spaces before footnote references that are necessary for the reST
# parser to recognize the footnote, but do not look too nice in the output.
trim_footnote_reference_space = True

# -- Options for HTML output ---------------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'default'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
html_title = 'MPC-HC project'

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = 'logo.png'

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
html_favicon = 'favicon.ico'

html_add_permalinks = False

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, the reST sources are included in the HTML build as _sources/name.
# The default is True.
#
# Warning
# If this config value is set to False, the JavaScript search function will only
# display the titles of matching documents, and no excerpt from the matching contents.
html_copy_source = False

# If true, links to the reST sources are added to the pages.
html_show_sourcelink = False

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
html_show_sphinx = False

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
html_file_suffix = '.html'

# Output file base name for HTML help builder.
htmlhelp_basename = 'mpchc2doc'<|endoftext|>


<commit_before><commit_msg>style: remove unnecessary comprehension<commit_after><|endoftext|>