<commit_before># -*- coding: utf-8 -*-
import socket
import struct
import errno
import json
from binascii import hexlify, unhexlify

from django.db import models

try:
    from django.utils.timezone import now as dt_now
except ImportError:
    import datetime
    dt_now = datetime.datetime.now

from django_fields.fields import EncryptedCharField
import OpenSSL

from .exceptions import NotificationPayloadSizeExceeded, InvalidPassPhrase
from .settings import get_setting

class BaseService(models.Model):
    """
    A base service class intended to be subclassed.
    """
    name = models.CharField(max_length=255)
    hostname = models.CharField(max_length=255)
    PORT = 0  # Should be overriden by subclass
    connection = None

    def _connect(self, certificate, private_key, passphrase=None):
        """
        Establishes an encrypted SSL socket connection to the service.
        After connecting the socket can be written to or read from.
        """
        # ssl in Python < 3.2 does not support certificates/keys as strings.
        # See http://bugs.python.org/issue3823
        # Therefore pyOpenSSL which lets us do this is a dependancy.
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, certificate)
        args = [OpenSSL.crypto.FILETYPE_PEM, private_key]
        if passphrase is not None:
            args.append(str(passphrase))
        try:
            pkey = OpenSSL.crypto.load_privatekey(*args)
        except OpenSSL.crypto.Error:
            raise InvalidPassPhrase
        context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv3_METHOD)
        context.use_certificate(cert)
        context.use_privatekey(pkey)
        self.connection = OpenSSL.SSL.Connection(context, sock)
        self.connection.connect((self.hostname, self.PORT))
        self.connection.set_connect_state()
        self.connection.do_handshake()

    def _disconnect(self):
        """
        Closes the SSL socket connection.
        """
        if self.connection is not None:
            self.connection.shutdown()
            self.connection.close()

    class Meta:
        abstract = True

class APNService(BaseService):
    """
    Represents an Apple Notification Service either for live
    or sandbox notifications.

    `private_key` is optional if both the certificate and key are provided in
    `certificate`.
    """
    certificate = models.TextField()
    private_key = models.TextField()
    passphrase = EncryptedCharField(
        null=True, blank=True, help_text='Passphrase for the private key',
        block_type='MODE_CBC')

    PORT = 2195
    fmt = '!cH32sH%ds'

    def _connect(self):
        """
        Establishes an encrypted SSL socket connection to the service.
        After connecting the socket can be written to or read from.
        """
        return super(APNService, self)._connect(self.certificate, self.private_key, self.passphrase)

    def push_notification_to_devices(self, notification, devices=None, chunk_size=100):
        """
        Sends the specific notification to devices.
        if `devices` is not supplied, all devices in the `APNService`'s device
        list will be sent the notification.
        """
        if devices is None:
            devices = self.device_set.filter(is_active=True)
        self._write_message(notification, devices, chunk_size)

    def _write_message(self, notification, devices, chunk_size):
        """
        Writes the message for the supplied devices to
        the APN Service SSL socket.
        """
        if not isinstance(notification, Notification):
            raise TypeError('notification should be an instance of ios_notifications.models.Notification')

        if not isinstance(chunk_size, int) or chunk_size < 1:
            raise ValueError('chunk_size must be an integer greater than zero.')

        payload = notification.payload

        # Split the devices into manageable chunks.
        # Chunk sizes being determined by the `chunk_size` arg.
        device_length = devices.count() if isinstance(devices, models.query.QuerySet) else len(devices)
        chunks = [devices[i:i + chunk_size] for i in xrange(0, device_length, chunk_size)]

        for index in xrange(len(chunks)):
            chunk = chunks[index]
            self._connect()

            for device in chunk:
                if not device.is_active:
                    continue
                try:
                    self.connection.send(self.pack_message(payload, device))
                except (OpenSSL.SSL.WantWriteError, socket.error) as e:
                    if isinstance(e, socket.error) and isinstance(e.args, tuple) and e.args[0] != errno.EPIPE:
                        raise e  # Unexpected exception, raise it.
                    self._disconnect()
                    i = chunk.index(device)
                    self.set_devices_last_notified_at(chunk[:i])
                    # Start again from the next device.
                    # We start from the next device since
                    # if the device no longer accepts push notifications from your app
                    # and you send one to it anyways, Apple immediately drops the connection to your APNS socket.
                    # http://stackoverflow.com/a/13332486/1025116
                    self._write_message(notification, chunk[i + 1:], chunk_size)

            self._disconnect()

            self.set_devices_last_notified_at(chunk)

        if notification.pk or notification.persist:
            notification.last_sent_at = dt_now()
            notification.save()

    def set_devices_last_notified_at(self, devices):
        # Rather than do a save on every object,
        # fetch another queryset and use it to update
        # the devices in a single query.
        # Since the devices argument could be a sliced queryset
        # we can't rely on devices.update() even if devices is
        # a queryset object.
        Device.objects.filter(pk__in=[d.pk for d in devices]).update(last_notified_at=dt_now())

    def pack_message(self, payload, device):
        """
        Converts a notification payload into binary form.
        """
        if len(payload) > 256:
            raise NotificationPayloadSizeExceeded
        if not isinstance(device, Device):
            raise TypeError('device must be an instance of ios_notifications.models.Device')

        msg = struct.pack(self.fmt % len(payload), chr(0), 32, unhexlify(device.token), len(payload), payload)
        return msg

    def __unicode__(self):
        return self.name

    class Meta:
        unique_together = ('name', 'hostname')

class Notification(models.Model):
    """
    Represents a notification which can be pushed to an iOS device.
    """
    service = models.ForeignKey(APNService)
    message = models.CharField(max_length=200, blank=True, help_text='Alert message to display to the user. Leave empty if no alert should be displayed to the user.')
    badge = models.PositiveIntegerField(null=True, blank=True, help_text='New application icon badge number. Set to None if the badge number must not be changed.')
    slient = models.BooleanField(null=True, blank=True, help_text='set True to send a slient notification')
    sound = models.CharField(max_length=30, blank=True, help_text='Name of the sound to play. Leave empty if no sound should be played.')
    created_at = models.DateTimeField(auto_now_add=True)
    last_sent_at = models.DateTimeField(null=True, blank=True)
    custom_payload = models.CharField(max_length=240, blank=True, help_text='JSON representation of an object containing custom payload.')
    loc_payload = models.CharField(max_length=240, blank=True, help_text="JSON representation of an object containing the localization payload.")

    def __init__(self, *args, **kwargs):
        self.persist = get_setting('IOS_NOTIFICATIONS_PERSIST_NOTIFICATIONS')
        super(Notification, self).__init__(*args, **kwargs)

    def __unicode__(self):
        return u'%s%s%s' % (self.message, ' ' if self.message and self.custom_payload else '', self.custom_payload)

    @property
    def extra(self):
        """
        The extra property is used to specify custom payload values
        outside the Apple-reserved aps namespace
        http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1
        """
        return json.loads(self.custom_payload) if self.custom_payload else None

    @extra.setter
    def extra(self, value):
        if value is None:
            self.custom_payload = ''
        else:
            if not isinstance(value, dict):
                raise TypeError('must be a valid Python dictionary')
            self.custom_payload = json.dumps(value)  # Raises a TypeError if can't be serialized

    @property
    def loc_data(self):
        """
        The loc_data property is used to specify localization paramaters within the 'alert' aps key.
        https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html
        """
        return json.loads(self.loc_payload) if self.loc_payload else None

    def set_loc_data(self, loc_key, loc_args, action_loc_key=None):
        if not isinstance(loc_args, (list, tuple)):
            raise TypeError("loc_args must be a list or tuple.")

        loc_data = {
            "loc-key": unicode(loc_key),
            "loc-args": [unicode(a) for a in loc_args],
        }

        if action_loc_key:
            loc_data['action-loc-key'] = unicode(action_loc_key)

        self.loc_payload = json.dumps(loc_data)

    def push_to_all_devices(self):
        """
        Pushes this notification to all active devices using the
        notification's related APN service.
        """
        self.service.push_notification_to_devices(self)

    def is_valid_length(self):
        """
        Determines if a notification payload is a valid length.

        returns bool
        """
        return len(self.payload) <= 256

    @property
    def payload(self):
        aps = {}

        loc_data = self.loc_data
        if loc_data:
            aps['alert'] = loc_data
        elif self.message:
            aps['alert'] = self.message

        if self.badge is not None:
            aps['badge'] = self.badge
        if self.sound:
            aps['sound'] = self.sound
        if self.slient:
            aps['content-available'] = 1
        message = {'aps': aps}
        extra = self.extra
        if extra is not None:
            message.update(extra)
        payload = json.dumps(message, separators=(',', ':'), ensure_ascii=False).encode('utf8')
        return payload

