<commit_before>from __future__ import division

""" NetRNG

    A network connected random number generator

    Copyright 2014 Infincia LLC

    See LICENSE file for license information

"""

__author__ = 'Stephen Oliver'
__maintainer__ = 'Stephen Oliver <steve@infincia.com>'
__version__ = '0.2a0'
__license__ = 'MIT'

# monkey patch for gevent
from gevent import monkey; monkey.patch_all()

# standard libraries
import time
import sys
import os
import logging
import ConfigParser
import msgpack
import socket
import errno
import signal

# pip packages
import gevent
import gevent.subprocess
import gevent.queue
import gevent.socket
from gevent.server import StreamServer
from gevent.pool import Pool
from gevent.coros import RLock
from gevent import Timeout
from zeroconf import ServiceBrowser, Zeroconf, ServiceInfo

'''
    Config

'''
config_defaults = dict()

global_defaults = {'mode': 'server',
                   'port': 8989,
                   'debug': False}

server_defaults = {'sample_size_bytes': 2048,
                   'listen_address': '0.0.0.0',
                   'hwrng_device': '/dev/hwrng',
                   'max_clients': 2}

client_defaults = {'server_address': '192.168.1.2'}

config_defaults.update(global_defaults)
config_defaults.update(server_defaults)
config_defaults.update(client_defaults)

netrng_config = ConfigParser.ConfigParser(defaults=config_defaults)
netrng_config.read('/etc/netrng.conf')

# logging level
DEBUG = netrng_config.getboolean('Global', 'debug')

# delimiter for end of socket messages
SOCKET_DELIMITER = '--NETRNG-SOCKET-DELIMITER'

'''
    Logging setup

'''

log = logging.getLogger(__name__)
if DEBUG:
    log.setLevel(logging.DEBUG)
else:
    log.setLevel(logging.INFO)
mainHandler = logging.StreamHandler()
mainHandler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
log.addHandler(mainHandler)

class NetRNGServer(object):
    '''
        NetRNG server

    '''

    def __init__(self,
                 listen_address=None,
                 port=None,
                 max_clients=None,
                 sample_size_bytes=None,
                 hwrng_device=None):
        log.debug('NetRNG server: initializing')

        # Listen address used by the server
        self.listen_address = listen_address

        # TCP port to listen on
        self.port = port

        # Maximum number of clients to accept, this prevents your HWRNG from being
        # overloaded, starving clients. This requires testing and depends entirely on
        # how fast your HWRNG can be read. A device that can spit out 1mbps (100KB/s) could
        # give 100 clients 1KB/s, but a device that can only generate 128bps may only
        # be able to serve 1 client slowly
        self.max_clients = max_clients

        # How much random data to request from the device for each client push
        self.sample_size_bytes = sample_size_bytes

        # Source device to use for random data, should be something fast and
        # high quality, DON'T set this to /dev/random
        self.hwrng_device = hwrng_device

        # open the hwrng for reading later during client requests
        self.hwrng = open(self.hwrng_device, 'r')

        # lock to prevent multiple clients from getting the same random samples
        self.rng_lock = RLock()

        self.zeroconf_controller = Zeroconf()

    def broadcast_service(self):
        desc = {'version': __version__}
        info = ServiceInfo('_netrng._tcp.local.', '{}._netrng._tcp.local.'.format(socket.gethostname()), socket.inet_aton(self.listen_address), self.port, 0, 0, desc)
        log.debug('NetRNG server: registering service with Bonjour: %s', info)
        self.zeroconf_controller.registerService(info)

    def unregister_service(self):
        log.debug('NetRNG server: unregistering all bonjour services')
        self.zeroconf_controller.unregisterAllServices()

    def serve(self, sock, address):
        '''
            Serves client connections providing random samples to them in a one-to-many
            request response architecture, with locking to ensure each client gets unique
            samples

        '''
        log.debug('NetRNG server: client connected %s', address)

        try:
            while True:
                log.debug('NetRNG server: receive cycle start')
                requestmsg = ""
                with Timeout(3, gevent.Timeout):
                    while True:
                        data = sock.recv(1024)
                        requestmsg = requestmsg + data
                        log.debug('NetRNG server: receive cycle')
                        if SOCKET_DELIMITER in requestmsg:
                            break
                        gevent.sleep()
                requestmsg = requestmsg.replace(SOCKET_DELIMITER, '')
                request = msgpack.unpackb(requestmsg)
                log.debug('NetRNG server: receive cycle done')
                log.debug('NetRNG server: request received %s', request)
                if request['get'] == 'sample':
                    with self.rng_lock:
                        log.debug('NetRNG server: rng lock acquired')
                        sample = self.hwrng.read(self.sample_size_bytes)
                    log.debug('NetRNG server: rng lock released')
                    log.debug('NetRNG server: sending response')
                    responsemsg = msgpack.packb({'push': 'sample', 'sample': sample})
                    sock.sendall(responsemsg + SOCKET_DELIMITER)
                if request['get'] == 'heartbeat':
                    log.debug('NetRNG server: sending heartbeat response to %s', address)
                    responsemsg = msgpack.packb({'push': 'heartbeat'})
                    sock.sendall(responsemsg + SOCKET_DELIMITER)
        except socket.error as e:
            if isinstance(e.args, tuple):
                if e[0] == errno.EPIPE:
                    log.debug('NetRNG server: client disconnected %s', address)
            else:
                log.exception('NetRNG server: socket error %s', e)
        except gevent.Timeout as timeout:
            log.debug('NetRNG server: client socket timeout')
        except Exception as e:
            log.exception('NetRNG server: %s', e)
        finally:
            sock.close()

    def calibrate(self):
        '''
            Naive implementation of auto-calibration for entropy source, should
            check how much entropy can be received in a given number of seconds
            and use that information to decide how much entropy can be distributed
            per second. With that information, it should be possible to decide
            how many clients can be promised `sample_size_bytes` per second

        '''
        log.debug('NetRNG server: starting entropy source performance calibration')
        calibration_period = 15 # seconds
        received_entropy = ""
        stop_time = time.time() + calibration_period
        while time.time() < stop_time:
            with self.rng_lock:
                received_entropy += self.hwrng.read(self.sample_size_bytes)
        received_entropy_size = len(received_entropy)
        received_entropy_per_second = received_entropy_size / calibration_period
        log.debug('NetRNG server: completed entropy source performance calibration')
        log.debug('NetRNG server: entropy source can provide %.2f bytes per second', received_entropy_per_second)

    def start(self, use_zeroconf=False):
        '''
            Server starts listening on a TCP socket and spawns a greenlet for each
            new connection. Blocks caller.

        '''
        self.pool = Pool(self.max_clients)
        self.server = StreamServer((self.listen_address, self.port), self.serve, spawn=self.pool)
        log.debug('NetRNG server: serving up to %d connections on %s:%d)', self.max_clients, self.listen_address, self.port)
        try:
            self.server.start()
            if use_zeroconf:
                self.broadcast_service()
            gevent.wait()
        except KeyboardInterrupt as e:
            log.debug('NetRNG server: exiting due to keyboard interrupt')
            sys.exit(0)

    def stop(self, use_zeroconf=False):
        '''
            Server stops listening on the TCP socket, stops accepting new connections
            and finally kills spawned connection handlers

        '''
        log.debug('NetRNG server: stopping server and killing existing client connections')
        if use_zeroconf:
            self.unregister_service()
        self.server.stop()

class NetRNGClient(object):
    '''
        NetRNG client

    '''
    def __init__(self, server_address=None, port=None):
        log.debug('NetRNG client: initializing')

        # Address of the server to connect to
        self.server_address = server_address

        # TCP port to connect to on the server
        self.port = port

        # client socket for connecting to server
        self.rngd = gevent.subprocess.Popen(['rngd','-f','-r','/dev/stdin'],
                                               stdin=gevent.subprocess.PIPE,
                                               stdout=open(os.devnull, 'w'),
                                               stderr=open(os.devnull, 'w'),
                                               close_fds=True)

        # client socket for connecting to server
        self.sock = None

        self.zeroconf_controller = Zeroconf()

        # queue for pushing received samples to the rngd subprocess as needed
        self.rngd_queue = gevent.queue.Queue(maxsize=10)

    def rngd_handler(self):
        '''
            Iterates over the rngd_queue

        '''
        log.debug('NetRNG client: starting rngd queue greenlet')
        try:
            while True:
                sample = self.rngd_queue.get()
                self.rngd.stdin.write(sample)
                self.rngd.stdin.flush()
                gevent.sleep()
        except gevent.GreenletExit as exit:
            log.debug('NetRNG client: rngd queue greenlet exiting due to graceful quit')
        except OSError:
            return

    def stream(self):
        '''
            Opens a connection to the server, configures the sample size to
            match the server configuration, then starts feeding received samples to rngd
            running in a subprocess.

            Running rngd in a subprocess allows runtime control over
            starting/stopping/configuring it at the right times

        '''
        log.debug('NetRNG client: starting stream greenlet')

        # client socket for connecting to server
        server_socket = None

        # Connection state
        server_connected = False

        while True:
            try:
                if not server_connected:
                    server_socket = gevent.socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    server_socket.connect((self.server_address, self.port))
                    log.debug('NetRNG client: connected to %s:%d', self.server_address, self.port)
                    server_connected = True

                if self.rngd_queue.full():
                    # send a keepalive to the server
                    log.debug('NetRNG client: sending heartbeat message')
                    requestmsg = msgpack.packb({'get': 'heartbeat'})
                    server_socket.sendall(requestmsg + SOCKET_DELIMITER)
                    log.debug('NetRNG client: heartbeat request sent')
                else:
                    # request a new sample
                    log.debug('NetRNG client: requesting sample')
                    requestmsg = msgpack.packb({'get': 'sample'})
                    server_socket.sendall(requestmsg + SOCKET_DELIMITER)
                    log.debug('NetRNG client: sample request sent')

                # wait for response
                log.debug('NetRNG client: receive cycle start')
                responsemsg = ""
                with Timeout(2, gevent.Timeout):
                    while True:
                        data = server_socket.recv(1024)
                        responsemsg = responsemsg + data
                        log.debug('NetRNG client: receive cycle')
                        if SOCKET_DELIMITER in responsemsg:
                            break
                        gevent.sleep()

                responsemsg = responsemsg.replace(SOCKET_DELIMITER, '')
                response = msgpack.unpackb(responsemsg)
                log.debug('NetRNG client: receive cycle done')

                if response['push'] == 'sample':
                    sample = response['sample']
                    log.debug('NetRNG client: received %d byte sample', len(sample))
                    self.rngd_queue.put(sample)
                elif response['push'] == 'heartbeat':
                    log.debug('NetRNG client: received heartbeat response')
                    gevent.sleep(1)
                else:
                    log.debug('NetRNG client: received unknown response from server')

            except socket.error as socket_exception:
                log.debug('NetRNG client: server unavailable, reconnecting in 10 seconds')
                server_connected = False
                server_socket.close()
                gevent.sleep(10)
            except gevent.Timeout as timeout:
                log.debug('NetRNG client: server socket timeout')
                server_connected = False
                server_socket.close()
                gevent.sleep(1)
            except KeyboardInterrupt as keyboard_exception:
                log.debug('NetRNG client: exiting due to keyboard interrupt')
                server_connected = False
                server_socket.close()
                break
            except gevent.GreenletExit as exit:
                log.debug('NetRNG client: stream greenlet exiting due to graceful quit')
                server_connected = False
                server_socket.close()
                break
            except Exception as unknown_exception:
                log.exception('NetRNG client: unknown exception %s', unknown_exception)
                server_connected = False
                server_socket.close()
        sys.exit(0)

    def start(self, use_zeroconf=False):
        '''
            Client spawns a greenlet for the rngd handler and the network stream
            connection, then joins and waits for them to block the caller

        '''
        log.debug('NetRNG client: spawning greenlets for rngd and stream')
        try:
            rngd_greenlet = gevent.spawn(self.rngd_handler)
            stream_greenlet = gevent.spawn(self.stream)
            greenlets = [stream_greenlet, rngd_greenlet]
            gevent.joinall(greenlets)
        except KeyboardInterrupt as e:
            log.debug('NetRNG client: exiting due to keyboard interrupt')
        finally:
            gevent.killall(greenlets)
            sys.exit(0)