class Device(models.Model):
    """
    Represents an iOS device with unique token.
    """
    token = models.CharField(max_length=64, blank=False, null=False)
    is_active = models.BooleanField(default=True)
    deactivated_at = models.DateTimeField(null=True, blank=True)
    service = models.ForeignKey(APNService)
    users = models.ManyToManyField(get_setting('AUTH_USER_MODEL'), null=True, blank=True, related_name='ios_devices')
    added_at = models.DateTimeField(auto_now_add=True)
    last_notified_at = models.DateTimeField(null=True, blank=True)
    platform = models.CharField(max_length=30, blank=True, null=True)
    display = models.CharField(max_length=30, blank=True, null=True)
    os_version = models.CharField(max_length=20, blank=True, null=True)

    def push_notification(self, notification):
        """
        Pushes a ios_notifications.models.Notification instance to an the device.
        For more details see http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html
        """
        if not isinstance(notification, Notification):
            raise TypeError('notification should be an instance of ios_notifications.models.Notification')

        self.service.push_notification_to_devices(notification, [self])

    def __unicode__(self):
        return self.token

    class Meta:
        unique_together = ('token', 'service')

class FeedbackService(BaseService):
    """
    The service provided by Apple to inform you of devices which no longer have your app installed
    and to which notifications have failed a number of times. Use this class to check the feedback
    service and deactivate any devices it informs you about.

    https://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW3
    """
    apn_service = models.ForeignKey(APNService)

    PORT = 2196

    fmt = '!lh32s'

    def _connect(self):
        """
        Establishes an encrypted socket connection to the feedback service.
        """
        return super(FeedbackService, self)._connect(self.apn_service.certificate, self.apn_service.private_key, self.apn_service.passphrase)

    def call(self):
        """
        Calls the feedback service and deactivates any devices the feedback service mentions.
        """
        self._connect()
        device_tokens = []
        try:
            while True:
                data = self.connection.recv(38)  # 38 being the length in bytes of the binary format feedback tuple.
                timestamp, token_length, token = struct.unpack(self.fmt, data)
                device_token = hexlify(token)
                device_tokens.append(device_token)
        except OpenSSL.SSL.ZeroReturnError:
            # Nothing to receive
            pass
        finally:
            self._disconnect()
        devices = Device.objects.filter(token__in=device_tokens, service=self.apn_service)
        devices.update(is_active=False, deactivated_at=dt_now())
        return devices.count()

    def __unicode__(self):
        return self.name

    class Meta:
        unique_together = ('name', 'hostname')
<commit_msg>fix spell error: the field should be silent not slient<commit_after># -*- coding: utf-8 -*-
import socket
import struct
import errno
import json
from binascii import hexlify, unhexlify

from django.db import models

try:
    from django.utils.timezone import now as dt_now
except ImportError:
    import datetime
    dt_now = datetime.datetime.now

from django_fields.fields import EncryptedCharField
import OpenSSL

from .exceptions import NotificationPayloadSizeExceeded, InvalidPassPhrase
from .settings import get_setting

class BaseService(models.Model):
    """
    A base service class intended to be subclassed.
    """
    name = models.CharField(max_length=255)
    hostname = models.CharField(max_length=255)
    PORT = 0  # Should be overriden by subclass
    connection = None

    def _connect(self, certificate, private_key, passphrase=None):
        """
        Establishes an encrypted SSL socket connection to the service.
        After connecting the socket can be written to or read from.
        """
        # ssl in Python < 3.2 does not support certificates/keys as strings.
        # See http://bugs.python.org/issue3823
        # Therefore pyOpenSSL which lets us do this is a dependancy.
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, certificate)
        args = [OpenSSL.crypto.FILETYPE_PEM, private_key]
        if passphrase is not None:
            args.append(str(passphrase))
        try:
            pkey = OpenSSL.crypto.load_privatekey(*args)
        except OpenSSL.crypto.Error:
            raise InvalidPassPhrase
        context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv3_METHOD)
        context.use_certificate(cert)
        context.use_privatekey(pkey)
        self.connection = OpenSSL.SSL.Connection(context, sock)
        self.connection.connect((self.hostname, self.PORT))
        self.connection.set_connect_state()
        self.connection.do_handshake()

    def _disconnect(self):
        """
        Closes the SSL socket connection.
        """
        if self.connection is not None:
            self.connection.shutdown()
            self.connection.close()

    class Meta:
        abstract = True

class APNService(BaseService):
    """
    Represents an Apple Notification Service either for live
    or sandbox notifications.

    `private_key` is optional if both the certificate and key are provided in
    `certificate`.
    """
    certificate = models.TextField()
    private_key = models.TextField()
    passphrase = EncryptedCharField(
        null=True, blank=True, help_text='Passphrase for the private key',
        block_type='MODE_CBC')

    PORT = 2195
    fmt = '!cH32sH%ds'

    def _connect(self):
        """
        Establishes an encrypted SSL socket connection to the service.
        After connecting the socket can be written to or read from.
        """
        return super(APNService, self)._connect(self.certificate, self.private_key, self.passphrase)

    def push_notification_to_devices(self, notification, devices=None, chunk_size=100):
        """
        Sends the specific notification to devices.
        if `devices` is not supplied, all devices in the `APNService`'s device
        list will be sent the notification.
        """
        if devices is None:
            devices = self.device_set.filter(is_active=True)
        self._write_message(notification, devices, chunk_size)

    def _write_message(self, notification, devices, chunk_size):
        """
        Writes the message for the supplied devices to
        the APN Service SSL socket.
        """
        if not isinstance(notification, Notification):
            raise TypeError('notification should be an instance of ios_notifications.models.Notification')

        if not isinstance(chunk_size, int) or chunk_size < 1:
            raise ValueError('chunk_size must be an integer greater than zero.')

        payload = notification.payload

        # Split the devices into manageable chunks.
        # Chunk sizes being determined by the `chunk_size` arg.
        device_length = devices.count() if isinstance(devices, models.query.QuerySet) else len(devices)
        chunks = [devices[i:i + chunk_size] for i in xrange(0, device_length, chunk_size)]

        for index in xrange(len(chunks)):
            chunk = chunks[index]
            self._connect()

            for device in chunk:
                if not device.is_active:
                    continue
                try:
                    self.connection.send(self.pack_message(payload, device))
                except (OpenSSL.SSL.WantWriteError, socket.error) as e:
                    if isinstance(e, socket.error) and isinstance(e.args, tuple) and e.args[0] != errno.EPIPE:
                        raise e  # Unexpected exception, raise it.
                    self._disconnect()
                    i = chunk.index(device)
                    self.set_devices_last_notified_at(chunk[:i])
                    # Start again from the next device.
                    # We start from the next device since
                    # if the device no longer accepts push notifications from your app
                    # and you send one to it anyways, Apple immediately drops the connection to your APNS socket.
                    # http://stackoverflow.com/a/13332486/1025116
                    self._write_message(notification, chunk[i + 1:], chunk_size)

            self._disconnect()

            self.set_devices_last_notified_at(chunk)

        if notification.pk or notification.persist:
            notification.last_sent_at = dt_now()
            notification.save()

    def set_devices_last_notified_at(self, devices):
        # Rather than do a save on every object,
        # fetch another queryset and use it to update
        # the devices in a single query.
        # Since the devices argument could be a sliced queryset
        # we can't rely on devices.update() even if devices is
        # a queryset object.
        Device.objects.filter(pk__in=[d.pk for d in devices]).update(last_notified_at=dt_now())

    def pack_message(self, payload, device):
        """
        Converts a notification payload into binary form.
        """
        if len(payload) > 256:
            raise NotificationPayloadSizeExceeded
        if not isinstance(device, Device):
            raise TypeError('device must be an instance of ios_notifications.models.Device')

        msg = struct.pack(self.fmt % len(payload), chr(0), 32, unhexlify(device.token), len(payload), payload)
        return msg

    def __unicode__(self):
        return self.name

    class Meta:
        unique_together = ('name', 'hostname')

class Notification(models.Model):
    """
    Represents a notification which can be pushed to an iOS device.
    """
    service = models.ForeignKey(APNService)
    message = models.CharField(max_length=200, blank=True, help_text='Alert message to display to the user. Leave empty if no alert should be displayed to the user.')
    badge = models.PositiveIntegerField(null=True, blank=True, help_text='New application icon badge number. Set to None if the badge number must not be changed.')
    silent = models.BooleanField(null=True, blank=True, help_text='set True to send a silent notification')
    sound = models.CharField(max_length=30, blank=True, help_text='Name of the sound to play. Leave empty if no sound should be played.')
    created_at = models.DateTimeField(auto_now_add=True)
    last_sent_at = models.DateTimeField(null=True, blank=True)
    custom_payload = models.CharField(max_length=240, blank=True, help_text='JSON representation of an object containing custom payload.')
    loc_payload = models.CharField(max_length=240, blank=True, help_text="JSON representation of an object containing the localization payload.")

    def __init__(self, *args, **kwargs):
        self.persist = get_setting('IOS_NOTIFICATIONS_PERSIST_NOTIFICATIONS')
        super(Notification, self).__init__(*args, **kwargs)

    def __unicode__(self):
        return u'%s%s%s' % (self.message, ' ' if self.message and self.custom_payload else '', self.custom_payload)

    @property
    def extra(self):
        """
        The extra property is used to specify custom payload values
        outside the Apple-reserved aps namespace
        http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1
        """
        return json.loads(self.custom_payload) if self.custom_payload else None

    @extra.setter
    def extra(self, value):
        if value is None:
            self.custom_payload = ''
        else:
            if not isinstance(value, dict):
                raise TypeError('must be a valid Python dictionary')
            self.custom_payload = json.dumps(value)  # Raises a TypeError if can't be serialized

    @property
    def loc_data(self):
        """
        The loc_data property is used to specify localization paramaters within the 'alert' aps key.
        https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html
        """
        return json.loads(self.loc_payload) if self.loc_payload else None

    def set_loc_data(self, loc_key, loc_args, action_loc_key=None):
        if not isinstance(loc_args, (list, tuple)):
            raise TypeError("loc_args must be a list or tuple.")

        loc_data = {
            "loc-key": unicode(loc_key),
            "loc-args": [unicode(a) for a in loc_args],
        }

        if action_loc_key:
            loc_data['action-loc-key'] = unicode(action_loc_key)

        self.loc_payload = json.dumps(loc_data)

    def push_to_all_devices(self):
        """
        Pushes this notification to all active devices using the
        notification's related APN service.
        """
        self.service.push_notification_to_devices(self)

    def is_valid_length(self):
        """
        Determines if a notification payload is a valid length.

        returns bool
        """
        return len(self.payload) <= 256

    @property
    def payload(self):
        aps = {}

        loc_data = self.loc_data
        if loc_data:
            aps['alert'] = loc_data
        elif self.message:
            aps['alert'] = self.message

        if self.badge is not None:
            aps['badge'] = self.badge
        if self.sound:
            aps['sound'] = self.sound
        if self.silent:
            aps['content-available'] = 1
        message = {'aps': aps}
        extra = self.extra
        if extra is not None:
            message.update(extra)
        payload = json.dumps(message, separators=(',', ':'), ensure_ascii=False).encode('utf8')
        return payload