'''
    Select correct mode based on configuration and start

'''

if __name__ == '__main__':
    mode = netrng_config.get('Global', 'mode')
    port = netrng_config.getint('Global', 'port')
    use_zeroconf = netrng_config.getboolean('Global', 'zeroconf')

    if mode == 'server':
        listen_address    = netrng_config.get('Server', 'listen_address')
        max_clients       = netrng_config.getint('Server', 'max_clients')
        sample_size_bytes = netrng_config.getint('Server', 'sample_size_bytes')
        hwrng_device      = netrng_config.get('Server', 'hwrng_device')

        server = NetRNGServer(listen_address=listen_address,
                              port=port,
                              max_clients=max_clients,
                              sample_size_bytes=sample_size_bytes,
                              hwrng_device=hwrng_device)

        try:
            server.start(use_zeroconf=use_zeroconf)
        finally:
            server.stop(use_zeroconf=use_zeroconf)

    elif mode == 'client':
        server_address = netrng_config.get('Client', 'server_address')

        client = NetRNGClient(server_address=server_address, port=port)
        client.start(use_zeroconf=use_zeroconf)

    else:
        log.error('NetRNG: no mode selected, quitting')
        sys.exit(1)

<commit_msg>Disable zeroconf by default<commit_after>from __future__ import division

""" NetRNG

    A network connected random number generator

    Copyright 2014 Infincia LLC

    See LICENSE file for license information

"""

__author__ = 'Stephen Oliver'
__maintainer__ = 'Stephen Oliver <steve@infincia.com>'
__version__ = '0.2a0'
__license__ = 'MIT'

# monkey patch for gevent
from gevent import monkey; monkey.patch_all()

# standard libraries
import time
import sys
import os
import logging
import ConfigParser
import msgpack
import socket
import errno
import signal

# pip packages
import gevent
import gevent.subprocess
import gevent.queue
import gevent.socket
from gevent.server import StreamServer
from gevent.pool import Pool
from gevent.coros import RLock
from gevent import Timeout
from zeroconf import ServiceBrowser, Zeroconf, ServiceInfo

'''
    Config

'''
config_defaults = dict()

global_defaults = {'mode': 'server',
                   'port': 8989,
                   'debug': False,
                   'zeroconf': False}

server_defaults = {'sample_size_bytes': 2048,
                   'listen_address': '0.0.0.0',
                   'hwrng_device': '/dev/hwrng',
                   'max_clients': 2}

client_defaults = {'server_address': '192.168.1.2'}

config_defaults.update(global_defaults)
config_defaults.update(server_defaults)
config_defaults.update(client_defaults)

netrng_config = ConfigParser.ConfigParser(defaults=config_defaults)
netrng_config.read('/etc/netrng.conf')

# logging level
DEBUG = netrng_config.getboolean('Global', 'debug')

# delimiter for end of socket messages
SOCKET_DELIMITER = '--NETRNG-SOCKET-DELIMITER'

'''
    Logging setup

'''

log = logging.getLogger(__name__)
if DEBUG:
    log.setLevel(logging.DEBUG)
else:
    log.setLevel(logging.INFO)
mainHandler = logging.StreamHandler()
mainHandler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
log.addHandler(mainHandler)

class NetRNGServer(object):
    '''
        NetRNG server

    '''

    def __init__(self,
                 listen_address=None,
                 port=None,
                 max_clients=None,
                 sample_size_bytes=None,
                 hwrng_device=None):
        log.debug('NetRNG server: initializing')

        # Listen address used by the server
        self.listen_address = listen_address

        # TCP port to listen on
        self.port = port

        # Maximum number of clients to accept, this prevents your HWRNG from being
        # overloaded, starving clients. This requires testing and depends entirely on
        # how fast your HWRNG can be read. A device that can spit out 1mbps (100KB/s) could
        # give 100 clients 1KB/s, but a device that can only generate 128bps may only
        # be able to serve 1 client slowly
        self.max_clients = max_clients

        # How much random data to request from the device for each client push
        self.sample_size_bytes = sample_size_bytes

        # Source device to use for random data, should be something fast and
        # high quality, DON'T set this to /dev/random
        self.hwrng_device = hwrng_device

        # open the hwrng for reading later during client requests
        self.hwrng = open(self.hwrng_device, 'r')

        # lock to prevent multiple clients from getting the same random samples
        self.rng_lock = RLock()

        self.zeroconf_controller = Zeroconf()

    def broadcast_service(self):
        desc = {'version': __version__}
        info = ServiceInfo('_netrng._tcp.local.', '{}._netrng._tcp.local.'.format(socket.gethostname()), socket.inet_aton(self.listen_address), self.port, 0, 0, desc)
        log.debug('NetRNG server: registering service with Bonjour: %s', info)
        self.zeroconf_controller.registerService(info)

    def unregister_service(self):
        log.debug('NetRNG server: unregistering all bonjour services')
        self.zeroconf_controller.unregisterAllServices()

    def serve(self, sock, address):
        '''
            Serves client connections providing random samples to them in a one-to-many
            request response architecture, with locking to ensure each client gets unique
            samples

        '''
        log.debug('NetRNG server: client connected %s', address)

        try:
            while True:
                log.debug('NetRNG server: receive cycle start')
                requestmsg = ""
                with Timeout(3, gevent.Timeout):
                    while True:
                        data = sock.recv(1024)
                        requestmsg = requestmsg + data
                        log.debug('NetRNG server: receive cycle')
                        if SOCKET_DELIMITER in requestmsg:
                            break
                        gevent.sleep()
                requestmsg = requestmsg.replace(SOCKET_DELIMITER, '')
                request = msgpack.unpackb(requestmsg)
                log.debug('NetRNG server: receive cycle done')
                log.debug('NetRNG server: request received %s', request)
                if request['get'] == 'sample':
                    with self.rng_lock:
                        log.debug('NetRNG server: rng lock acquired')
                        sample = self.hwrng.read(self.sample_size_bytes)
                    log.debug('NetRNG server: rng lock released')
                    log.debug('NetRNG server: sending response')
                    responsemsg = msgpack.packb({'push': 'sample', 'sample': sample})
                    sock.sendall(responsemsg + SOCKET_DELIMITER)
                if request['get'] == 'heartbeat':
                    log.debug('NetRNG server: sending heartbeat response to %s', address)
                    responsemsg = msgpack.packb({'push': 'heartbeat'})
                    sock.sendall(responsemsg + SOCKET_DELIMITER)
        except socket.error as e:
            if isinstance(e.args, tuple):
                if e[0] == errno.EPIPE:
                    log.debug('NetRNG server: client disconnected %s', address)
            else:
                log.exception('NetRNG server: socket error %s', e)
        except gevent.Timeout as timeout:
            log.debug('NetRNG server: client socket timeout')
        except Exception as e:
            log.exception('NetRNG server: %s', e)
        finally:
            sock.close()

    def calibrate(self):
        '''
            Naive implementation of auto-calibration for entropy source, should
            check how much entropy can be received in a given number of seconds
            and use that information to decide how much entropy can be distributed
            per second. With that information, it should be possible to decide
            how many clients can be promised `sample_size_bytes` per second

        '''
        log.debug('NetRNG server: starting entropy source performance calibration')
        calibration_period = 15 # seconds
        received_entropy = ""
        stop_time = time.time() + calibration_period
        while time.time() < stop_time:
            with self.rng_lock:
                received_entropy += self.hwrng.read(self.sample_size_bytes)
        received_entropy_size = len(received_entropy)
        received_entropy_per_second = received_entropy_size / calibration_period
        log.debug('NetRNG server: completed entropy source performance calibration')
        log.debug('NetRNG server: entropy source can provide %.2f bytes per second', received_entropy_per_second)

    def start(self, use_zeroconf=False):
        '''
            Server starts listening on a TCP socket and spawns a greenlet for each
            new connection. Blocks caller.

        '''
        self.pool = Pool(self.max_clients)
        self.server = StreamServer((self.listen_address, self.port), self.serve, spawn=self.pool)
        log.debug('NetRNG server: serving up to %d connections on %s:%d)', self.max_clients, self.listen_address, self.port)
        try:
            self.server.start()
            if use_zeroconf:
                self.broadcast_service()
            gevent.wait()
        except KeyboardInterrupt as e:
            log.debug('NetRNG server: exiting due to keyboard interrupt')
            sys.exit(0)

    def stop(self, use_zeroconf=False):
        '''
            Server stops listening on the TCP socket, stops accepting new connections
            and finally kills spawned connection handlers

        '''
        log.debug('NetRNG server: stopping server and killing existing client connections')
        if use_zeroconf:
            self.unregister_service()
        self.server.stop()

class NetRNGClient(object):
    '''
        NetRNG client

    '''
    def __init__(self, server_address=None, port=None):
        log.debug('NetRNG client: initializing')

        # Address of the server to connect to
        self.server_address = server_address

        # TCP port to connect to on the server
        self.port = port

        # client socket for connecting to server
        self.rngd = gevent.subprocess.Popen(['rngd','-f','-r','/dev/stdin'],
                                               stdin=gevent.subprocess.PIPE,
                                               stdout=open(os.devnull, 'w'),
                                               stderr=open(os.devnull, 'w'),
                                               close_fds=True)

        # client socket for connecting to server
        self.sock = None

        self.zeroconf_controller = Zeroconf()

        # queue for pushing received samples to the rngd subprocess as needed
        self.rngd_queue = gevent.queue.Queue(maxsize=10)

    def rngd_handler(self):
        '''
            Iterates over the rngd_queue

        '''
        log.debug('NetRNG client: starting rngd queue greenlet')
        try:
            while True:
                sample = self.rngd_queue.get()
                self.rngd.stdin.write(sample)
                self.rngd.stdin.flush()
                gevent.sleep()
        except gevent.GreenletExit as exit:
            log.debug('NetRNG client: rngd queue greenlet exiting due to graceful quit')
        except OSError:
            return

    def stream(self):
        '''
            Opens a connection to the server, configures the sample size to
            match the server configuration, then starts feeding received samples to rngd
            running in a subprocess.

            Running rngd in a subprocess allows runtime control over
            starting/stopping/configuring it at the right times

        '''
        log.debug('NetRNG client: starting stream greenlet')

        # client socket for connecting to server
        server_socket = None

        # Connection state
        server_connected = False

        while True:
            try:
                if not server_connected:
                    server_socket = gevent.socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    server_socket.connect((self.server_address, self.port))
                    log.debug('NetRNG client: connected to %s:%d', self.server_address, self.port)
                    server_connected = True

                if self.rngd_queue.full():
                    # send a keepalive to the server
                    log.debug('NetRNG client: sending heartbeat message')
                    requestmsg = msgpack.packb({'get': 'heartbeat'})
                    server_socket.sendall(requestmsg + SOCKET_DELIMITER)
                    log.debug('NetRNG client: heartbeat request sent')
                else:
                    # request a new sample
                    log.debug('NetRNG client: requesting sample')
                    requestmsg = msgpack.packb({'get': 'sample'})
                    server_socket.sendall(requestmsg + SOCKET_DELIMITER)
                    log.debug('NetRNG client: sample request sent')

                # wait for response
                log.debug('NetRNG client: receive cycle start')
                responsemsg = ""
                with Timeout(2, gevent.Timeout):
                    while True:
                        data = server_socket.recv(1024)
                        responsemsg = responsemsg + data
                        log.debug('NetRNG client: receive cycle')
                        if SOCKET_DELIMITER in responsemsg:
                            break
                        gevent.sleep()

                responsemsg = responsemsg.replace(SOCKET_DELIMITER, '')
                response = msgpack.unpackb(responsemsg)
                log.debug('NetRNG client: receive cycle done')

                if response['push'] == 'sample':
                    sample = response['sample']
                    log.debug('NetRNG client: received %d byte sample', len(sample))
                    self.rngd_queue.put(sample)
                elif response['push'] == 'heartbeat':
                    log.debug('NetRNG client: received heartbeat response')
                    gevent.sleep(1)
                else:
                    log.debug('NetRNG client: received unknown response from server')

            except socket.error as socket_exception:
                log.debug('NetRNG client: server unavailable, reconnecting in 10 seconds')
                server_connected = False
                server_socket.close()
                gevent.sleep(10)
            except gevent.Timeout as timeout:
                log.debug('NetRNG client: server socket timeout')
                server_connected = False
                server_socket.close()
                gevent.sleep(1)
            except KeyboardInterrupt as keyboard_exception:
                log.debug('NetRNG client: exiting due to keyboard interrupt')
                server_connected = False
                server_socket.close()
                break
            except gevent.GreenletExit as exit:
                log.debug('NetRNG client: stream greenlet exiting due to graceful quit')
                server_connected = False
                server_socket.close()
                break
            except Exception as unknown_exception:
                log.exception('NetRNG client: unknown exception %s', unknown_exception)
                server_connected = False
                server_socket.close()
        sys.exit(0)

    def start(self, use_zeroconf=False):
        '''
            Client spawns a greenlet for the rngd handler and the network stream
            connection, then joins and waits for them to block the caller

        '''
        log.debug('NetRNG client: spawning greenlets for rngd and stream')
        try:
            rngd_greenlet = gevent.spawn(self.rngd_handler)
            stream_greenlet = gevent.spawn(self.stream)
            greenlets = [stream_greenlet, rngd_greenlet]
            gevent.joinall(greenlets)
        except KeyboardInterrupt as e:
            log.debug('NetRNG client: exiting due to keyboard interrupt')
        finally:
            gevent.killall(greenlets)
            sys.exit(0)

'''
    Select correct mode based on configuration and start

'''

if __name__ == '__main__':
    mode = netrng_config.get('Global', 'mode')
    port = netrng_config.getint('Global', 'port')
    use_zeroconf = netrng_config.getboolean('Global', 'zeroconf')

    if mode == 'server':
        listen_address    = netrng_config.get('Server', 'listen_address')
        max_clients       = netrng_config.getint('Server', 'max_clients')
        sample_size_bytes = netrng_config.getint('Server', 'sample_size_bytes')
        hwrng_device      = netrng_config.get('Server', 'hwrng_device')

        server = NetRNGServer(listen_address=listen_address,
                              port=port,
                              max_clients=max_clients,
                              sample_size_bytes=sample_size_bytes,
                              hwrng_device=hwrng_device)

        try:
            server.start(use_zeroconf=use_zeroconf)
        finally:
            server.stop(use_zeroconf=use_zeroconf)

    elif mode == 'client':
        server_address = netrng_config.get('Client', 'server_address')

        client = NetRNGClient(server_address=server_address, port=port)
        client.start(use_zeroconf=use_zeroconf)

    else:
        log.error('NetRNG: no mode selected, quitting')
        sys.exit(1)

<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from django.contrib.syndication.views import Feed
from django.utils.feedgenerator import Atom1Feed
from django_cal.views import Events
from datetime import datetime, date
import django_cal
import locale
import dateutil

from hat.models import Strike, Region

locale.setlocale(locale.LC_ALL, "pt_PT.UTF-8")

tzlx = dateutil.tz.gettz('Europe/Lisbon')

def strikeItems():
    return Strike.objects.filter(start_date__gte=datetime.today().date()).exclude(canceled=True).order_by('start_date')[:10]

class RssFeed(Feed):
    """Generate an RSS of the strikes"""
    title = u'Hoje há greve?'
    link = 'http://hagreve.com'
    description = u'Veja se consegue chegar ao trabalho. Lembre-se que as informações podem estar desactualizadas.'
    author_name = 'hagreve.com'
    author_link = 'http://hagreve.com'
    author_email = 'info@hagreve.com'
    copyright = 'hagreve.com, ' + str(datetime.now().year)

    def items(self):
        return strikeItems().reverse()

    def item_title(self, strike):
        return strike.company.name + ' - ' + strike.region.name

    description_template = 'feeds/rss_description.html'

    def item_summary(self, strike):
        return strike.company.name + ' - ' + strike.region.name

    def item_link(self, strike):
        return 'http://hagreve.com'

    def item_pubdate(self, strike):
        return strike.start_date

class AtomFeed(RssFeed):
    feed_type = Atom1Feed
    subtitle = RssFeed.description

class IcsFeed(Events):
    def cal_name(self):
        return u'Hoje há greve?'

    def cal_desc(self):
        return u'Veja se consegue chegar ao trabalho. Lembre-se que as informações podem estar desactualizadas.'

    def items(self):
        return strikeItems()

    def item_summary(self, strike):
        return 'Greve da ' + strike.company.name + ' - ' + strike.region.name

    def item_title(self, strike):
        return strike.company.name + ' - ' + strike.region.name

    def item_start(self, strike):
        if strike.start_date == strike.end_date:
          return strike.start_date.date()
        return strike.start_date.replace(tzinfo=tzlx)

    def item_end(self, strike):
        return strike.end_date.replace(tzinfo=tzlx)

    def item_comment(self, strike):
        return 'Greve da empresa ' + strike.company.name + '\n' + 'De ' + str(strike.start_date) + ' a ' + str(strike.end_date.strftime) + '\n' + strike.description

    def item_link(self, strike):
        return 'https://hagreve.com'

<commit_msg>ics: Se for whole day, mete só a data no final do evento<commit_after># -*- coding: utf-8 -*-
from django.contrib.syndication.views import Feed
from django.utils.feedgenerator import Atom1Feed
from django_cal.views import Events
from datetime import datetime, date
import django_cal
import locale
import dateutil

from hat.models import Strike, Region

locale.setlocale(locale.LC_ALL, "pt_PT.UTF-8")

tzlx = dateutil.tz.gettz('Europe/Lisbon')

def strikeItems():
    return Strike.objects.filter(start_date__gte=datetime.today().date()).exclude(canceled=True).order_by('start_date')[:10]

class RssFeed(Feed):
    """Generate an RSS of the strikes"""
    title = u'Hoje há greve?'
    link = 'http://hagreve.com'
    description = u'Veja se consegue chegar ao trabalho. Lembre-se que as informações podem estar desactualizadas.'
    author_name = 'hagreve.com'
    author_link = 'http://hagreve.com'
    author_email = 'info@hagreve.com'
    copyright = 'hagreve.com, ' + str(datetime.now().year)

    def items(self):
        return strikeItems().reverse()

    def item_title(self, strike):
        return strike.company.name + ' - ' + strike.region.name

    description_template = 'feeds/rss_description.html'

    def item_summary(self, strike):
        return strike.company.name + ' - ' + strike.region.name

    def item_link(self, strike):
        return 'http://hagreve.com'

    def item_pubdate(self, strike):
        return strike.start_date

class AtomFeed(RssFeed):
    feed_type = Atom1Feed
    subtitle = RssFeed.description

class IcsFeed(Events):
    def cal_name(self):
        return u'Hoje há greve?'

    def cal_desc(self):
        return u'Veja se consegue chegar ao trabalho. Lembre-se que as informações podem estar desactualizadas.'

    def items(self):
        return strikeItems()

    def item_summary(self, strike):
        return 'Greve da ' + strike.company.name + ' - ' + strike.region.name

    def item_title(self, strike):
        return strike.company.name + ' - ' + strike.region.name

    def item_start(self, strike):
        if strike.start_date == strike.end_date:
          return strike.start_date.date()
        return strike.start_date.replace(tzinfo=tzlx)

    def item_end(self, strike):
        if strike.start_date == strike.end_date:
          return strike.end_date.date()
        return strike.end_date.replace(tzinfo=tzlx)

    def item_comment(self, strike):
        return 'Greve da empresa ' + strike.company.name + '\n' + 'De ' + str(strike.start_date) + ' a ' + str(strike.end_date.strftime) + '\n' + strike.description

    def item_link(self, strike):
        return 'https://hagreve.com'

<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# If no documentation is generated, make sure that this file is executable.
import sys
import os

def printout(text):
	sys.stdout.write(text)

if len(sys.argv) != 2:
	sys.exit(1)

# Get the file infos.
filePath = sys.argv[1]
fileSubpath, fileExt = os.path.splitext(filePath)
fileDir, fileName = os.path.split(fileSubpath)
fileExt = fileExt
fileName = fileName

# Get the file content.
fileHandle = open(filePath)
fileLines = fileHandle.readlines()
fileHandle.close()

# Doxygen infos.
doxyNamespace = "GPU"
doxyGroup = "Shaders"
doxySubnamespace = fileExt.lower()[1:].capitalize()
doxyClass = fileName.capitalize().replace("-", "_")
doxyFullNames = {"Vert" : "vertex", "Frag" : "fragment", "Geom" : "geometry" }
doxyClassDetail = doxyClass.replace("_", " ")
# C++ namespaces
printout("namespace " + doxyNamespace + "{\n")
printout("namespace " + doxySubnamespace + "{\n")
# Class
printout("/** \\class " + doxyClass + "\n")
printout("  * \\brief \"" + doxyClassDetail + "\" " + doxyFullNames[doxySubnamespace] + " shader." + "\n")
printout("  * \\ingroup " + doxyGroup + "*/\n")
printout("public class " + doxyClass + " {" + "\n")
printout("public:" + "\n")
# Content of the shader
for line in fileLines:
	printout(line)
# Close the class, the namespaces, the group.
printout("}" + "\n}\n}\n")

sys.exit(0)
<commit_msg>Documentation: improve support for shader interface blocks members.<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# If no documentation is generated, make sure that this file is executable.
import sys
import os

def printout(text):
	sys.stdout.write(text)

if len(sys.argv) != 2:
	sys.exit(1)

# Get the file infos.
filePath = sys.argv[1]
fileSubpath, fileExt = os.path.splitext(filePath)
fileDir, fileName = os.path.split(fileSubpath)
fileExt = fileExt
fileName = fileName

# Get the file content.
fileHandle = open(filePath)
fileLines = fileHandle.readlines()
fileHandle.close()