class Device(models.Model):
    """
    Represents an iOS device with unique token.
    """
    token = models.CharField(max_length=64, blank=False, null=False)
    is_active = models.BooleanField(default=True)
    deactivated_at = models.DateTimeField(null=True, blank=True)
    service = models.ForeignKey(APNService)
    users = models.ManyToManyField(get_setting('AUTH_USER_MODEL'), null=True, blank=True, related_name='ios_devices')
    added_at = models.DateTimeField(auto_now_add=True)
    last_notified_at = models.DateTimeField(null=True, blank=True)
    platform = models.CharField(max_length=30, blank=True, null=True)
    display = models.CharField(max_length=30, blank=True, null=True)
    os_version = models.CharField(max_length=20, blank=True, null=True)

    def push_notification(self, notification):
        """
        Pushes a ios_notifications.models.Notification instance to an the device.
        For more details see http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html
        """
        if not isinstance(notification, Notification):
            raise TypeError('notification should be an instance of ios_notifications.models.Notification')

        self.service.push_notification_to_devices(notification, [self])

    def __unicode__(self):
        return self.token

    class Meta:
        unique_together = ('token', 'service')

class FeedbackService(BaseService):
    """
    The service provided by Apple to inform you of devices which no longer have your app installed
    and to which notifications have failed a number of times. Use this class to check the feedback
    service and deactivate any devices it informs you about.

    https://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW3
    """
    apn_service = models.ForeignKey(APNService)

    PORT = 2196

    fmt = '!lh32s'

    def _connect(self):
        """
        Establishes an encrypted socket connection to the feedback service.
        """
        return super(FeedbackService, self)._connect(self.apn_service.certificate, self.apn_service.private_key, self.apn_service.passphrase)

    def call(self):
        """
        Calls the feedback service and deactivates any devices the feedback service mentions.
        """
        self._connect()
        device_tokens = []
        try:
            while True:
                data = self.connection.recv(38)  # 38 being the length in bytes of the binary format feedback tuple.
                timestamp, token_length, token = struct.unpack(self.fmt, data)
                device_token = hexlify(token)
                device_tokens.append(device_token)
        except OpenSSL.SSL.ZeroReturnError:
            # Nothing to receive
            pass
        finally:
            self._disconnect()
        devices = Device.objects.filter(token__in=device_tokens, service=self.apn_service)
        devices.update(is_active=False, deactivated_at=dt_now())
        return devices.count()

    def __unicode__(self):
        return self.name

    class Meta:
        unique_together = ('name', 'hostname')
<|endoftext|>


<commit_before># coding: utf-8
# Author: Milan Kubik

class PluginConfigError(ValueError):
    pass

class PluginBase:  # pylint: disable=R0903
    """Dynamic Directory plugin base class

    All plugins must inherit from this class and
    implement the __call__ method.

    This method is later used by the plugin manager
    instance to call the plugin.
    """

    def __call__(self, host):
        """Call magic method

        The host argument is the host dictionary.
        The dictionary is passed directly with all
        its keys. This allows plugins to work with
        the host based on its role.
        """
        raise RuntimeError('You need to subclass this method.')
<commit_msg>Update base.py<commit_after># coding: utf-8
# Author: Milan Kubik

class PluginConfigError(ValueError):
    pass

class PluginBase:  # pylint: disable=R0903
    """Dynamic Directory plugin base class

    All plugins must inherit from this class and
    implement the __call__ method.

    This method is later used by the plugin manager
    instance to call the plugin.
    """

    def __call__(self, host):
        """Call magic method

        The host argument is the host dictionary.
        The dictionary is passed directly with all
        its keys. This allows plugins to work with
        the host based on its role.
        """
        raise RuntimeError('You need to implement this method in a subclass.')
<|endoftext|>


<commit_before>from .widgets import *

# Register comm target for Javascript initialized widgets..
from IPython import get_ipython
ip = get_ipython()
if ip is not None:
    ip.kernel.comm_manager.register_target('ipython.widget', Widget.handle_comm_opened)
<commit_msg>Poke the CI<commit_after>from .widgets import *

# Register a comm target for Javascript initialized widgets..
from IPython import get_ipython
ip = get_ipython()
if ip is not None:
    ip.kernel.comm_manager.register_target('ipython.widget', Widget.handle_comm_opened)
<|endoftext|>


<commit_before>"""
Standard config for loading Django settings from Docker
"""
from __future__ import absolute_import, unicode_literals

import logging
import logging.handlers
import os
import socket
import sys
import warnings

# Copy BASE_DIR from the main configuration
BASE_DIR = getattr(
    sys.modules[os.environ['DJANGO_SETTINGS_MODULE']], 'BASE_DIR', None)

assert BASE_DIR, "BASE_DIR setting must be defined."

# Environment
ENVIRONMENT = os.environ.get('ENVIRONMENT', None)
if ENVIRONMENT == 'dev_local':
    DEVNAME = os.environ['DEVNAME']

# Debugging
DEBUG = ENVIRONMENT in ('dev_local', 'dev', 'test')
TEMPLATE_DEBUG = DEBUG
TASTYPIE_FULL_DEBUG = DEBUG

# Trust nginx
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Allowed hosts, Site domain and URL
ALLOWED_HOSTS = ["*"]
try:
    MY_SITE_DOMAIN = os.environ.get('SITE_DOMAIN', '').split('|')[0]
    SITE_URL = '{0}://{1}'.format(os.environ.get('SITE_PROTOCOL', ''),
                                MY_SITE_DOMAIN)
except KeyError:
    pass

# Databases
DATABASES = {}

if 'DB_DEFAULT_URL' in os.environ:
    import dj_database_url  # pylint:disable=wrong-import-position

    if ENVIRONMENT:
        DATABASES = {
            'default': dj_database_url.parse(os.environ['DB_DEFAULT_URL'])
        }

# Elasticsearch
ELASTICSEARCH_INDEX_NAME = os.environ.get('ELASTICSEARCH_INDEX_NAME')
ELASTICSEARCH_URL = os.environ.get('ELASTICSEARCH_URLS', '').split('|')

# Memcache
try:
    # pylint:disable=unused-import,import-error,wrong-import-position
    import memcache
    # pylint:enable=unused-import,import-error,wrong-import-position

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': os.environ['MEMCACHE_HOSTS'].split('|'),
            'KEY_PREFIX': os.environ['MEMCACHE_PREFIX'],
        },
    }

except (ImportError, KeyError):
    pass

# Email
EMAIL_HOST = os.environ.get('EMAIL_HOST', 'localhost')
EMAIL_PORT = os.environ.get('EMAIL_PORT', 25)
DEFAULT_FROM_EMAIL = 'do.not.reply@%s' % MY_SITE_DOMAIN

# Secret key
if not hasattr(
        sys.modules[os.environ['DJANGO_SETTINGS_MODULE']],
        'SECRET_KEY'
):
    warnings.warn((
        "Please define SECRET_KEY before importing {0}, as a fallback "
        "for when the environment variable is not available."
    ).format(__name__))
try:
    if os.environ.get('SECRET_KEY'):
        SECRET_KEY = os.environ.pop('SECRET_KEY')
except KeyError:
    pass

# Logging
LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        },
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue'
        },
    },
    'formatters': {
        'ixa': {
            '()': 'ixdjango.logging_.IXAFormatter',
        },
    },
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler'
        },
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'gunicorn': {
            'level': 'DEBUG' if ENVIRONMENT == 'dev_local' else 'INFO',
            'handlers': [],
            'propagate': True,
        },
        'django.request': {
            'level': 'INFO',
            'handlers': ['mail_admins'],
            'propagate': True,
        },
        'django.db': {
            'level': 'INFO' if ENVIRONMENT == 'dev_local' else 'WARNING',
            'handlers': ['mail_admins'],
            'propagate': True,
        },
        'django.security': {
            'level': 'INFO',
            'handlers': ['mail_admins'],
            'propagate': True,
        },
        'requests': {
            'level': 'INFO' if ENVIRONMENT == 'dev_local' else 'WARNING',
            'handlers': [],
            'propagate': True,
        },
        'newrelic': {
            'level': 'INFO',
            'handlers': [],
            'propagate': True,
        },
    },
}