# Doxygen infos.
doxyNamespace = "GPU"
doxyGroup = "Shaders"
doxySubnamespace = fileExt.lower()[1:].capitalize()
doxyClass = fileName.capitalize().replace("-", "_")
doxyFullNames = {"Vert" : "vertex", "Frag" : "fragment", "Geom" : "geometry" }
structAddKeyword = {"Vert" : "out", "Frag" : "in", "Geom" : "in" }
doxyClassDetail = doxyClass.replace("_", " ")
structKeyword = structAddKeyword[doxySubnamespace]
# C++ namespaces
printout("namespace " + doxyNamespace + "{\n")
printout("namespace " + doxySubnamespace + "{\n")
# Class
printout("/** \\class " + doxyClass + "\n")
printout("  * \\brief \"" + doxyClassDetail + "\" " + doxyFullNames[doxySubnamespace] + " shader." + "\n")
printout("  * \\ingroup " + doxyGroup + "\n*/\n")
printout("public class " + doxyClass + " {" + "\n")
printout("public:" + "\n")

inInterfaceBlock = False
# Content of the shader
for line in fileLines:
	printLine = True
	# Detect layout keyword at beginning and remove layout block that make Doxygen think it's a function.
	if line.lstrip().startswith("layout"):
		line = line[line.find(")")+1:].lstrip()
	if line.find("INTERFACE")>=0:
		printLine = False
		inInterfaceBlock = True
	if inInterfaceBlock and line.find("}") >= 0:
		printLine = False
		inInterfaceBlock = False
	if inInterfaceBlock and len(line) > 0:
		line = structKeyword + " " + line.lstrip()

	if printLine:
		printout(line)
# Close the class, the namespaces, the group.
printout("\n}}}\n")

sys.exit(0)
<|endoftext|>


<commit_before>import math
from math import sqrt

import hypothesis.strategies as st
import pytest  # type: ignore
from hypothesis import assume, example, given, note

from ppb_vector import Vector2
from utils import angle_isclose, angles, floats, isclose, vectors

data_exact = [
    (Vector2(1, 1), -90, Vector2(1, -1)),
    (Vector2(1, 1), 0, Vector2(1, 1)),
    (Vector2(1, 1), 90, Vector2(-1, 1)),
    (Vector2(1, 1), 180, Vector2(-1, -1)),
]

@pytest.mark.parametrize("input, angle, expected", data_exact,
                         ids=[str(angle) for _, angle, _ in data_exact],
)
def test_exact_rotations(input, angle, expected):
    assert input.rotate(angle) == expected
    assert input.angle(expected) == angle

# angle (in degrees) -> (sin, cos)
## values from 0 to 45°
## lifted from https://en.wikibooks.org/wiki/Trigonometry/Selected_Angles_Reference
remarkable_angles = {
    15: ((sqrt(6) - sqrt(2))/4, (sqrt(6) + sqrt(2))/4),
    22.5: (sqrt(2 - sqrt(2))/2, sqrt(2 + sqrt(2))/2),
    30: (0.5, sqrt(3)/2),
    45: (sqrt(2)/2, sqrt(2)/2),
}

## extend up to 90°
remarkable_angles.update({
    90 - angle: (cos_t, sin_t)
    for angle, (sin_t, cos_t) in remarkable_angles.items()
})

## extend up to 180°
remarkable_angles.update({
    angle + 90: (cos_t, -sin_t)
    for angle, (sin_t, cos_t) in remarkable_angles.items()
})

## extend up to 360°
remarkable_angles.update({
    angle + 180: (-sin_t, -cos_t)
    for angle, (sin_t, cos_t) in remarkable_angles.items()
})

## extend to negative angles
remarkable_angles.update({
    -angle: (-sin_t, cos_t)
    for angle, (sin_t, cos_t) in remarkable_angles.items()
})

@pytest.mark.parametrize("angle, trig", remarkable_angles.items(),
                          ids=[str(x) for x in remarkable_angles]
)
def test_remarkable_angles(angle, trig):
    _angle = math.radians(angle)
    sin_t, cos_t = trig
    sin_m, cos_m = math.sin(_angle), math.cos(_angle)

    assert math.isclose(sin_t, sin_m)
    assert math.isclose(cos_t, cos_m)

data_close = [
    (Vector2(1, 0), angle, Vector2(cos_t, sin_t))
    for (angle, (sin_t, cos_t)) in remarkable_angles.items()
] + [
    (Vector2(1, 1), angle, Vector2(cos_t - sin_t, cos_t + sin_t))
    for (angle, (sin_t, cos_t)) in remarkable_angles.items()
]

@pytest.mark.parametrize("input, angle, expected", data_close,
                         ids=[f"(1,0).rotate({x})" for x in remarkable_angles] +
                             [f"(1,1).rotate({x})" for x in remarkable_angles],
)
def test_close_rotations(input, angle, expected):
    assert input.rotate(angle).isclose(expected)
    assert angle_isclose(input.angle(expected), angle)

def test_for_exception():
    with pytest.raises(TypeError):
        Vector2("gibberish", 1).rotate(180)

@given(angle=angles())
def test_trig_stability(angle):
    """cos² + sin² == 1

    We are testing that this equation holds, as otherwise rotations
    would (slightly) change the length of vectors they are applied to.
    """
    r_cos, r_sin = Vector2._trig(angle)

    # Don't use exponents here. Multiplication is generally more stable.
    assert math.isclose(r_cos * r_cos + r_sin * r_sin, 1, rel_tol=1e-18)

@given(angle=angles(), n=st.integers(min_value=0, max_value=1e6))
def test_trig_invariance(angle: float, n: int):
    """Test that cos(θ), sin(θ) ≃ cos(θ + n*360°), sin(θ + n*360°)"""
    r_cos, r_sin = Vector2._trig(angle)
    n_cos, n_sin = Vector2._trig(angle + 360*n)

    note(f"δcos: {r_cos - n_cos}")
    assert isclose(r_cos, n_cos, rel_to=[n / 1e9])
    note(f"δsin: {r_sin - n_sin}")
    assert isclose(r_sin, n_sin, rel_to=[n / 1e9])

@given(v=vectors(), angle=angles(), n=st.integers(min_value=0, max_value=1e6))
def test_rotation_invariance(v: Vector2, angle: float, n: int):
    """Check that rotating by angle and angle + n×360° have the same result."""
    rot_once = v.rotate(angle)
    rot_many = v.rotate(angle + 360 * n)
    note(f"δ: {(rot_once - rot_many).length}")
    assert rot_once.isclose(rot_many, rel_tol=n / 1e9)

@given(initial=vectors(), angle=angles())
def test_rotation_angle(initial, angle):
    """initial.angle( initial.rotate(angle) ) == angle"""
    assume(initial.length > 1e-5)
    rotated = initial.rotate(angle)
    note(f"Rotated: {rotated}")

    measured_angle = initial.angle(rotated)
    d = measured_angle - angle % 360
    note(f"Angle: {measured_angle} = {angle} + {d if d<180 else d-360}")
    assert angle_isclose(angle, measured_angle)

@given(angle=angles(), loops=st.integers(min_value=0, max_value=500))
def test_rotation_stability(angle, loops):
    """Rotating loops times by angle is equivalent to rotating by loops*angle."""
    initial = Vector2(1, 0)

    fellswoop = initial.rotate(angle * loops)
    note(f"One Fell Swoop: {fellswoop}")

    stepwise = initial
    for _ in range(loops):
        stepwise = stepwise.rotate(angle)
    note(f"Step-wise: {stepwise}")

    assert fellswoop.isclose(stepwise)
    assert math.isclose(fellswoop.length, initial.length, rel_tol=1e-15)

@given(initial=vectors(), angles=st.lists(angles()))
def test_rotation_stability2(initial, angles):
    """Rotating by a sequence of angles is equivalent to rotating by the total."""
    total_angle = sum(angles)
    fellswoop = initial.rotate(total_angle)
    note(f"One Fell Swoop: {fellswoop}")

    stepwise = initial
    for angle in angles:
        stepwise = stepwise.rotate(angle)
    note(f"Step-wise: {stepwise}")

    # Increase the tolerance on this comparison,
    # as stepwise rotations induce rounding errors
    assert fellswoop.isclose(stepwise, rel_tol=1e-6)

    assert math.isclose(fellswoop.length, initial.length, rel_tol=1e-15)

@given(x=vectors(), y=vectors(), scalar=floats(), angle=angles())
# In this example:
# * x * l == -y
# * Rotation must not be an multiple of 90deg
# * Must be sufficiently large
@example(x=Vector2(1e10, 1e10), y=Vector2(1e19, 1e19), scalar=-1e9, angle=45)
def test_rotation_linearity(x, y, scalar, angle):
    """(l*x + y).rotate is equivalent to l*x.rotate + y.rotate"""
    inner = (scalar * x + y).rotate(angle)
    outer = scalar * x.rotate(angle) + y.rotate(angle)
    note(f"scalar * x + y: {scalar * x + y}")
    note(f"scalar * x.rotate(): {scalar * x.rotate(angle)}")
    note(f"y.rotate(): {y.rotate(angle)}")
    note(f"Inner: {inner}")
    note(f"Outer: {outer}")
    assert inner.isclose(outer, rel_to=[x, scalar * x, y])
<commit_msg>tests/rotate: Limit invariance tests to 10⁵ turns<commit_after>import math
from math import sqrt

import hypothesis.strategies as st
import pytest  # type: ignore
from hypothesis import assume, example, given, note

from ppb_vector import Vector2
from utils import angle_isclose, angles, floats, isclose, vectors

data_exact = [
    (Vector2(1, 1), -90, Vector2(1, -1)),
    (Vector2(1, 1), 0, Vector2(1, 1)),
    (Vector2(1, 1), 90, Vector2(-1, 1)),
    (Vector2(1, 1), 180, Vector2(-1, -1)),
]

@pytest.mark.parametrize("input, angle, expected", data_exact,
                         ids=[str(angle) for _, angle, _ in data_exact],
)
def test_exact_rotations(input, angle, expected):
    assert input.rotate(angle) == expected
    assert input.angle(expected) == angle

# angle (in degrees) -> (sin, cos)
## values from 0 to 45°
## lifted from https://en.wikibooks.org/wiki/Trigonometry/Selected_Angles_Reference
remarkable_angles = {
    15: ((sqrt(6) - sqrt(2))/4, (sqrt(6) + sqrt(2))/4),
    22.5: (sqrt(2 - sqrt(2))/2, sqrt(2 + sqrt(2))/2),
    30: (0.5, sqrt(3)/2),
    45: (sqrt(2)/2, sqrt(2)/2),
}

## extend up to 90°
remarkable_angles.update({
    90 - angle: (cos_t, sin_t)
    for angle, (sin_t, cos_t) in remarkable_angles.items()
})

## extend up to 180°
remarkable_angles.update({
    angle + 90: (cos_t, -sin_t)
    for angle, (sin_t, cos_t) in remarkable_angles.items()
})

## extend up to 360°
remarkable_angles.update({
    angle + 180: (-sin_t, -cos_t)
    for angle, (sin_t, cos_t) in remarkable_angles.items()
})

## extend to negative angles
remarkable_angles.update({
    -angle: (-sin_t, cos_t)
    for angle, (sin_t, cos_t) in remarkable_angles.items()
})

@pytest.mark.parametrize("angle, trig", remarkable_angles.items(),
                          ids=[str(x) for x in remarkable_angles]
)
def test_remarkable_angles(angle, trig):
    _angle = math.radians(angle)
    sin_t, cos_t = trig
    sin_m, cos_m = math.sin(_angle), math.cos(_angle)

    assert math.isclose(sin_t, sin_m)
    assert math.isclose(cos_t, cos_m)

data_close = [
    (Vector2(1, 0), angle, Vector2(cos_t, sin_t))
    for (angle, (sin_t, cos_t)) in remarkable_angles.items()
] + [
    (Vector2(1, 1), angle, Vector2(cos_t - sin_t, cos_t + sin_t))
    for (angle, (sin_t, cos_t)) in remarkable_angles.items()
]

@pytest.mark.parametrize("input, angle, expected", data_close,
                         ids=[f"(1,0).rotate({x})" for x in remarkable_angles] +
                             [f"(1,1).rotate({x})" for x in remarkable_angles],
)
def test_close_rotations(input, angle, expected):
    assert input.rotate(angle).isclose(expected)
    assert angle_isclose(input.angle(expected), angle)

def test_for_exception():
    with pytest.raises(TypeError):
        Vector2("gibberish", 1).rotate(180)

@given(angle=angles())
def test_trig_stability(angle):
    """cos² + sin² == 1

    We are testing that this equation holds, as otherwise rotations
    would (slightly) change the length of vectors they are applied to.
    """
    r_cos, r_sin = Vector2._trig(angle)

    # Don't use exponents here. Multiplication is generally more stable.
    assert math.isclose(r_cos * r_cos + r_sin * r_sin, 1, rel_tol=1e-18)

@given(angle=angles(), n=st.integers(min_value=0, max_value=1e5))
def test_trig_invariance(angle: float, n: int):
    """Test that cos(θ), sin(θ) ≃ cos(θ + n*360°), sin(θ + n*360°)"""
    r_cos, r_sin = Vector2._trig(angle)
    n_cos, n_sin = Vector2._trig(angle + 360*n)

    note(f"δcos: {r_cos - n_cos}")
    assert isclose(r_cos, n_cos, rel_to=[n / 1e9])
    note(f"δsin: {r_sin - n_sin}")
    assert isclose(r_sin, n_sin, rel_to=[n / 1e9])

@given(v=vectors(), angle=angles(), n=st.integers(min_value=0, max_value=1e5))
def test_rotation_invariance(v: Vector2, angle: float, n: int):
    """Check that rotating by angle and angle + n×360° have the same result."""
    rot_once = v.rotate(angle)
    rot_many = v.rotate(angle + 360 * n)
    note(f"δ: {(rot_once - rot_many).length}")
    assert rot_once.isclose(rot_many, rel_tol=n / 1e9)

@given(initial=vectors(), angle=angles())
def test_rotation_angle(initial, angle):
    """initial.angle( initial.rotate(angle) ) == angle"""
    assume(initial.length > 1e-5)
    rotated = initial.rotate(angle)
    note(f"Rotated: {rotated}")

    measured_angle = initial.angle(rotated)
    d = measured_angle - angle % 360
    note(f"Angle: {measured_angle} = {angle} + {d if d<180 else d-360}")
    assert angle_isclose(angle, measured_angle)

@given(angle=angles(), loops=st.integers(min_value=0, max_value=500))
def test_rotation_stability(angle, loops):
    """Rotating loops times by angle is equivalent to rotating by loops*angle."""
    initial = Vector2(1, 0)

    fellswoop = initial.rotate(angle * loops)
    note(f"One Fell Swoop: {fellswoop}")

    stepwise = initial
    for _ in range(loops):
        stepwise = stepwise.rotate(angle)
    note(f"Step-wise: {stepwise}")

    assert fellswoop.isclose(stepwise)
    assert math.isclose(fellswoop.length, initial.length, rel_tol=1e-15)

@given(initial=vectors(), angles=st.lists(angles()))
def test_rotation_stability2(initial, angles):
    """Rotating by a sequence of angles is equivalent to rotating by the total."""
    total_angle = sum(angles)
    fellswoop = initial.rotate(total_angle)
    note(f"One Fell Swoop: {fellswoop}")

    stepwise = initial
    for angle in angles:
        stepwise = stepwise.rotate(angle)
    note(f"Step-wise: {stepwise}")

    # Increase the tolerance on this comparison,
    # as stepwise rotations induce rounding errors
    assert fellswoop.isclose(stepwise, rel_tol=1e-6)

    assert math.isclose(fellswoop.length, initial.length, rel_tol=1e-15)

@given(x=vectors(), y=vectors(), scalar=floats(), angle=angles())
# In this example:
# * x * l == -y
# * Rotation must not be an multiple of 90deg
# * Must be sufficiently large
@example(x=Vector2(1e10, 1e10), y=Vector2(1e19, 1e19), scalar=-1e9, angle=45)
def test_rotation_linearity(x, y, scalar, angle):
    """(l*x + y).rotate is equivalent to l*x.rotate + y.rotate"""
    inner = (scalar * x + y).rotate(angle)
    outer = scalar * x.rotate(angle) + y.rotate(angle)
    note(f"scalar * x + y: {scalar * x + y}")
    note(f"scalar * x.rotate(): {scalar * x.rotate(angle)}")
    note(f"y.rotate(): {y.rotate(angle)}")
    note(f"Inner: {inner}")
    note(f"Outer: {outer}")
    assert inner.isclose(outer, rel_to=[x, scalar * x, y])
<|endoftext|>


<commit_before>import os
import re
import time
import textwrap
import threading
import traceback

from prompt_toolkit.application import Application
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.document import Document
from prompt_toolkit.enums import DEFAULT_BUFFER
from prompt_toolkit.filters import Condition
from prompt_toolkit.interface import CommandLineInterface
from prompt_toolkit.key_binding.registry import Registry
from prompt_toolkit.key_binding.bindings.named_commands import get_by_name
from prompt_toolkit.keys import Keys
from prompt_toolkit.layout.containers import VSplit, HSplit, Window
from prompt_toolkit.layout.controls import BufferControl, FillControl
from prompt_toolkit.layout.dimension import LayoutDimension as D
from prompt_toolkit.layout.margins import ScrollbarMargin, ConditionalMargin
from prompt_toolkit.shortcuts import create_eventloop
from prompt_toolkit.token import Token

import scroll
import memhook
import statinfo
import interactions

help_text = textwrap.dedent(
    '''
    Hello this is the help text I will write the rest of this later.

    Use PageUp and PageDown to scroll this window when a scrollbar is present.

    ''')

stat_args = {
    'dont_extend_width': True,
    'dont_extend_height': True,
    'always_hide_cursor': True,
    'wrap_lines': True,
    'height': D.exact(1),
    'width': D.exact(38),
}

def hpad(height, ch=' ', token=Token.Padding):
    return Window(height=D.exact(height), content=FillControl(ch, token=token))

def vpad(width, ch=' ', token=Token.Padding):
    return Window(width=D.exact(width), content=FillControl(ch, token=token))

def make_stat_doc(name, value=4):
    return Document(statinfo.Stats.format(name, value))

def make_stat_window(name):
    name = statinfo.Stats.get(name).buffername
    return Window(content=BufferControl(buffer_name=name), **stat_args)

def _is_main_thread():
    return threading.current_thread() is threading.main_thread()

class BufferState:
    order = list(reversed(
        [stat.buffername for stat in statinfo.Stats.all()] + [DEFAULT_BUFFER]))

    def __init__(self, position=0):
        self.position = position

    @property
    def at(self):
        return self.order[self.position]

    def up(self):
        self.position = min(len(self.order) - 1, self.position + 1)
        return self.order[self.position]

    def down(self):
        self.position = max(0, self.position - 1)
        return self.order[self.position]

    def first(self):
        self.position = len(self.order) - 1
        return self.order[self.position]

    def last(self):
        self.position = 0
        return self.order[self.position]

class HelpItem:
    def __init__(self, name, *keys, info=''):
        self.name = name
        self.keys = keys
        self.info = info

    def _gen_keycode(self):
        return ', '.join(self.keys)

    def render(self):
        return [
            (Token.Space, ' '),
            (Token.HelpKeyCode, self._gen_keycode()),
            (Token.Space, ' '),
            (Token.HelpName, self.name),
            (Token.Space, ' '),

        ]