if ENVIRONMENT not in (None, 'dev_local',):
    # configure logstash on sites other than dev_local
    # N.B. Travis is also running as dev_local

    LOGGING_ADDRESS = (os.environ.get('SYSLOG_SERVER', 'localhost'),
                       int(os.environ.get('SYSLOG_PORT',
                                          logging.handlers.SYSLOG_UDP_PORT)))
    LOGGING_SOCKTYPE = ({
        'tcp': socket.SOCK_STREAM,
        'udp': socket.SOCK_DGRAM,
    })[os.environ.get('SYSLOG_PROTO', 'udp')]

    LOGGING['handlers']['logstash'] = {
        'level': 'DEBUG' if DEBUG else 'INFO',
        'class': 'ixdjango.logging_.SysLogHandler',
        'address': LOGGING_ADDRESS,
        'socktype': LOGGING_SOCKTYPE,
        'formatter': 'ixa',
    }

    # send all logs to logstash
    for logger in LOGGING['loggers'].values():
        logger['handlers'].insert(0, 'logstash')

    CONSOLE_OUTPUT_DISABLED = True  # Default; may be overridden

else:
    CONSOLE_OUTPUT_DISABLED = False  # Default; may be overridden

CONSOLE_OUTPUT_DISABLED = bool(os.environ.get('CONSOLE_OUTPUT_DISABLED',
                                              CONSOLE_OUTPUT_DISABLED))

if not CONSOLE_OUTPUT_DISABLED:
    # show request logs on the console
    for logger in LOGGING['loggers'].values():
        logger['handlers'].insert(0, 'console')

# File locations
if ENVIRONMENT == 'dev_local':
    STORAGE_DIR = BASE_DIR
    NGINX_STATIC_DIR = None

else:
    STORAGE_DIR = '/storage'
    NGINX_STATIC_DIR = '/static'

STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MEDIA_ROOT = os.path.join(STORAGE_DIR, 'media')

STATIC_URL = '/static/'
MEDIA_URL = '/media/'
<commit_msg>Remove deprecated template setting<commit_after>"""
Standard config for loading Django settings from Docker
"""
from __future__ import absolute_import, unicode_literals

import logging
import logging.handlers
import os
import socket
import sys
import warnings

# Copy BASE_DIR from the main configuration
BASE_DIR = getattr(
    sys.modules[os.environ['DJANGO_SETTINGS_MODULE']], 'BASE_DIR', None)

assert BASE_DIR, "BASE_DIR setting must be defined."

# Environment
ENVIRONMENT = os.environ.get('ENVIRONMENT', None)
if ENVIRONMENT == 'dev_local':
    DEVNAME = os.environ['DEVNAME']

# Debugging
DEBUG = ENVIRONMENT in ('dev_local', 'dev', 'test')
TASTYPIE_FULL_DEBUG = DEBUG

# Trust nginx
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Allowed hosts, Site domain and URL
ALLOWED_HOSTS = ["*"]
try:
    MY_SITE_DOMAIN = os.environ.get('SITE_DOMAIN', '').split('|')[0]
    SITE_URL = '{0}://{1}'.format(os.environ.get('SITE_PROTOCOL', ''),
                                MY_SITE_DOMAIN)
except KeyError:
    pass

# Databases
DATABASES = {}

if 'DB_DEFAULT_URL' in os.environ:
    import dj_database_url  # pylint:disable=wrong-import-position

    if ENVIRONMENT:
        DATABASES = {
            'default': dj_database_url.parse(os.environ['DB_DEFAULT_URL'])
        }

# Elasticsearch
ELASTICSEARCH_INDEX_NAME = os.environ.get('ELASTICSEARCH_INDEX_NAME')
ELASTICSEARCH_URL = os.environ.get('ELASTICSEARCH_URLS', '').split('|')

# Memcache
try:
    # pylint:disable=unused-import,import-error,wrong-import-position
    import memcache
    # pylint:enable=unused-import,import-error,wrong-import-position

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': os.environ['MEMCACHE_HOSTS'].split('|'),
            'KEY_PREFIX': os.environ['MEMCACHE_PREFIX'],
        },
    }

except (ImportError, KeyError):
    pass

# Email
EMAIL_HOST = os.environ.get('EMAIL_HOST', 'localhost')
EMAIL_PORT = os.environ.get('EMAIL_PORT', 25)
DEFAULT_FROM_EMAIL = 'do.not.reply@%s' % MY_SITE_DOMAIN

# Secret key
if not hasattr(
        sys.modules[os.environ['DJANGO_SETTINGS_MODULE']],
        'SECRET_KEY'
):
    warnings.warn((
        "Please define SECRET_KEY before importing {0}, as a fallback "
        "for when the environment variable is not available."
    ).format(__name__))
try:
    if os.environ.get('SECRET_KEY'):
        SECRET_KEY = os.environ.pop('SECRET_KEY')
except KeyError:
    pass

# Logging
LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        },
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue'
        },
    },
    'formatters': {
        'ixa': {
            '()': 'ixdjango.logging_.IXAFormatter',
        },
    },
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler'
        },
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'gunicorn': {
            'level': 'DEBUG' if ENVIRONMENT == 'dev_local' else 'INFO',
            'handlers': [],
            'propagate': True,
        },
        'django.request': {
            'level': 'INFO',
            'handlers': ['mail_admins'],
            'propagate': True,
        },
        'django.db': {
            'level': 'INFO' if ENVIRONMENT == 'dev_local' else 'WARNING',
            'handlers': ['mail_admins'],
            'propagate': True,
        },
        'django.security': {
            'level': 'INFO',
            'handlers': ['mail_admins'],
            'propagate': True,
        },
        'requests': {
            'level': 'INFO' if ENVIRONMENT == 'dev_local' else 'WARNING',
            'handlers': [],
            'propagate': True,
        },
        'newrelic': {
            'level': 'INFO',
            'handlers': [],
            'propagate': True,
        },
    },
}

if ENVIRONMENT not in (None, 'dev_local',):
    # configure logstash on sites other than dev_local
    # N.B. Travis is also running as dev_local

    LOGGING_ADDRESS = (os.environ.get('SYSLOG_SERVER', 'localhost'),
                       int(os.environ.get('SYSLOG_PORT',
                                          logging.handlers.SYSLOG_UDP_PORT)))
    LOGGING_SOCKTYPE = ({
        'tcp': socket.SOCK_STREAM,
        'udp': socket.SOCK_DGRAM,
    })[os.environ.get('SYSLOG_PROTO', 'udp')]

    LOGGING['handlers']['logstash'] = {
        'level': 'DEBUG' if DEBUG else 'INFO',
        'class': 'ixdjango.logging_.SysLogHandler',
        'address': LOGGING_ADDRESS,
        'socktype': LOGGING_SOCKTYPE,
        'formatter': 'ixa',
    }

    # send all logs to logstash
    for logger in LOGGING['loggers'].values():
        logger['handlers'].insert(0, 'logstash')

    CONSOLE_OUTPUT_DISABLED = True  # Default; may be overridden

else:
    CONSOLE_OUTPUT_DISABLED = False  # Default; may be overridden

CONSOLE_OUTPUT_DISABLED = bool(os.environ.get('CONSOLE_OUTPUT_DISABLED',
                                              CONSOLE_OUTPUT_DISABLED))

if not CONSOLE_OUTPUT_DISABLED:
    # show request logs on the console
    for logger in LOGGING['loggers'].values():
        logger['handlers'].insert(0, 'console')

# File locations
if ENVIRONMENT == 'dev_local':
    STORAGE_DIR = BASE_DIR
    NGINX_STATIC_DIR = None

else:
    STORAGE_DIR = '/storage'
    NGINX_STATIC_DIR = '/static'

STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MEDIA_ROOT = os.path.join(STORAGE_DIR, 'media')

STATIC_URL = '/static/'
MEDIA_URL = '/media/'
<|endoftext|>


<commit_before>import sys
import os
import getopt
import tempfile
import shutil
import ConfigParser

from os.path import abspath, join, exists, isdir, isfile, expanduser

python = "python2.6"
distbase = "jarn.com:/home/psol/dist"
distdefault = "public"

version = "mkrelease 0.20"
usage = """\
Usage: mkrelease [-CTSDK] [-cz] [-d dist-location] [svn-url|svn-sandbox]
       mkrelease [-CTSDK] [-cz] [-p [-s [-i identity]]] [svn-url|svn-sandbox]

Release an sdist egg.

Options:
  -C                Do not checkin release-relevant files from the sandbox.
  -T                Do not tag the release in subversion.
  -S                Do not scp the release tarball to dist-location.
  -D                Dry-run; equivalent to -CTS.
  -K                Keep the temporary build directory.

  -c                Assume a codespeak.net-style repository layout.
  -z                Create a zip archive instead of the default tar.gz.

  -d dist-location  An scp destination specification.

  -p                Upload the release to PyPI.
  -s                Sign the release tarball with GnuPG.
  -i identity       The GnuPG identity to sign with.

  svn-url           A URL with protocol svn, svn+ssh, http, https, or file.
  svn-sandbox       A local directory; defaults to the current working
                    directory.

Files:
  /etc/mkrelease    Global configuration file.
  ~/.mkrelease      Per user configuration file.

  The configuration file consists of sections, led by a "[section]" header
  and followed by "name = value" entries.

  The [defaults] section has the following options:

  python            The Python executable used; defaults to %(python)s.
  distbase          The value prepended if dist-location does not contain a
                    host part; defaults to %(distbase)s.
  distdefault       The default value for dist-location; defaults to
                    %(distbase)s/%(distdefault)s.

  The [aliases] section may be used to define short names for (one or more)
  dist-locations.
""" % locals()