class Ui:
    repeated_message_pattern = re.compile(r'[ ]\((?P<num>[0-9]+)x\)$')

    def __init__(self, hook):
        self.hook = hook

        self._built = False
        self._scroll_state = 1
        self._help_showing = False
        self._last_roll = None
        self._help_text = {}

        self.stat_state = {name: 0 for name in statinfo.names}
        self.stat_state['Rerolls'] = 0

    @property
    def info_wb(self):
        return self.info_window, self.buffers['INFO_BUFFER']

    def _increment_repeated_msg(self, first=False):
        if first:
            return lambda line: line.rstrip(' ') + ' (2x)'
        else:
            incr = lambda match: f" ({str(int(match.group('num')) + 1)}x)"
            return lambda line: self.repeated_message_pattern.sub(incr, line)

    def _print(self, *args, sep=' ', pre='\n', **kwargs):
        new_msg = sep.join(str(x) for x in args)

        buffer = self.buffers['MSG_BUFFER']

        last_line = buffer.document.current_line
        line_parts = self.repeated_message_pattern.split(last_line)
        last_msg = line_parts[0]

        if last_msg != new_msg:
            buffer.insert_text(pre + new_msg)
        else:
            buffer.transform_current_line(self._increment_repeated_msg(first=len(line_parts) == 1))
            buffer.cursor_right(len(buffer.document.current_line))

    def print(self, *args, **kwargs):
        if _is_main_thread():
            self._print(*args, **kwargs)
        else:
            self.cli.eventloop.call_from_executor(lambda: self._print(*args, **kwargs))

    def prompt(self, message, end='>'):
        ...

    def _update_info_text(self, buff=None):
        buff = buff or self.stat_buffer_state.at
        buffer = statinfo.Stats.get_name(buff)

        if buffer:
            buffername = buffer.name
        else:
            return

        text = statinfo.extra_info.get(buffername, f'TODO: {buffername} text')

        if text:
            self.set_info_text(text)

    def _focus(self, buffer, cli=None):
        cli = cli or self.cli

        cli.focus(buffer)
        self._update_info_text()

    # Don't question these double half scrolls this is completely correct
    def _scroll_up(self):
        if self._scroll_state < 0:
            scroll.scroll_half_page_up(*self.info_wb)
            scroll.scroll_half_page_up(*self.info_wb)

        scroll.scroll_half_page_up(*self.info_wb)
        self._scroll_state = 1

    def _scroll_down(self):
        if self._scroll_state > 0:
            scroll.scroll_half_page_down(*self.info_wb)
            scroll.scroll_half_page_down(*self.info_wb)

        scroll.scroll_half_page_down(*self.info_wb)
        self._scroll_state = -1

    # Setup functions

    def _gen_buffers(self):
        self.stat_buffer_state = BufferState()
        return {
            DEFAULT_BUFFER: Buffer(
                initial_document=Document(""),
                is_multiline=False, read_only=False),

            'INFO_BUFFER': Buffer(
                initial_document=Document(),
                is_multiline=True),

            'REROLL_STAT_BUFFER': Buffer(
                initial_document=make_stat_doc('Rerolls', 0),
                is_multiline=False),

            'MSG_BUFFER': Buffer(
                initial_document=Document(),
                is_multiline=True),

            **{
               stat.buffername: Buffer(initial_document=make_stat_doc(stat.name))
               for stat in statinfo.Stats.all()
            }
        }

    # TODO: Lexers
    # from pygments.lexers import HtmlLexer
    # from prompt_toolkit.layout.lexers import PygmentsLexer
    # BufferControl(lexer=PygmentsLexer(HtmlLexer))

    def _gen_layout(self):
        stat_windows = []

        for stat_group in statinfo.groups:
            for stat in stat_group:
                stat_windows.append(make_stat_window(stat))

            stat_windows.append(vpad(1))

        stat_windows.append(Window(
            content=BufferControl(buffer_name='REROLL_STAT_BUFFER'), **stat_args))
        stat_windows.append(vpad(1))

        @Condition
        def scroll_cond(cli):
            if self.info_window.render_info is None:
                return True

            try:
                l = self.buffers['INFO_BUFFER'].document.line_count
                r = self.info_window.render_info
                return r.window_height < l
            except:
                return True

        self.info_window = Window(
            content=BufferControl(buffer_name='INFO_BUFFER'),
            dont_extend_width=True, wrap_lines=True, always_hide_cursor=True,
            right_margins=[ConditionalMargin(
                ScrollbarMargin(display_arrows=True), scroll_cond)
            ])

        return HSplit([
            hpad(1),
            VSplit([
                vpad(1),
                HSplit(stat_windows),
                vpad(2), # idk why there's an extra space on the stats
                self.info_window,
                vpad(1)
            ]),
            hpad(1),
            Window(content=BufferControl(buffer_name='MSG_BUFFER'),
                   height=D.exact(3), wrap_lines=True),
            Window(content=BufferControl(buffer_name=DEFAULT_BUFFER),
                   height=D.exact(1), always_hide_cursor=True)
        ])

    def _gen_bindings(self):
        registry = Registry()
        bind = registry.add_binding

        def bind_with_help(*args, name, info='', **kwargs):
            def dec(func):
                self._help_text[name] = info

                return func
            return bind(*args, **kwargs)(dec)

        bind(Keys.Left)(get_by_name('backward-char'))
        bind(Keys.Right)(get_by_name('forward-char'))

        @bind(Keys.ControlC)
        def _(event):
            event.cli.set_return_value(None)

        @bind_with_help('?', name='Help', info="Shows a help screen")
        def _(event):
            if self._help_showing:
                self._help_showing = False
                self._update_info_text()
                return

            self.set_info_text(help_text)
            self._help_showing = True

        @bind(Keys.Up)
        def _(event):
            self._focus(self.stat_buffer_state.up())

        @bind(Keys.Down)
        def _(event):
            self._focus(self.stat_buffer_state.down())

        @bind_with_help('t', name='Reroll test')
        def _(event):
            self.print("Dispatching runner")

            def do():
                self.print("ok running")

                num = 50
                t0 = time.time()

                for x in range(num):
                    # self.reroll(delay=0.017, warning="WARNING SAME STATS ROLLED")
                    self.reroll(delay=0.09, retry_delay=0.05, retry=1)
                    self.print("Rolled")

                t1 = time.time()
                self.print(f'Rolled {num} times in {t1-t0:.4f}', 'sec')

            event.cli.eventloop.run_in_executor(do)
            self.print("alrighty then")

        @bind('r', name='Reroll')
        def _(event):
            l = self.reroll()
            self.set_stats(**self.hook.zip(l))

        @bind('e', name='Update stats')
        def _(event):
            self.set_stats(**self.hook.zip(self.hook.read_all()))

        @bind('l')
        def _(event):
            self.print(f"testing executor in {__import__('threading').currentThread()}")

            def do():
                self.print(f'ok whatever {__import__("threading").currentThread()}')
                # event.cli.eventloop.call_from_executor(lambda: )

            event.cli.eventloop.run_in_executor(do)
            self.print("ok sure done")

        @bind('o')
        def _(event):
            self.set_info_text(self._help_text)

        @bind('E', name='Embed IPython')
        def _(event):
            def do():
                self, event # behold the magic of closures and scope

                __import__('IPython').embed()
                os.system('cls')

            event.cli.run_in_terminal(do)

        @bind(Keys.PageUp)
        def _(event):
            self._scroll_up()

        @bind(Keys.PageDown)
        def _(event):
            self._scroll_down()

        @bind('-')
        def _(event):
            self.print("got random stats")
            self.set_stats(**memhook.get_random_stats())

        @Condition
        def _in_stat_buffer(cli):
            return any(b == cli.current_buffer for n,b in self.buffers.items() if n.endswith("_STAT_BUFFER"))

        @bind(Keys.Enter, filter=_in_stat_buffer)
        def _(event):
            buf = event.cli.current_buffer
            self.set_info_text(f"Enter on buffer {buf} at {buf.cursor_position}")

        return registry

    def _add_events(self):
        def default_buffer_changed(_):
            self.buffers['INFO_BUFFER'].reset(self.buffers[DEFAULT_BUFFER].document)

        self.buffers[DEFAULT_BUFFER].on_text_changed += default_buffer_changed

    def build(self, alt=True):
        self.buffers = self._gen_buffers()
        self.layout = self._gen_layout()
        self.registry = self._gen_bindings()

        self._add_events()

        self.application = Application(
            layout=self.layout,
            buffers=self.buffers,
            key_bindings_registry=self.registry,
            mouse_support=True,
            use_alternate_screen=alt)

        self.cli = CommandLineInterface(
            application=self.application, eventloop=create_eventloop())

        self.set_info_text("TODO: Startup text")

        self.print("UnReal World Stat Roller v2.0")
        self.print("Press ? for help\n")

        self._built = True
        return self

    def run(self, build=True):
        if build and not self._built:
            self.build()

        elif not self._built:
            raise RuntimeError("UI has not been built yet")

        try:
            self.cli.run()
        finally:
            self.cli.eventloop.close()

    def _redraw(self):
        if _is_main_thread():
            self.cli._redraw()
        else:
            self.cli.invalidate()

    def reroll(self, **kw):
        new_stats = self.hook.safe_reroll(**kw)

        self.set_stats(**self.hook.zip(new_stats))
        self.stat_state['Rerolls'] += 1

        self._redraw()

    def set_stat(self, stat, value):
        self.stat_state[stat] = value
        self.buffers[statinfo.Stats.get(stat).buffername].reset(
            make_stat_doc(stat, value))

    def set_stats(self, **stats):
        for stat, value in stats.items():
            self.set_stat(stat, value)

    def _make_info_text(self, text):
        parts = str(text).strip().split('\n\n')
        filled_parts = [textwrap.fill(t, 35) for t in parts]
        return '\n\n'.join(filled_parts)

    def set_info_text(self, text):
        text = self._make_info_text(text)
        self.buffers['INFO_BUFFER'].reset(Document(text, cursor_position=0))

    def append_info_text(self, text, sep='\n'):
        text = self._make_info_text(text)
        buffer = self.buffers['INFO_BUFFER']
        newdoc = Document(buffer.document.text + sep + text,
                          cursor_position=buffer.document.cursor_position)

        buffer.reset(newdoc)
        buffer.on_text_changed.fire()

    def on_error(self, *args):
        self.print(f"An error has occurred: {traceback.format_exception(*args)}")
        traceback.print_exception(*args)

# TODO:
#   Race info and stat bounds helpers/warnings
#   Cheat mode, turn all the text hacker green
#     Undo button (stat history)
#     Custom roll bounds and distribution (generate and set)
<commit_msg>Misc changes<commit_after>import os
import re
import time
import textwrap
import threading
import traceback

from prompt_toolkit.application import Application
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.document import Document
from prompt_toolkit.enums import DEFAULT_BUFFER
from prompt_toolkit.filters import Condition
from prompt_toolkit.interface import CommandLineInterface
from prompt_toolkit.key_binding.registry import Registry
from prompt_toolkit.key_binding.bindings.named_commands import get_by_name
from prompt_toolkit.keys import Keys
from prompt_toolkit.layout.containers import VSplit, HSplit, Window
from prompt_toolkit.layout.controls import BufferControl, FillControl
from prompt_toolkit.layout.dimension import LayoutDimension as D
from prompt_toolkit.layout.margins import ScrollbarMargin, ConditionalMargin
from prompt_toolkit.shortcuts import create_eventloop
from prompt_toolkit.token import Token

import scroll
import memhook
import statinfo
import interactions

help_text = textwrap.dedent(
    '''
    Hello this is the help text I will write the rest of this later.

    Use PageUp and PageDown to scroll this window when a scrollbar is present.

    ''')

stat_args = {
    'dont_extend_width': True,
    'dont_extend_height': True,
    'always_hide_cursor': True,
    'wrap_lines': True,
    'height': D.exact(1),
    'width': D.exact(38),
}

def hpad(height, ch=' ', token=Token.Padding):
    return Window(height=D.exact(height), content=FillControl(ch, token=token))

def vpad(width, ch=' ', token=Token.Padding):
    return Window(width=D.exact(width), content=FillControl(ch, token=token))

def make_stat_doc(name, value=4):
    return Document(statinfo.Stats.format(name, value))

def make_stat_window(name):
    name = statinfo.Stats.get(name).buffername
    return Window(content=BufferControl(buffer_name=name), **stat_args)

def _is_main_thread():
    return threading.current_thread() is threading.main_thread()

class BufferState:
    order = list(reversed(
        [stat.buffername for stat in statinfo.Stats.all()] + [DEFAULT_BUFFER]))

    def __init__(self, position=0):
        self.position = position

    @property
    def current(self):
        return self.order[self.position]

    def up(self):
        self.position = min(len(self.order) - 1, self.position + 1)
        return self.order[self.position]

    def down(self):
        self.position = max(0, self.position - 1)
        return self.order[self.position]

    def goto(self, pos):
        if not 0 <= pos <= len(self.order):
            raise IndexError(f"Invalid buffer position {pos} (0 <= {pos} <= {len(self.order)})")

        self.position = pos
        return self.order[pos]

    def first(self):
        self.position = len(self.order) - 1
        return self.order[self.position]

    def last(self):
        self.position = 0
        return self.order[0]

class HelpItem:
    def __init__(self, name, *keys, info=''):
        self.name = name
        self.keys = keys
        self.info = info

    def _gen_keycode(self):
        return ', '.join(self.keys)

    def render(self):
        return [
            (Token.Space, ' '),
            (Token.HelpKeyCode, self._gen_keycode()),
            (Token.Space, ' '),
            (Token.HelpName, self.name),
            (Token.Space, ' '),

        ]