def system(cmd):
    return os.system(cmd)

def pipe(cmd):
    p = os.popen(cmd)
    try:
        return p.readline()[:-1]
    finally:
        p.close()

class Defaults(object):

    def __init__(self):
        self.parser = ConfigParser.ConfigParser()
        self.parser.read(('/etc/mkrelease', expanduser('~/.mkrelease')))

        def get(section, key, default=None):
            if self.parser.has_option(section, key):
                return self.parser.get(section, key)
            return default

        self.python = get('defaults', 'python', python)
        self.distbase = get('defaults', 'distbase', distbase)
        self.distdefault = get('defaults', 'distdefault', distdefault)

        self.aliases = {}
        if self.parser.has_section('aliases'):
            for key, value in self.parser.items('aliases'):
                self.aliases[key] = value.split()

class ReleaseMaker(object):

    def __init__(self):
        self.defaults = Defaults()
        self.skipcheckin = False
        self.skiptag = False
        self.skipscp = False
        self.keeptemp = False
        self.pypi = False
        self.sdistflags = []
        self.uploadflags = []
        self.directory = os.curdir
        self.layout = ('trunk', 'branches', 'tags')
        self.python = self.defaults.python
        self.distbase = self.defaults.distbase
        self.distdefault = self.defaults.distdefault
        self.aliases = self.defaults.aliases
        self.distlocation = self.get_location(self.distdefault)

    def err_exit(self, msg, rc=1):
        print >>sys.stderr, msg
        sys.exit(rc)

    def assert_checkout(self, dir):
        if not exists(dir):
            self.err_exit("No such file or directory: %(dir)s" % locals())
        if not isdir(dir):
            self.err_exit("Not a directory: %(dir)s" % locals())
        if not isdir(join(dir, '.svn')):
            self.err_exit("Not a checkout: %(dir)s" % locals())

    def assert_package(self, dir):
        if not exists(dir):
            self.err_exit("No such file or directory: %(dir)s" % locals())
        if not isdir(dir):
            self.err_exit("Not a directory: %(dir)s" % locals())
        if not isfile(join(dir, 'setup.py')):
            self.err_exit("Not eggified (no setup.py found): %(dir)s" % locals())

    def assert_trunkurl(self, url):
        parts = url.split('/')
        if parts[-1] != self.layout[0] and parts[-2] not in self.layout[1:]:
            self.err_exit("URL must point to trunk, branch, or tag: %(url)s" % locals())

    def get_tagurl(self, url, tag):
        parts = url.split('/')
        if parts[-1] == self.layout[0]:
            parts = parts[:-1]
        elif parts[-2] in self.layout[1:]:
            parts = parts[:-2]
        return '/'.join(parts + [self.layout[2], tag])

    def get_location(self, location):
        if location in self.aliases:
            return self.aliases[location]
        if location.find(':') < 0 and self.distbase:
            return ['%s/%s' % (self.distbase, location)]
        return [location]

    def is_svnurl(self, url):
        return (url.startswith('svn://') or
                url.startswith('svn+ssh://') or
                url.startswith('http://') or
                url.startswith('https://') or
                url.startswith('file://'))

    def find(self, dir, name, maxdepth=999):
        regex = r'.*[/\\:]%s$' % name.replace('.', '[.]')
        return pipe("find %(dir)s -maxdepth %(maxdepth)s -iregex '%(regex)s' -print" % locals())

    def get_options(self):
        try:
            options, args = getopt.getopt(sys.argv[1:], 'CDKSTcd:hi:psvz',
                                          ('help', 'version', 'codespeak'))
        except getopt.GetoptError, e:
            self.err_exit('%s\n\n%s' % (e.msg, usage))

        for name, value in options:
            if name == '-C':
                self.skipcheckin = True
            elif name == '-T':
                self.skiptag = True
            elif name == '-S':
                self.skipscp = True
            elif name == '-D':
                self.skipcheckin = self.skiptag = self.skipscp = True
            elif name == '-K':
                self.keeptemp = True
            elif name in ('-c', '--codespeak'):
                self.layout = ('trunk', 'branch', 'tag')
            elif name == '-z':
                self.sdistflags.append('--formats=zip')
            elif name == '-d':
                self.distlocation = self.get_location(value)
            elif name == '-p':
                self.pypi = True
            elif name == '-s':
                self.uploadflags.append('--sign')
            elif name == '-i':
                self.uploadflags.append('--identity=%s' % value)
            elif name in ('-v', '--version'):
                self.err_exit(version, 0)
            elif name in ('-h', '--help'):
                self.err_exit(usage, 0)
            else:
                self.err_exit(usage)

        if args:
            self.directory = args[0]

    def get_package_url(self):
        directory = self.directory
        python = self.python

        if self.is_svnurl(directory):
            self.trunkurl = directory
            self.assert_trunkurl(self.trunkurl)
        else:
            directory = abspath(directory)
            self.assert_checkout(directory)
            self.assert_package(directory)
            os.chdir(directory)
            self.trunkurl = pipe("svn info | grep ^URL")[5:]
            self.assert_trunkurl(self.trunkurl)

            name = pipe('"%(python)s" setup.py --name' % locals())
            version = pipe('"%(python)s" setup.py --version' % locals())

            print 'Releasing', name, version
            print 'URL:', self.trunkurl

            if not self.skipcheckin:
                setup_cfg = self.find(directory, 'setup.cfg', maxdepth=1)
                changes_txt = self.find(directory, 'CHANGES.txt')
                history_txt = self.find(directory, 'HISTORY.txt')
                version_txt = self.find(directory, 'version.txt')
                rc = system('svn ci -m"Prepare %(name)s %(version)s." setup.py %(setup_cfg)s '
                            '%(changes_txt)s %(history_txt)s %(version_txt)s' % locals())
                if rc != 0:
                    self.err_exit('Checkin failed')

    def make_release(self):
        tempname = abspath(tempfile.mkdtemp(prefix='release-'))
        trunkurl = self.trunkurl
        python = self.python
        sdistflags = ' '.join(self.sdistflags)
        uploadflags = ' '.join(self.uploadflags)

        try:
            rc = system('svn co "%(trunkurl)s" "%(tempname)s"' % locals())
            if rc != 0:
                self.err_exit('Checkout failed')

            self.assert_package(tempname)
            os.chdir(tempname)
            name = pipe('"%(python)s" setup.py --name' % locals())
            version = pipe('"%(python)s" setup.py --version' % locals())

            print 'Releasing', name, version

            if not self.skiptag:
                tagurl = self.get_tagurl(trunkurl, version)
                rc = system('svn cp -m"Tagged %(name)s %(version)s." "%(trunkurl)s" "%(tagurl)s"' % locals())
                if rc != 0:
                    self.err_exit('Tag failed')

            if not self.skipscp and self.pypi:
                rc = system('"%(python)s" setup.py sdist %(sdistflags)s register upload %(uploadflags)s' % locals())
            else:
                rc = system('"%(python)s" setup.py sdist %(sdistflags)s' % locals())
                if not self.skipscp and rc == 0:
                    for location in self.distlocation:
                        rc = system('scp dist/* "%(location)s"' % locals())
                        if rc != 0:
                            self.err_exit('Scp failed')

            if rc != 0:
                self.err_exit('Release failed')
        finally:
            if not self.keeptemp:
                shutil.rmtree(tempname)

    def run(self):
        self.get_options()
        self.get_package_url()
        self.make_release()
        print 'done'

def main():
    ReleaseMaker().run()
    sys.exit(0)

if __name__ == '__main__':
    main()

<commit_msg>Turns out we have to check the tag URL after all.<commit_after>import sys
import os
import getopt
import tempfile
import shutil
import ConfigParser

from os.path import abspath, join, exists, isdir, isfile, expanduser

python = "python2.6"
distbase = "jarn.com:/home/psol/dist"
distdefault = "public"

version = "mkrelease 0.20"
usage = """\
Usage: mkrelease [-CTSDK] [-cz] [-d dist-location] [svn-url|svn-sandbox]
       mkrelease [-CTSDK] [-cz] [-p [-s [-i identity]]] [svn-url|svn-sandbox]

Release an sdist egg.

Options:
  -C                Do not checkin release-relevant files from the sandbox.
  -T                Do not tag the release in subversion.
  -S                Do not scp the release tarball to dist-location.
  -D                Dry-run; equivalent to -CTS.
  -K                Keep the temporary build directory.

  -c                Assume a codespeak.net-style repository layout.
  -z                Create a zip archive instead of the default tar.gz.

  -d dist-location  An scp destination specification.

  -p                Upload the release to PyPI.
  -s                Sign the release tarball with GnuPG.
  -i identity       The GnuPG identity to sign with.

  svn-url           A URL with protocol svn, svn+ssh, http, https, or file.
  svn-sandbox       A local directory; defaults to the current working
                    directory.

Files:
  /etc/mkrelease    Global configuration file.
  ~/.mkrelease      Per user configuration file.

  The configuration file consists of sections, led by a "[section]" header
  and followed by "name = value" entries.

  The [defaults] section has the following options:

  python            The Python executable used; defaults to %(python)s.
  distbase          The value prepended if dist-location does not contain a
                    host part; defaults to %(distbase)s.
  distdefault       The default value for dist-location; defaults to
                    %(distbase)s/%(distdefault)s.

  The [aliases] section may be used to define short names for (one or more)
  dist-locations.
""" % locals()

def system(cmd):
    return os.system(cmd)

def pipe(cmd):
    p = os.popen(cmd)
    try:
        return p.readline()[:-1]
    finally:
        p.close()

class Defaults(object):

    def __init__(self):
        self.parser = ConfigParser.ConfigParser()
        self.parser.read(('/etc/mkrelease', expanduser('~/.mkrelease')))

        def get(section, key, default=None):
            if self.parser.has_option(section, key):
                return self.parser.get(section, key)
            return default

        self.python = get('defaults', 'python', python)
        self.distbase = get('defaults', 'distbase', distbase)
        self.distdefault = get('defaults', 'distdefault', distdefault)

        self.aliases = {}
        if self.parser.has_section('aliases'):
            for key, value in self.parser.items('aliases'):
                self.aliases[key] = value.split()

class ReleaseMaker(object):

    def __init__(self):
        self.defaults = Defaults()
        self.skipcheckin = False
        self.skiptag = False
        self.skipscp = False
        self.keeptemp = False
        self.pypi = False
        self.sdistflags = []
        self.uploadflags = []
        self.directory = os.curdir
        self.layout = ('trunk', 'branches', 'tags')
        self.python = self.defaults.python
        self.distbase = self.defaults.distbase
        self.distdefault = self.defaults.distdefault
        self.aliases = self.defaults.aliases
        self.distlocation = self.get_location(self.distdefault)

    def err_exit(self, msg, rc=1):
        print >>sys.stderr, msg
        sys.exit(rc)

    def assert_checkout(self, dir):
        if not exists(dir):
            self.err_exit("No such file or directory: %(dir)s" % locals())
        if not isdir(dir):
            self.err_exit("Not a directory: %(dir)s" % locals())
        if not isdir(join(dir, '.svn')):
            self.err_exit("Not a checkout: %(dir)s" % locals())

    def assert_package(self, dir):
        if not exists(dir):
            self.err_exit("No such file or directory: %(dir)s" % locals())
        if not isdir(dir):
            self.err_exit("Not a directory: %(dir)s" % locals())
        if not isfile(join(dir, 'setup.py')):
            self.err_exit("Not eggified (no setup.py found): %(dir)s" % locals())

    def assert_trunkurl(self, url):
        parts = url.split('/')
        if parts[-1] != self.layout[0] and parts[-2] not in self.layout[1:]:
            self.err_exit("URL must point to trunk, branch, or tag: %(url)s" % locals())

    def assert_tagurl(self, url):
        devnull = os.devnull
        if system('svn ls "%(url)s" >%(devnull)s 2>&1' % locals()) == 0:
            self.err_exit("Tag exists: %(url)s" % locals())

    def get_tagurl(self, url, tag):
        parts = url.split('/')
        if parts[-1] == self.layout[0]:
            parts = parts[:-1]
        elif parts[-2] in self.layout[1:]:
            parts = parts[:-2]
        return '/'.join(parts + [self.layout[2], tag])

    def get_location(self, location):
        if location in self.aliases:
            return self.aliases[location]
        if location.find(':') < 0 and self.distbase:
            return ['%s/%s' % (self.distbase, location)]
        return [location]

    def is_svnurl(self, url):
        return (url.startswith('svn://') or
                url.startswith('svn+ssh://') or
                url.startswith('http://') or
                url.startswith('https://') or
                url.startswith('file://'))

    def find(self, dir, name, maxdepth=999):
        regex = r'.*[/\\:]%s$' % name.replace('.', '[.]')
        return pipe("find %(dir)s -maxdepth %(maxdepth)s -iregex '%(regex)s' -print" % locals())

    def get_options(self):
        try:
            options, args = getopt.getopt(sys.argv[1:], 'CDKSTcd:hi:psvz',
                                          ('help', 'version', 'codespeak'))
        except getopt.GetoptError, e:
            self.err_exit('%s\n\n%s' % (e.msg, usage))

        for name, value in options:
            if name == '-C':
                self.skipcheckin = True
            elif name == '-T':
                self.skiptag = True
            elif name == '-S':
                self.skipscp = True
            elif name == '-D':
                self.skipcheckin = self.skiptag = self.skipscp = True
            elif name == '-K':
                self.keeptemp = True
            elif name in ('-c', '--codespeak'):
                self.layout = ('trunk', 'branch', 'tag')
            elif name == '-z':
                self.sdistflags.append('--formats=zip')
            elif name == '-d':
                self.distlocation = self.get_location(value)
            elif name == '-p':
                self.pypi = True
            elif name == '-s':
                self.uploadflags.append('--sign')
            elif name == '-i':
                self.uploadflags.append('--identity=%s' % value)
            elif name in ('-v', '--version'):
                self.err_exit(version, 0)
            elif name in ('-h', '--help'):
                self.err_exit(usage, 0)
            else:
                self.err_exit(usage)

        if args:
            self.directory = args[0]

    def get_package_url(self):
        directory = self.directory
        python = self.python

        if self.is_svnurl(directory):
            self.trunkurl = directory
            self.assert_trunkurl(self.trunkurl)
        else:
            directory = abspath(directory)
            self.assert_checkout(directory)
            self.assert_package(directory)
            os.chdir(directory)
            self.trunkurl = pipe("svn info | grep ^URL")[5:]
            self.assert_trunkurl(self.trunkurl)

            name = pipe('"%(python)s" setup.py --name' % locals())
            version = pipe('"%(python)s" setup.py --version' % locals())

            print 'Releasing', name, version
            print 'URL:', self.trunkurl

            if not self.skipcheckin:
                setup_cfg = self.find(directory, 'setup.cfg', maxdepth=1)
                changes_txt = self.find(directory, 'CHANGES.txt')
                history_txt = self.find(directory, 'HISTORY.txt')
                version_txt = self.find(directory, 'version.txt')
                rc = system('svn ci -m"Prepare %(name)s %(version)s." setup.py %(setup_cfg)s '
                            '%(changes_txt)s %(history_txt)s %(version_txt)s' % locals())
                if rc != 0:
                    self.err_exit('Checkin failed')

    def make_release(self):
        tempname = abspath(tempfile.mkdtemp(prefix='release-'))
        trunkurl = self.trunkurl
        python = self.python
        sdistflags = ' '.join(self.sdistflags)
        uploadflags = ' '.join(self.uploadflags)

        try:
            rc = system('svn co "%(trunkurl)s" "%(tempname)s"' % locals())
            if rc != 0:
                self.err_exit('Checkout failed')

            self.assert_package(tempname)
            os.chdir(tempname)
            name = pipe('"%(python)s" setup.py --name' % locals())
            version = pipe('"%(python)s" setup.py --version' % locals())

            print 'Releasing', name, version

            if not self.skiptag:
                tagurl = self.get_tagurl(trunkurl, version)
                self.assert_tagurl(tagurl)
                rc = system('svn cp -m"Tagged %(name)s %(version)s." "%(trunkurl)s" "%(tagurl)s"' % locals())
                if rc != 0:
                    self.err_exit('Tag failed')

            if not self.skipscp and self.pypi:
                rc = system('"%(python)s" setup.py sdist %(sdistflags)s register upload %(uploadflags)s' % locals())
            else:
                rc = system('"%(python)s" setup.py sdist %(sdistflags)s' % locals())
                if not self.skipscp and rc == 0:
                    for location in self.distlocation:
                        rc = system('scp dist/* "%(location)s"' % locals())
                        if rc != 0:
                            self.err_exit('Scp failed')

            if rc != 0:
                self.err_exit('Release failed')
        finally:
            if not self.keeptemp:
                shutil.rmtree(tempname)

    def run(self):
        self.get_options()
        self.get_package_url()
        self.make_release()
        print 'done'

def main():
    ReleaseMaker().run()
    sys.exit(0)

if __name__ == '__main__':
    main()

<|endoftext|>


<commit_before>import signal
from cmd import Cmd
from functools import partial
import sys
import traceback
import sys

from colorama import Fore
from PluginManager import PluginManager

from utilities import schedule
from utilities.voice import create_voice
from utilities.notification import notify
from utilities.GeneralUtilities import print_say

from packages.memory.memory import Memory