class Ui:
    repeated_message_pattern = re.compile(r'[ ]\((?P<num>[0-9]+)x\)$')

    def __init__(self, hook):
        self.hook = hook

        self._built = False
        self._scroll_state = 1
        self._help_showing = False
        self._last_roll = None
        self._help_text = {}

        self.stat_state = {name: 0 for name in statinfo.names}
        self.stat_state['Rerolls'] = 0

    @property
    def info_wb(self):
        return self.info_window, self.buffers['INFO_BUFFER']

    def _increment_repeated_msg(self, first=False):
        if first:
            return lambda line: line.rstrip(' ') + ' (2x)'
        else:
            incr = lambda match: f" ({str(int(match.group('num')) + 1)}x)"
            return lambda line: self.repeated_message_pattern.sub(incr, line)

    def _print(self, *args, sep=' ', pre='\n', **kwargs):
        new_msg = sep.join(str(x) for x in args)

        buffer = self.buffers['MSG_BUFFER']

        last_line = buffer.document.current_line
        line_parts = self.repeated_message_pattern.split(last_line)
        last_msg = line_parts[0]

        if last_msg != new_msg:
            buffer.insert_text(pre + new_msg)
        else:
            buffer.transform_current_line(self._increment_repeated_msg(first=len(line_parts) == 1))
            buffer.cursor_right(len(buffer.document.current_line))

    def print(self, *args, **kwargs):
        if _is_main_thread():
            self._print(*args, **kwargs)
        else:
            self.cli.eventloop.call_from_executor(lambda: self._print(*args, **kwargs))

    # TODO
    def prompt(self, message, end='>'):
        ...

    def _update_info_text(self, buff=None):
        buff = buff or self.stat_buffer_state.current
        buffer = statinfo.Stats.get_name(buff)

        if buffer:
            buffername = buffer.name
        else:
            return

        text = statinfo.extra_info.get(buffername, f'TODO: {buffername} text')

        if text:
            self.set_info_text(text)

    def _focus(self, buffer, cli=None):
        cli = cli or self.cli

        cli.focus(buffer)
        self._update_info_text()

    # Don't question these double half scrolls this is completely correct
    def _scroll_up(self):
        if self._scroll_state < 0:
            scroll.scroll_half_page_up(*self.info_wb)
            scroll.scroll_half_page_up(*self.info_wb)

        scroll.scroll_half_page_up(*self.info_wb)
        self._scroll_state = 1

    def _scroll_down(self):
        if self._scroll_state > 0:
            scroll.scroll_half_page_down(*self.info_wb)
            scroll.scroll_half_page_down(*self.info_wb)

        scroll.scroll_half_page_down(*self.info_wb)
        self._scroll_state = -1

    # Setup functions

    def _gen_buffers(self):
        self.stat_buffer_state = BufferState()

        return {
            DEFAULT_BUFFER: Buffer(
                initial_document=Document(""),
                is_multiline=False, read_only=False),

            'INFO_BUFFER': Buffer(
                initial_document=Document(),
                is_multiline=True),

            'REROLL_STAT_BUFFER': Buffer(
                initial_document=make_stat_doc('Rerolls', 0),
                is_multiline=False),

            'MSG_BUFFER': Buffer(
                initial_document=Document(),
                is_multiline=True),

            **{
               stat.buffername: Buffer(initial_document=make_stat_doc(stat.name))
               for stat in statinfo.Stats.all()
            }
        }

    # TODO: Lexers
    # from pygments.lexers import HtmlLexer
    # from prompt_toolkit.layout.lexers import PygmentsLexer
    # BufferControl(lexer=PygmentsLexer(HtmlLexer))

    def _gen_layout(self):
        stat_windows = []

        for stat_group in statinfo.groups:
            for stat in stat_group:
                stat_windows.append(make_stat_window(stat))

            stat_windows.append(vpad(1))

        stat_windows.append(Window(
            content=BufferControl(buffer_name='REROLL_STAT_BUFFER'), **stat_args))
        stat_windows.append(vpad(1))

        @Condition
        def scroll_cond(cli):
            if self.info_window.render_info is None:
                return True

            try:
                l = self.buffers['INFO_BUFFER'].document.line_count
                return self.info_window.render_info.window_height < l
            except:
                return True

        self.info_window = Window(
            content=BufferControl(buffer_name='INFO_BUFFER'),
            dont_extend_width=True, wrap_lines=True, always_hide_cursor=True,
            right_margins=[ConditionalMargin(
                ScrollbarMargin(display_arrows=True), scroll_cond)
            ])

        return HSplit([
            hpad(1),
            VSplit([
                vpad(1),
                HSplit(stat_windows),
                vpad(2), # idk why there's an extra space on the stats
                self.info_window,
                vpad(1)
            ]),
            hpad(1),
            Window(content=BufferControl(buffer_name='MSG_BUFFER'),
                   height=D.exact(3), wrap_lines=True),
            Window(content=BufferControl(buffer_name=DEFAULT_BUFFER),
                   height=D.exact(1), always_hide_cursor=True)
        ])

    def _gen_bindings(self):
        registry = Registry()
        bind = registry.add_binding

        def bind_with_help(*args, name, info='', **kwargs):
            def dec(func):
                self._help_text[name] = info

                return func
            return bind(*args, **kwargs)(dec)

        bind(Keys.Left)(get_by_name('backward-char'))
        bind(Keys.Right)(get_by_name('forward-char'))

        @bind(Keys.ControlC)
        def _(event):
            event.cli.set_return_value(None)

        @bind_with_help('?', name='Help', info="Shows a help screen")
        def _(event):
            if self._help_showing:
                self._help_showing = False
                self._update_info_text()
                return

            self.set_info_text(help_text)
            self._help_showing = True

        @bind(Keys.Up)
        def _(event):
            self._focus(self.stat_buffer_state.up())

        @bind(Keys.Down)
        def _(event):
            self._focus(self.stat_buffer_state.down())

        @bind_with_help('t', name='Reroll test')
        def _(event):
            self.print("Dispatching runner")

            def do():
                self.print("ok running")

                num = 50
                t0 = time.time()

                for x in range(num):
                    # self.reroll(delay=0.017, warning="WARNING SAME STATS ROLLED")
                    self.reroll(delay=0.09, retry_delay=0.05, retry=1)
                    self.print("Rolled")

                t1 = time.time()
                self.print(f'Rolled {num} times in {t1-t0:.4f}', 'sec')

            event.cli.eventloop.run_in_executor(do)
            self.print("alrighty then")

        @bind('r', name='Reroll')
        def _(event):
            l = self.reroll()
            self.set_stats(**self.hook.zip(l))

        @bind('e', name='Update stats')
        def _(event):
            self.set_stats(**self.hook.zip(self.hook.read_all()))

        @bind('l')
        def _(event):
            self.print(f"testing executor in {__import__('threading').currentThread()}")

            def do():
                self.print(f'ok whatever {__import__("threading").currentThread()}')
                # event.cli.eventloop.call_from_executor(lambda: )

            event.cli.eventloop.run_in_executor(do)
            self.print("ok sure done")

        @bind('o')
        def _(event):
            self.set_info_text(self._help_text)

        @bind('E', name='Embed IPython')
        def _(event):
            def do():
                self, event # behold the magic of closures and scope

                __import__('IPython').embed()
                os.system('cls')

            event.cli.run_in_terminal(do)

        @bind(Keys.PageUp)
        def _(event):
            self._scroll_up()

        @bind(Keys.PageDown)
        def _(event):
            self._scroll_down()

        @bind('-')
        def _(event):
            self.print("got random stats")
            self.set_stats(**memhook.get_random_stats())

        @Condition
        def _in_stat_buffer(cli):
            return any(b == cli.current_buffer for n,b in self.buffers.items() if n.endswith("_STAT_BUFFER"))

        @bind(Keys.Enter, filter=_in_stat_buffer)
        def _(event):
            buf = event.cli.current_buffer
            self.set_info_text(f"Enter on buffer {buf} at {buf.cursor_position}")

        return registry

    def _add_events(self):
        def default_buffer_changed(_):
            self.buffers['INFO_BUFFER'].reset(self.buffers[DEFAULT_BUFFER].document)

        self.buffers[DEFAULT_BUFFER].on_text_changed += default_buffer_changed

    def _finalize_build(self):
        self.set_info_text(help_text)

        self.print("UnReal World Stat Roller v2.0")
        self.print("Press ? for help\n")

    def build(self):
        self.buffers = self._gen_buffers()
        self.layout = self._gen_layout()
        self.registry = self._gen_bindings()

        self._add_events()

        self.application = Application(
            layout=self.layout,
            buffers=self.buffers,
            key_bindings_registry=self.registry,
            mouse_support=True,
            use_alternate_screen=alt)

        self.cli = CommandLineInterface(
            application=self.application, eventloop=create_eventloop())

        self._finalize_build()

        self._built = True
        return self

    def run(self, build=True):
        if build and not self._built:
            self.build()

        elif not self._built:
            raise RuntimeError("UI has not been built yet")

        try:
            self.cli.run()
        finally:
            self.cli.eventloop.close()

    def redraw(self):
        if _is_main_thread():
            self.cli._redraw()
        else:
            self.cli.invalidate()

    def reroll(self, **kw):
        new_stats = self.hook.safe_reroll(**kw)

        self.set_stats(**self.hook.zip(new_stats))
        self.stat_state['Rerolls'] += 1

        self.redraw()

    def set_stat(self, stat, value):
        self.stat_state[stat] = value
        self.buffers[statinfo.Stats.get(stat).buffername].reset(
            make_stat_doc(stat, value))

    def set_stats(self, **stats):
        for stat, value in stats.items():
            self.set_stat(stat, value)

    def _make_info_text(self, text):
        parts = str(text).strip().split('\n\n')
        filled_parts = [textwrap.fill(t, 35) for t in parts]
        return '\n\n'.join(filled_parts)

    def set_info_text(self, text):
        text = self._make_info_text(text)
        self.buffers['INFO_BUFFER'].reset(Document(text, cursor_position=0))

    def append_info_text(self, text, sep='\n'):
        text = self._make_info_text(text)
        buffer = self.buffers['INFO_BUFFER']

        newdoc = Document(buffer.document.text + sep + text,
                          cursor_position=buffer.document.cursor_position)

        buffer.reset(newdoc)
        buffer.on_text_changed.fire()

    def _make_help_text(self):
        return help_text

    def on_error(self, *args):
        self.print(f"An error has occurred: {traceback.format_exception(*args)}")
        traceback.print_exception(*args)

# TODO:
#   Race info and stat bounds helpers/warnings
#   Cheat mode, turn all the text hacker green
#     Undo button (stat history)
#     Custom roll bounds and distribution (generate and set)
<|endoftext|>


<commit_before># coding=utf-8
from models import Strike, Region, Company
from django.contrib.auth.models import User
from forms import SubmitForm
from django.contrib.auth import logout as django_logout
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_POST
from django.http import HttpResponse, HttpResponseServerError, HttpResponseRedirect, HttpResponseNotFound
from django.shortcuts import render_to_response, get_object_or_404
from django.core.exceptions import ObjectDoesNotExist
from django.utils.datastructures import SortedDict
from django.template import RequestContext
import locale
from datetime import datetime, date, timedelta
from operator import itemgetter, attrgetter

locale.setlocale(locale.LC_ALL, "pt_PT.UTF-8")

def index(request):
    latest_strikes = Strike.objects.filter(end_date__gte=datetime.today().date()).order_by('start_date').exclude(approved=False)[:20]
    companies = Company.objects.all()
    regions = Region.objects.all()

    strikes = SortedDict()

    hoje = datetime.today().strftime("%d")
    amanha = datetime.today().date() + timedelta(days=1)
    amanha = amanha.strftime("%d")

    for strike in latest_strikes:
        m = strike.start_date.strftime("%m")
        d = strike.start_date.strftime("%d")

        if strike.start_date < datetime.today():
            d = hoje

        if not strikes.has_key(m):
            strikes[m] = {"nome":strike.start_date.strftime("%B"), "dias":SortedDict()}
        if not strikes[m]["dias"].has_key(d):
            strikes[m]["dias"][d] = {'greves':{}}
        if not strikes[m]["dias"][d]['greves'].has_key(strike.company):
            strikes[m]["dias"][d]['greves'][strike.company] = []
        strikes[m]["dias"][d]['greves'][strike.company].append(strike)

    m = datetime.today().strftime("%m")
    if len(strikes) > 0 and strikes.has_key(m):
        fix = False
        if strikes[m]["dias"].has_key(amanha):
            strikes[m]["dias"][amanha]["alias"] = "Amanhã"
            fix = True

        if strikes[m]["dias"].has_key(hoje):
            strikes[m]["dias"][hoje]["alias"] = "Hoje"
            if fix:
                strikes[m]["dias"][hoje]["fix"] = "fixAmanha"
            for c in strikes[m]["dias"][hoje]["greves"]:
                cc = strikes[m]["dias"][hoje]["greves"][c]
                if len(cc) > 1:
                    strikes[m]["dias"][hoje]["greves"][c] = sorted(cc, key=MYattrgetter('start_date.day'), reverse=True)

    #strikes['04']["dias"] = sorted(strikes['04']["dias"])

    context = { 'strikes': strikes, 'regions': regions, 'host': request.get_host(), 'companies': companies }

    return render_to_response('index.html', context, context_instance=RequestContext(request))