class JarvisAPI(object):
    """
    Jarvis interface for plugins.

    Plugins will receive a instance of this as the second (non-self) parameter
    of the exec()-method.

    Everything Jarvis-related that can't be implemented as a stateless-function
    in the utilities-package should be implemented here.
    """

    _CONNECTION_ERROR_MSG = "You are not connected to Internet"

    def __init__(self, jarvis):
        self._jarvis = jarvis

    def say(self, text, color=""):
        """
        This method give the jarvis the ability to print a text
        and talk when sound is enable.
        :param text: the text to print (or talk)
        :param color: for text - use colorama (https://pypi.org/project/colorama/)
                      e.g. Fore.BLUE
        """
        self._jarvis.speak(text)
        print(color + text + Fore.RESET)

    def input(self, prompt="", color=""):
        """
        Get user input
        """
        # we can't use input because for some reason input() and color codes do not work on
        # windows cmd
        sys.stdout.write(color + prompt + Fore.RESET)
        sys.stdout.flush()
        text = sys.stdin.readline()
        # return without newline
        return text.rstrip()

    def input_number(self, prompt="", color="", rtype=float, rmin=None, rmax=None):
        """
        Get user input: As number.

        Guaranteed only returns number - ask user till correct number entered.

        :param prompt: Printed to console
        :param color: Color of prompot
        :param rtype: type of return value; e.g. float (default) or int
        :param rmin: Minum of values returned
        :param rmax: Maximum of values returned
        """
        while True:
            try:
                value = rtype(self.input(prompt, color).replace(',', '.'))
                if (rmin is not None and value < rmin) or (rmax is not None and value > rmax):
                    prompt = "Sorry, needs to be between {} and {}. Try again: ".format(rmin, ramx)
                else:
                    return value
            except ValueError:
                prompt = 'Sorry, needs to be a number. Try again: '
                continue

    def connection_error(self):
        """Print generic connection error"""
        self.say(JarvisAPI._CONNECTION_ERROR_MSG)

    def exit(self):
        """Immediately exit Jarvis"""
        self._jarvis.close()

    def notification(self, msg, time_seconds=0):
        """
        Sends notification msg in time_in milliseconds
        :param msg: Message. Either String (message body) or tuple (headline, message body)
        :param time_seconds: Time in seconds to wait before showing notification
        """
        if isinstance(msg, tuple):
            headline, message = msg
        elif isinstance(msg, str):
            headline = "Jarvis"
            message = msg
        else:
            raise ValueError("msg not a string or tuple")

        if time_seconds == 0:
            notify(headline, message)
        else:
            schedule(time_seconds, notify, headline, message)

    def schedule(self, time_seconds, function, *args):
        """
        Schedules function
        After time_seconds call function with these parameter:
           - reference to this JarvisAPI instance
           - schedule_id (return value of this function)
           - *args
        :return: integer, id - use with cancel
        """
        return self._jarvis.scheduler.create_event(
            time_seconds, function, self, *args)

    def cancel(self, schedule_id):
        """
        Cancel event scheduled with schedule
        :param schedule_id: id returned by schedule
        """
        self._jarvis.scheduler.cancel(schedule_id)

    # Voice wrapper
    def enable_voice(self):
        """
        Use text to speech for every text passed to jarvis.say()
        """
        self._jarvis.enable_voice = True

    def disable_voice(self):
        """
        Stop text to speech output for every text passed to jarvis.say()
        """
        self._jarvis.enable_voice = False

    def is_voice_enabled(self):
        """
        Returns True/False if voice is enabled/disabled with
        enable_voice or disable_voice
        Default: False (disabled)
        """
        return self._jarvis.enable_voice

    # MEMORY WRAPPER
    def get_data(self, key):
        """
        Get a specific key from memory
        """
        return self._jarvis.memory.get_data(key)

    def add_data(self, key, value):
        """
        Add a key and value to memory
        """
        self._jarvis.memory.add_data(key, value)
        self._jarvis.memory.save()

    def update_data(self, key, value):
        """
        Updates a key with supplied value.
        """
        self._jarvis.memory.update_data(key, value)
        self._jarvis.memory.save()

    def del_data(self, key):
        """
        Delete a key from memory
        """
        self._jarvis.memory.del_data(key)
        self._jarvis.memory.save()

    def eval(self, s):
        """
        Simulates typing 's' in Jarvis prompt
        """
        line = self._jarvis.precmd(s)
        stop = self._jarvis.onecmd(line)
        stop = self._jarvis.postcmd(stop, line)

def catch_all_exceptions(do, pass_self=True):
    def try_do(self, s):
        try:
            if pass_self:
                do(self, s)
            else:
                do(s)
        except Exception:
            print(
                Fore.RED
                + "Some error occurred, please open an issue on github!")
            print("Here is error:")
            print('')
            traceback.print_exc()
            print(Fore.RESET)
    return try_do

class CmdInterpreter(Cmd):
    # We use this variable at Breakpoint #1.
    # We use this in order to allow Jarvis say "Hi", only at the first
    # interaction.

    # This can be used to store user specific data

    def __init__(
            self,
            first_reaction_text,
            prompt,
            directories=[],
            first_reaction=True,
            enable_voice=False):
        """
        This constructor contains a dictionary with Jarvis Actions (what Jarvis can do).
        In alphabetically order.
        """
        Cmd.__init__(self)
        self.first_reaction = first_reaction
        self.first_reaction_text = first_reaction_text
        self.prompt = prompt
        self.enable_voice = enable_voice
        # Register do_quit() function to SIGINT signal (Ctrl-C)
        signal.signal(signal.SIGINT, self.interrupt_handler)

        self.memory = Memory()
        self.scheduler = schedule.Scheduler()
        # what if the platform does not have any engines, travis doesn't have sapi5 acc to me
        try:
            self.speech = create_voice()
        except Exception as e:
            print_say("Voice not supported", self, Fore.RED)
            print_say(str(e), self, Fore.RED)

        self.fixed_responses = {"what time is it": "clock",
                                "where am i": "pinpoint",
                                }

        self._api = JarvisAPI(self)
        self._plugin_manager = PluginManager()

        for directory in directories:
            self._plugin_manager.add_directory(directory)

        self._activate_plugins()
        self._init_plugin_info()

    def _init_plugin_info(self):
        plugin_status_formatter = {
            "disabled": len(self._plugin_manager.get_disabled()),
            "enabled": self._plugin_manager.get_number_plugins_loaded(),
            "red": Fore.RED,
            "blue": Fore.BLUE,
            "reset": Fore.RESET
        }

        plugin_status = "{red}{enabled} {blue}plugins loaded"
        if plugin_status_formatter['disabled'] > 0:
            plugin_status += " {red}{disabled} {blue}plugins disabled. More information: {red}status\n"
        plugin_status += Fore.RESET

        self.first_reaction_text += plugin_status.format(
            **plugin_status_formatter)

    def _activate_plugins(self):
        """Generate do_XXX, help_XXX and (optionally) complete_XXX functions"""
        for (plugin_name, plugin) in self._plugin_manager.get_plugins().items():
            self._plugin_update_completion(plugin, plugin_name)

            run_catch = catch_all_exceptions(plugin.run)
            setattr(
                CmdInterpreter,
                "do_"
                + plugin_name,
                partial(
                    run_catch,
                    self))
            setattr(
                CmdInterpreter,
                "help_" + plugin_name,
                partial(
                    self._api.say,
                    plugin.get_doc()))

            plugin.init(self._api)

    def _plugin_update_completion(self, plugin, plugin_name):
        """Return True if completion is available"""
        completions = [i for i in plugin.complete()]
        if len(completions) > 0:
            def complete(completions):
                def _complete_impl(self, text, line, begidx, endidx):
                    return [i for i in completions if i.startswith(text)]
                return _complete_impl
            setattr(
                CmdInterpreter,
                "complete_"
                + plugin_name,
                complete(completions))

    def get_api(self):
        return self._api

    def close(self):
        """Closing Jarvis."""
        print_say("Goodbye, see you later!", self, Fore.RED)
        self.scheduler.stop_all()
        exit()

    def completedefault(self, text, line, begidx, endidx):
        """Default completion"""
        return [i for i in self.actions if i.startswith(text)]

    def execute_once(self, command):
        self.get_api().eval(command)
        self.close()

    def error(self):
        """Jarvis let you know if an error has occurred."""
        print_say("I could not identify your command...", self, Fore.RED)

    def get_completions(self, command, text):
        """Returns a list with the completions of a command."""
        dict_target = [item for item in self.actions
                       if isinstance(item, dict) and command in item][0]
        completions_list = dict_target[command]
        return [i for i in completions_list if i.startswith(text) and i != '']

    def interrupt_handler(self, signal, frame):
        """Closes Jarvis on SIGINT signal. (Ctrl-C)"""
        self.close()

    def do_status(self, s):
        """Prints plugin status status"""
        count_enabled = self._plugin_manager.get_number_plugins_loaded()
        count_disabled = len(self._plugin_manager.get_disabled())
        print_say(
            "{} Plugins enabled, {} Plugins disabled.".format(
                count_enabled,
                count_disabled),
            self)

        if "short" not in s and count_disabled > 0:
            print_say("", self)
            for disabled, reason in self._plugin_manager.get_disabled().items():
                print_say(
                    "{:<20}: {}".format(
                        disabled,
                        " OR ".join(reason)),
                    self)

    def help_status(self):
        print_say("Prints info about enabled or disabled plugins", self)
        print_say("Use \"status short\" to omit detailed information.", self)
<commit_msg>Refactor CmdInterpreter<commit_after>import signal
from cmd import Cmd
from functools import partial
import sys
import traceback

from colorama import Fore
from PluginManager import PluginManager

from utilities import schedule
from utilities.voice import create_voice
from utilities.notification import notify
from utilities.GeneralUtilities import print_say

from packages.memory.memory import Memory

class JarvisAPI(object):
    """
    Jarvis interface for plugins.

    Plugins will receive a instance of this as the second (non-self) parameter
    of the exec()-method.

    Everything Jarvis-related that can't be implemented as a stateless-function
    in the utilities-package should be implemented here.
    """

    _CONNECTION_ERROR_MSG = "You are not connected to Internet"

    def __init__(self, jarvis):
        self._jarvis = jarvis

    def say(self, text, color=""):
        """
        This method give the jarvis the ability to print a text
        and talk when sound is enable.
        :param text: the text to print (or talk)
        :param color: for text - use colorama (https://pypi.org/project/colorama/)
                      e.g. Fore.BLUE
        """
        self._jarvis.speak(text)
        print(color + text + Fore.RESET)

    def input(self, prompt="", color=""):
        """
        Get user input
        """
        # we can't use input because for some reason input() and color codes do not work on
        # windows cmd
        sys.stdout.write(color + prompt + Fore.RESET)
        sys.stdout.flush()
        text = sys.stdin.readline()
        # return without newline
        return text.rstrip()

    def input_number(self, prompt="", color="", rtype=float, rmin=None, rmax=None):
        """
        Get user input: As number.

        Guaranteed only returns number - ask user till correct number entered.

        :param prompt: Printed to console
        :param color: Color of prompot
        :param rtype: type of return value; e.g. float (default) or int
        :param rmin: Minum of values returned
        :param rmax: Maximum of values returned
        """
        while True:
            try:
                value = rtype(self.input(prompt, color).replace(',', '.'))
                if (rmin is not None and value < rmin) or (rmax is not None and value > rmax):
                    prompt = "Sorry, needs to be between {} and {}. Try again: ".format(rmin, ramx)
                else:
                    return value
            except ValueError:
                prompt = 'Sorry, needs to be a number. Try again: '
                continue

    def connection_error(self):
        """Print generic connection error"""
        self.say(JarvisAPI._CONNECTION_ERROR_MSG)

    def exit(self):
        """Immediately exit Jarvis"""
        self._jarvis.close()

    def notification(self, msg, time_seconds=0):
        """
        Sends notification msg in time_in milliseconds
        :param msg: Message. Either String (message body) or tuple (headline, message body)
        :param time_seconds: Time in seconds to wait before showing notification
        """
        if isinstance(msg, tuple):
            headline, message = msg
        elif isinstance(msg, str):
            headline = "Jarvis"
            message = msg
        else:
            raise ValueError("msg not a string or tuple")

        if time_seconds == 0:
            notify(headline, message)
        else:
            schedule(time_seconds, notify, headline, message)

    def schedule(self, time_seconds, function, *args):
        """
        Schedules function
        After time_seconds call function with these parameter:
           - reference to this JarvisAPI instance
           - schedule_id (return value of this function)
           - *args
        :return: integer, id - use with cancel
        """
        return self._jarvis.scheduler.create_event(
            time_seconds, function, self, *args)

    def cancel(self, schedule_id):
        """
        Cancel event scheduled with schedule
        :param schedule_id: id returned by schedule
        """
        self._jarvis.scheduler.cancel(schedule_id)

    # Voice wrapper
    def enable_voice(self):
        """
        Use text to speech for every text passed to jarvis.say()
        """
        self._jarvis.enable_voice = True

    def disable_voice(self):
        """
        Stop text to speech output for every text passed to jarvis.say()
        """
        self._jarvis.enable_voice = False

    def is_voice_enabled(self):
        """
        Returns True/False if voice is enabled/disabled with
        enable_voice or disable_voice
        Default: False (disabled)
        """
        return self._jarvis.enable_voice

    # MEMORY WRAPPER
    def get_data(self, key):
        """
        Get a specific key from memory
        """
        return self._jarvis.memory.get_data(key)

    def add_data(self, key, value):
        """
        Add a key and value to memory
        """
        self._jarvis.memory.add_data(key, value)
        self._jarvis.memory.save()

    def update_data(self, key, value):
        """
        Updates a key with supplied value.
        """
        self._jarvis.memory.update_data(key, value)
        self._jarvis.memory.save()

    def del_data(self, key):
        """
        Delete a key from memory
        """
        self._jarvis.memory.del_data(key)
        self._jarvis.memory.save()

    def eval(self, s):
        """
        Simulates typing 's' in Jarvis prompt
        """
        line = self._jarvis.precmd(s)
        stop = self._jarvis.onecmd(line)
        stop = self._jarvis.postcmd(stop, line)

def catch_all_exceptions(do, pass_self=True):
    def try_do(self, s):
        try:
            if pass_self:
                do(self, s)
            else:
                do(s)
        except Exception:
            print(
                Fore.RED
                + "Some error occurred, please open an issue on github!")
            print("Here is error:")
            print('')
            traceback.print_exc()
            print(Fore.RESET)
    return try_do

class CmdInterpreter(Cmd):
    # We use this variable at Breakpoint #1.
    # We use this in order to allow Jarvis say "Hi", only at the first
    # interaction.

    # This can be used to store user specific data

    def __init__(
            self,
            first_reaction_text,
            prompt,
            directories=[],
            first_reaction=True,
            enable_voice=False):
        """
        This constructor contains a dictionary with Jarvis Actions (what Jarvis can do).
        In alphabetically order.
        """
        Cmd.__init__(self)
        self.first_reaction = first_reaction
        self.first_reaction_text = first_reaction_text
        self.prompt = prompt
        self.enable_voice = enable_voice
        # Register do_quit() function to SIGINT signal (Ctrl-C)
        signal.signal(signal.SIGINT, self.interrupt_handler)

        self.memory = Memory()
        self.scheduler = schedule.Scheduler()
        # what if the platform does not have any engines, travis doesn't have sapi5 acc to me
        try:
            self.speech = create_voice()
        except Exception as e:
            print_say("Voice not supported", self, Fore.RED)
            print_say(str(e), self, Fore.RED)

        self.fixed_responses = {"what time is it": "clock",
                                "where am i": "pinpoint",
                                }

        self._api = JarvisAPI(self)
        self._plugin_manager = PluginManager()

        for directory in directories:
            self._plugin_manager.add_directory(directory)

        self._activate_plugins()
        self._init_plugin_info()

    def _init_plugin_info(self):
        plugin_status_formatter = {
            "disabled": len(self._plugin_manager.get_disabled()),
            "enabled": self._plugin_manager.get_number_plugins_loaded(),
            "red": Fore.RED,
            "blue": Fore.BLUE,
            "reset": Fore.RESET
        }

        plugin_status = "{red}{enabled} {blue}plugins loaded"
        if plugin_status_formatter['disabled'] > 0:
            plugin_status += " {red}{disabled} {blue}plugins disabled. More information: {red}status\n"
        plugin_status += Fore.RESET

        self.first_reaction_text += plugin_status.format(
            **plugin_status_formatter)

    def _activate_plugins(self):
        """Generate do_XXX, help_XXX and (optionally) complete_XXX functions"""
        for (plugin_name, plugin) in self._plugin_manager.get_plugins().items():
            self._plugin_update_completion(plugin, plugin_name)

            run_catch = catch_all_exceptions(plugin.run)
            setattr(
                CmdInterpreter,
                "do_"
                + plugin_name,
                partial(
                    run_catch,
                    self))
            setattr(
                CmdInterpreter,
                "help_" + plugin_name,
                partial(
                    self._api.say,
                    plugin.get_doc()))

            plugin.init(self._api)

    def _plugin_update_completion(self, plugin, plugin_name):
        """Return True if completion is available"""
        completions = [i for i in plugin.complete()]
        if len(completions) > 0:
            def complete(completions):
                def _complete_impl(self, text, line, begidx, endidx):
                    return [i for i in completions if i.startswith(text)]
                return _complete_impl
            setattr(
                CmdInterpreter,
                "complete_"
                + plugin_name,
                complete(completions))

    def get_api(self):
        return self._api

    def close(self):
        """Closing Jarvis."""
        print_say("Goodbye, see you later!", self, Fore.RED)
        self.scheduler.stop_all()
        sys.exit()

    def execute_once(self, command):
        self.get_api().eval(command)
        self.close()

    def error(self):
        """Jarvis let you know if an error has occurred."""
        print_say("I could not identify your command...", self, Fore.RED)

    def interrupt_handler(self, signal, frame):
        """Closes Jarvis on SIGINT signal. (Ctrl-C)"""
        self.close()

    def do_status(self, s):
        """Prints plugin status status"""
        count_enabled = self._plugin_manager.get_number_plugins_loaded()
        count_disabled = len(self._plugin_manager.get_disabled())
        print_say(
            "{} Plugins enabled, {} Plugins disabled.".format(
                count_enabled,
                count_disabled),
            self)

        if "short" not in s and count_disabled > 0:
            print_say("", self)
            for disabled, reason in self._plugin_manager.get_disabled().items():
                print_say(
                    "{:<20}: {}".format(
                        disabled,
                        " OR ".join(reason)),
                    self)

    def help_status(self):
        print_say("Prints info about enabled or disabled plugins", self)
        print_say("Use \"status short\" to omit detailed information.", self)
<|endoftext|>