def MYattrgetter(*items):
    if len(items) == 1:
        attr = items[0]
        def g(obj):
            return resolve_attr(obj, attr)
    else:
        def g(obj):
            return tuple(resolve_att(obj, attr) for attr in items)
    return g

def resolve_attr(obj, attr):
    for name in attr.split("."):
        obj = getattr(obj, name)
    return obj

def thanks(request):
    return render_to_response('thanks.html', context_instance=RequestContext(request))

@require_POST
def upvote(request):
    """Add an upvote to a strike"""
    try:
        strike_id = int(request.POST['strikeid'])
        strike = Strike.objects.get(id=strike_id)
    except KeyError:
        print "Error: bad parameter strikeid"
        return HttpResponseServerError()
    except ObjectDoesNotExist:
        print "Error: strike with id %d does not exist" % strike_id
        return HttpResponseServerError()
    else:
        strike.upvotes += 1
        strike.save()
        return HttpResponse()

@require_POST
def downvote(request):
    """Add a downvote to a strike"""
    try:
        strike_id = int(request.POST['strikeid'])
        strike = Strike.objects.get(id=strike_id)
    except KeyError:
        print "Error: bad parameter strikeid"
        return HttpResponseServerError()
    except ObjectDoesNotExist:
        print "Error: strike with id %d does not exist" % strike_id
        return HttpResponseServerError()
    else:
        strike.downvotes += 1
        strike.save()
        return HttpResponse()

@login_required
@csrf_protect
def submit(request):
    if request.method == 'POST':
        form = SubmitForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect('/thanks')
    else:
        form = SubmitForm(initial={'submitter': request.user.id})

    return render_to_response('submit.html', { 'form': form }, context_instance=RequestContext(request))

@login_required
@csrf_protect
def edit(request, strike_id):
    try:
        strike = Strike.objects.get(pk=strike_id)
    except ObjectDoesNotExist:
        return HttpResponseNotFound()
    if request.method == 'POST':
        form = SubmitForm(request.POST, instance=strike)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect('/thanks')
    else:
        form = SubmitForm(instance=strike)

    return render_to_response('submit.html', { 'form': form }, context_instance=RequestContext(request))

@login_required
def submissions(request):
    latest_strikes = Strike.objects.filter(start_date__gte=datetime.today().date()).order_by('start_date').filter(approved=False).exclude(submitter=1)
    companies = Company.objects.all()
    regions = Region.objects.all()

    strikes = SortedDict()

    hoje = datetime.today().strftime("%d")
    amanha = datetime.today().date() + timedelta(days=1)
    amanha = amanha.strftime("%d")

    for strike in latest_strikes:
        m = strike.start_date.strftime("%m")
        d = strike.start_date.strftime("%d")

        if not strikes.has_key(m):
            strikes[m] = {"nome":strike.start_date.strftime("%B"), "dias":SortedDict()}
        if not strikes[m]["dias"].has_key(d):
            strikes[m]["dias"][d] = {'greves':{}}
        if not strikes[m]["dias"][d]['greves'].has_key(strike.company):
            strikes[m]["dias"][d]['greves'][strike.company] = []
        strikes[m]["dias"][d]['greves'][strike.company].append(strike)

    if len(strikes) > 0:
        fix = False
        if strikes[m]["dias"].has_key(amanha):
            strikes[m]["dias"][amanha]["alias"] = "Amanhã"
            fix = True

        if strikes[m]["dias"].has_key(hoje):
            strikes[m]["dias"][hoje]["alias"] = "Hoje"
            if fix:
                strikes[m]["dias"][hoje]["fix"] = "fixAmanha"

    #strikes['04']["dias"] = sorted(strikes['04']["dias"])

    context = { 'strikes': strikes, 'regions': regions, 'host': request.get_host(), 'companies': companies }

    return render_to_response('submissions.html', context, context_instance=RequestContext(request))

def login(request):
    return render_to_response('login.html')

def logout(request):
    django_logout(request)
    return index(request)
<commit_msg>Changed exception to a more common Django idiom.<commit_after># coding=utf-8
from models import Strike, Region, Company
from django.contrib.auth.models import User
from forms import SubmitForm
from django.contrib.auth import logout as django_logout
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_POST
from django.http import HttpResponse, HttpResponseServerError, HttpResponseRedirect
from django.shortcuts import render_to_response, get_object_or_404
from django.core.exceptions import ObjectDoesNotExist
from django.utils.datastructures import SortedDict
from django.template import RequestContext
import locale
from datetime import datetime, date, timedelta
from operator import itemgetter, attrgetter

locale.setlocale(locale.LC_ALL, "pt_PT.UTF-8")

def index(request):
    latest_strikes = Strike.objects.filter(end_date__gte=datetime.today().date()).order_by('start_date').exclude(approved=False)[:20]
    companies = Company.objects.all()
    regions = Region.objects.all()

    strikes = SortedDict()

    hoje = datetime.today().strftime("%d")
    amanha = datetime.today().date() + timedelta(days=1)
    amanha = amanha.strftime("%d")

    for strike in latest_strikes:
        m = strike.start_date.strftime("%m")
        d = strike.start_date.strftime("%d")

        if strike.start_date < datetime.today():
            d = hoje

        if not strikes.has_key(m):
            strikes[m] = {"nome":strike.start_date.strftime("%B"), "dias":SortedDict()}
        if not strikes[m]["dias"].has_key(d):
            strikes[m]["dias"][d] = {'greves':{}}
        if not strikes[m]["dias"][d]['greves'].has_key(strike.company):
            strikes[m]["dias"][d]['greves'][strike.company] = []
        strikes[m]["dias"][d]['greves'][strike.company].append(strike)

    m = datetime.today().strftime("%m")
    if len(strikes) > 0 and strikes.has_key(m):
        fix = False
        if strikes[m]["dias"].has_key(amanha):
            strikes[m]["dias"][amanha]["alias"] = "Amanhã"
            fix = True

        if strikes[m]["dias"].has_key(hoje):
            strikes[m]["dias"][hoje]["alias"] = "Hoje"
            if fix:
                strikes[m]["dias"][hoje]["fix"] = "fixAmanha"
            for c in strikes[m]["dias"][hoje]["greves"]:
                cc = strikes[m]["dias"][hoje]["greves"][c]
                if len(cc) > 1:
                    strikes[m]["dias"][hoje]["greves"][c] = sorted(cc, key=MYattrgetter('start_date.day'), reverse=True)

    #strikes['04']["dias"] = sorted(strikes['04']["dias"])

    context = { 'strikes': strikes, 'regions': regions, 'host': request.get_host(), 'companies': companies }

    return render_to_response('index.html', context, context_instance=RequestContext(request))

def MYattrgetter(*items):
    if len(items) == 1:
        attr = items[0]
        def g(obj):
            return resolve_attr(obj, attr)
    else:
        def g(obj):
            return tuple(resolve_att(obj, attr) for attr in items)
    return g

def resolve_attr(obj, attr):
    for name in attr.split("."):
        obj = getattr(obj, name)
    return obj

def thanks(request):
    return render_to_response('thanks.html', context_instance=RequestContext(request))

@require_POST
def upvote(request):
    """Add an upvote to a strike"""
    try:
        strike_id = int(request.POST['strikeid'])
        strike = Strike.objects.get(id=strike_id)
    except KeyError:
        print "Error: bad parameter strikeid"
        return HttpResponseServerError()
    except ObjectDoesNotExist:
        print "Error: strike with id %d does not exist" % strike_id
        return HttpResponseServerError()
    else:
        strike.upvotes += 1
        strike.save()
        return HttpResponse()

@require_POST
def downvote(request):
    """Add a downvote to a strike"""
    try:
        strike_id = int(request.POST['strikeid'])
        strike = Strike.objects.get(id=strike_id)
    except KeyError:
        print "Error: bad parameter strikeid"
        return HttpResponseServerError()
    except ObjectDoesNotExist:
        print "Error: strike with id %d does not exist" % strike_id
        return HttpResponseServerError()
    else:
        strike.downvotes += 1
        strike.save()
        return HttpResponse()

@login_required
@csrf_protect
def submit(request):
    if request.method == 'POST':
        form = SubmitForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect('/thanks')
    else:
        form = SubmitForm(initial={'submitter': request.user.id})

    return render_to_response('submit.html', { 'form': form }, context_instance=RequestContext(request))

@login_required
@csrf_protect
def edit(request, strike_id):
    strike = get_object_or_404(Strike, pk=strike_id)
    if request.method == 'POST':
        form = SubmitForm(request.POST, instance=strike)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect('/thanks')
    else:
        form = SubmitForm(instance=strike)

    return render_to_response('submit.html', { 'form': form }, context_instance=RequestContext(request))

@login_required
def submissions(request):
    latest_strikes = Strike.objects.filter(start_date__gte=datetime.today().date()).order_by('start_date').filter(approved=False).exclude(submitter=1)
    companies = Company.objects.all()
    regions = Region.objects.all()

    strikes = SortedDict()

    hoje = datetime.today().strftime("%d")
    amanha = datetime.today().date() + timedelta(days=1)
    amanha = amanha.strftime("%d")

    for strike in latest_strikes:
        m = strike.start_date.strftime("%m")
        d = strike.start_date.strftime("%d")

        if not strikes.has_key(m):
            strikes[m] = {"nome":strike.start_date.strftime("%B"), "dias":SortedDict()}
        if not strikes[m]["dias"].has_key(d):
            strikes[m]["dias"][d] = {'greves':{}}
        if not strikes[m]["dias"][d]['greves'].has_key(strike.company):
            strikes[m]["dias"][d]['greves'][strike.company] = []
        strikes[m]["dias"][d]['greves'][strike.company].append(strike)

    if len(strikes) > 0:
        fix = False
        if strikes[m]["dias"].has_key(amanha):
            strikes[m]["dias"][amanha]["alias"] = "Amanhã"
            fix = True

        if strikes[m]["dias"].has_key(hoje):
            strikes[m]["dias"][hoje]["alias"] = "Hoje"
            if fix:
                strikes[m]["dias"][hoje]["fix"] = "fixAmanha"

    #strikes['04']["dias"] = sorted(strikes['04']["dias"])

    context = { 'strikes': strikes, 'regions': regions, 'host': request.get_host(), 'companies': companies }

    return render_to_response('submissions.html', context, context_instance=RequestContext(request))

def login(request):
    return render_to_response('login.html')

def logout(request):
    django_logout(request)
    return index(request)
<|endoftext|>