<commit_before>from collections import defaultdict
from .common import error_info
from .common import fail
from .env import env
from .errors import Errors
from .scope import Scope
from .ir import CONST, TEMP, MOVE
from .irvisitor import IRVisitor, IRTransformer
from .type import Type
from .typecheck import TypePropagation

class PortTypeProp(TypePropagation):
    def visit_NEW(self, ir):
        if ir.func_scope.is_port():
            assert self.scope.is_ctor() and self.scope.parent.is_module()
            attrs = {}
            ctor = ir.func_scope.find_ctor()
            for (_, a), p in zip(ir.args, ctor.params[1:]):
                if a.is_a(CONST):
                    if p.copy.name == 'direction':
                        attrs[p.copy.name] = self._normalize_direction(a.value)
                    else:
                        attrs[p.copy.name] = a.value
                elif a.is_a(TEMP) and a.symbol().typ.is_class():
                    attrs[p.copy.name] = a.symbol().typ.name
                else:
                    fail(self.current_stm, Errors.PORT_PARAM_MUST_BE_CONST)
                    print(error_info(self.scope, ir.lineno))
                    raise RuntimeError('')

            assert len(ir.func_scope.type_args) == 1
            attrs['dtype'] = ir.func_scope.type_args[0]
            if 'direction' not in attrs or attrs['direction'] == 'auto':
                attrs['direction'] = '?'
            attrs['root_symbol'] = self.current_stm.dst.symbol()
            if self.current_stm.is_a(MOVE) and self.current_stm.dst.is_a(TEMP):
                attrs['port_kind'] = 'internal'
            else:
                if attrs['direction'] == 'input' or attrs['direction'] == 'output':
                    attrs['port_kind'] = 'external'
                else:
                    attrs['port_kind'] = 'internal'
            if 'protocol' not in attrs:
                attrs['protocol'] = 'none'
            if 'init' not in attrs or attrs['init'] is None:
                attrs['init'] = 0
            port_typ = Type.port(ir.func_scope, attrs)
            #port_typ.freeze()
            ir.func_scope.return_type = port_typ
        return ir.func_scope.return_type

    def _set_type(self, sym, typ):
        if sym.typ.is_object() and sym.typ.get_scope().is_port() and typ.is_port():
            sym.set_type(typ)

    def _normalize_direction(self, di):
        if di == 'in' or di == 'input' or di == 'i':
            return 'input'
        elif di == 'out' or di == 'output' or di == 'o':
            return 'output'
        elif di == 'any':
            return 'any'
        return '?'

    def visit_CALL(self, ir):
        if ir.func_scope.is_method() and ir.func_scope.parent.is_port():
            sym = ir.func.tail()
            assert sym.typ.is_port()
            kind = sym.typ.get_port_kind()
            root = sym.typ.get_root_symbol()
            port_owner = root.scope
            # if port is a local variable, we modify the port owner its parent
            if port_owner.is_method():
                port_owner = port_owner.parent
            if self.scope.is_worker():
                scope = self.scope.worker_owner
            elif self.scope.is_method():
                scope = self.scope.parent
            else:
                scope = self.scope
            if not scope.is_subclassof(port_owner) and kind == 'internal':
                fail(self.current_stm, Errors.PORT_ACCESS_IS_NOT_ALLOWED)
        return super().visit_CALL(ir)

class PortConverter(IRTransformer):
    def __init__(self):
        super().__init__()

    def process_all(self):
        scopes = Scope.get_scopes(with_class=True)
        modules = [s for s in scopes if s.is_module()]
        if not modules:
            return
        typeprop = PortTypeProp()
        for m in modules:
            if not m.is_instantiated():
                continue
            ctor = m.find_ctor()
            assert ctor
            typeprop.process(ctor)
            for w, args in m.workers:
                typeprop.process(w)
            for caller in env.call_graph.preds(m):
                if caller.is_namespace():
                    continue
                typeprop.process(caller)

            self.union_ports = defaultdict(set)
            self.process(ctor)
            for w, args in m.workers:
                self.process(w)
            for caller in env.call_graph.preds(m):
                if caller.is_namespace():
                    continue
                self.process(caller)

            for field in m.class_fields().values():
                if field.typ.is_port() and field.typ.get_direction() == '?':
                    if not env.call_graph.preds(m):
                        continue
                    assert ctor.usedef
                    stm = ctor.usedef.get_stms_defining(field).pop()
                    fail(stm, Errors.PORT_IS_NOT_USED,
                         [field.orig_name()])
            for sym in ctor.symbols.values():
                if sym.typ.is_port() and sym.typ.get_direction() == '?':

                    assert ctor.usedef
                    stms = ctor.usedef.get_stms_defining(sym)
                    # This symbol might not be used (e.g. ancestor symbol),
                    # so we have to check if its definition statement exists.
                    if stms:
                        fail(stms.pop(), Errors.PORT_IS_NOT_USED,
                             [sym.orig_name()])

    def _set_and_check_port_direction(self, expected_di, sym):
        port_typ = sym.typ
        if not port_typ.has_direction():
            port_typ.set_direction('?')
        di = port_typ.get_direction()
        kind = port_typ.get_port_kind()
        if kind == 'external':
            if di == '?':
                port_typ.set_direction(expected_di)
                port_typ.freeze()
            elif di == 'any':
                port_typ.set_port_kind('internal')
                port_typ.set_direction('inout')
            elif di != expected_di:
                if sym.ancestor and sym.ancestor.scope is not sym.scope:
                    # the port has been accessed as opposite direction
                    # by a module includes original owner module
                    port_typ.set_port_kind('internal')
                    port_typ.set_direction('inout')
                else:
                    fail(self.current_stm, Errors.DIRECTION_IS_CONFLICTED,
                         [sym.orig_name()])
        elif kind == 'internal':
            port_typ.set_direction('inout')

        if expected_di == 'output':
            # write-write conflict
            if port_typ.has_writer():
                writer = port_typ.get_writer()
                if writer is not self.scope and writer.worker_owner is self.scope.worker_owner:
                    fail(self.current_stm, Errors.WRITING_IS_CONFLICTED,
                         [sym.orig_name()])
            else:
                assert self.scope.is_worker() or self.scope.parent.is_module()
                port_typ.set_writer(self.scope)
        elif expected_di == 'input' and port_typ.get_scope().name.startswith('polyphony.io.Queue'):
            # read-read conflict
            if port_typ.has_reader():
                reader = port_typ.get_reader()
                if reader is not self.scope and reader.worker_owner is self.scope.worker_owner:
                    fail(self.current_stm, Errors.READING_IS_CONFLICTED,
                         [sym.orig_name()])
            else:
                assert self.scope.is_worker() or self.scope.parent.is_module()
                port_typ.set_reader(self.scope)

    def _get_port_owner(self, sym):
        assert sym.typ.is_port()
        root = sym.typ.get_root_symbol()
        if root.scope.is_ctor():
            return root.scope.parent
        else:
            return root.scope

    def _check_port_direction(self, sym, func_scope):
        if func_scope.orig_name == 'wr':
            expected_di = 'output'
        else:
            expected_di = 'input'
        port_owner = self._get_port_owner(sym)
        if ((self.scope.is_worker() and not self.scope.worker_owner.is_subclassof(port_owner)) or
                not self.scope.is_worker()):
            expected_di = 'output' if expected_di == 'input' else 'input'
        if sym in self.union_ports:
            for s in self.union_ports[sym]:
                self._set_and_check_port_direction(expected_di, s)
        else:
            self._set_and_check_port_direction(expected_di, sym)

    def visit_CALL(self, ir):
        if not ir.func_scope.is_lib():
            return ir
        if ir.func_scope.is_method() and ir.func_scope.parent.is_port():
            sym = ir.func.tail()
            assert sym.typ.is_port()
            self._check_port_direction(sym, ir.func_scope)
            if self.current_stm.block.synth_params['scheduling'] == 'pipeline' and self.current_stm.block.parent:
                root_sym = sym.typ.get_root_symbol()
                root_sym.add_tag('pipelined')
        return ir

    def visit_SYSCALL(self, ir):
        if ir.sym.name.startswith('polyphony.timing.wait_'):
            if (ir.sym.name == 'polyphony.timing.wait_rising' or
                    ir.sym.name == 'polyphony.timing.wait_falling'):
                ports = ir.args
            elif ir.sym.name == 'polyphony.timing.wait_edge':
                ports = ir.args[2:]
            elif ir.sym.name == 'polyphony.timing.wait_value':
                ports = ir.args[1:]
            for _, p in ports:
                port = p.symbol().typ
                assert port.is_port()
                di = port.get_direction()
                kind = port.get_port_kind()
                if kind == 'external':
                    port_owner = self._get_port_owner(p.symbol())
                    assert di != '?'
                    #port.set_direction('input')
                    #port.freeze()
                    if ((self.scope.is_worker() and not self.scope.worker_owner.is_subclassof(port_owner)) or
                            not self.scope.is_worker()):
                        if di == 'input':
                            fail(self.current_stm, Errors.CANNOT_WAIT_INPUT)
                    else:
                        if di == 'output':
                            fail(self.current_stm, Errors.CANNOT_WAIT_OUTPUT)
        return ir

    def visit_PHI(self, ir):
        if ir.var.symbol().typ.is_port():
            for arg in ir.args:
                self.union_ports[ir.var.symbol()].add(arg.symbol())
        super().visit_PHI(ir)

class FlattenPortList(IRTransformer):
    def visit_MREF(self, ir):
        memsym = ir.mem.symbol()
        memtyp = memsym.typ
        assert memtyp.is_seq()
        elm_t = memtyp.get_element()
        if not elm_t.is_object():
            return ir
        if not elm_t.get_scope().is_port():
            return ir
        if not ir.offset.is_a(CONST):
            return ir
        portname = '{}_{}'.format(memsym.name, ir.offset.value)
        scope = ir.mem.symbol().scope
        portsym = scope.find_sym(portname)
        assert portsym
        ir.mem.set_symbol(portsym)
        return ir.mem
<commit_msg>Fix wrong assertion<commit_after>from collections import defaultdict
from .common import error_info
from .common import fail
from .env import env
from .errors import Errors
from .scope import Scope
from .ir import CONST, TEMP, MOVE
from .irvisitor import IRVisitor, IRTransformer
from .type import Type
from .typecheck import TypePropagation

class PortTypeProp(TypePropagation):
    def visit_NEW(self, ir):
        if ir.func_scope.is_port():
            assert self.scope.is_ctor() and self.scope.parent.is_module()
            attrs = {}
            ctor = ir.func_scope.find_ctor()
            for (_, a), p in zip(ir.args, ctor.params[1:]):
                if a.is_a(CONST):
                    if p.copy.name == 'direction':
                        attrs[p.copy.name] = self._normalize_direction(a.value)
                    else:
                        attrs[p.copy.name] = a.value
                elif a.is_a(TEMP) and a.symbol().typ.is_class():
                    attrs[p.copy.name] = a.symbol().typ.name
                else:
                    fail(self.current_stm, Errors.PORT_PARAM_MUST_BE_CONST)
                    print(error_info(self.scope, ir.lineno))
                    raise RuntimeError('')

            assert len(ir.func_scope.type_args) == 1
            attrs['dtype'] = ir.func_scope.type_args[0]
            if 'direction' not in attrs or attrs['direction'] == 'auto':
                attrs['direction'] = '?'
            attrs['root_symbol'] = self.current_stm.dst.symbol()
            if self.current_stm.is_a(MOVE) and self.current_stm.dst.is_a(TEMP):
                attrs['port_kind'] = 'internal'
            else:
                if attrs['direction'] == 'input' or attrs['direction'] == 'output':
                    attrs['port_kind'] = 'external'
                else:
                    attrs['port_kind'] = 'internal'
            if 'protocol' not in attrs:
                attrs['protocol'] = 'none'
            if 'init' not in attrs or attrs['init'] is None:
                attrs['init'] = 0
            port_typ = Type.port(ir.func_scope, attrs)
            #port_typ.freeze()
            ir.func_scope.return_type = port_typ
        return ir.func_scope.return_type

    def _set_type(self, sym, typ):
        if sym.typ.is_object() and sym.typ.get_scope().is_port() and typ.is_port():
            sym.set_type(typ)

    def _normalize_direction(self, di):
        if di == 'in' or di == 'input' or di == 'i':
            return 'input'
        elif di == 'out' or di == 'output' or di == 'o':
            return 'output'
        elif di == 'any':
            return 'any'
        return '?'

    def visit_CALL(self, ir):
        if ir.func_scope.is_method() and ir.func_scope.parent.is_port():
            sym = ir.func.tail()
            assert sym.typ.is_port()
            kind = sym.typ.get_port_kind()
            root = sym.typ.get_root_symbol()
            port_owner = root.scope
            # if port is a local variable, we modify the port owner its parent
            if port_owner.is_method():
                port_owner = port_owner.parent
            if self.scope.is_worker():
                scope = self.scope.worker_owner
            elif self.scope.is_method():
                scope = self.scope.parent
            else:
                scope = self.scope
            if not scope.is_subclassof(port_owner) and kind == 'internal':
                fail(self.current_stm, Errors.PORT_ACCESS_IS_NOT_ALLOWED)
        return super().visit_CALL(ir)

class PortConverter(IRTransformer):
    def __init__(self):
        super().__init__()

    def process_all(self):
        scopes = Scope.get_scopes(with_class=True)
        modules = [s for s in scopes if s.is_module()]
        if not modules:
            return
        typeprop = PortTypeProp()
        for m in modules:
            if not m.is_instantiated():
                continue
            ctor = m.find_ctor()
            assert ctor
            typeprop.process(ctor)
            for w, args in m.workers:
                typeprop.process(w)
            for caller in env.call_graph.preds(m):
                if caller.is_namespace():
                    continue
                typeprop.process(caller)

            self.union_ports = defaultdict(set)
            self.process(ctor)
            for w, args in m.workers:
                self.process(w)
            for caller in env.call_graph.preds(m):
                if caller.is_namespace():
                    continue
                self.process(caller)

            for field in m.class_fields().values():
                if field.typ.is_port() and field.typ.get_direction() == '?':
                    if not env.call_graph.preds(m):
                        continue
                    assert ctor.usedef
                    stm = ctor.usedef.get_stms_defining(field).pop()
                    fail(stm, Errors.PORT_IS_NOT_USED,
                         [field.orig_name()])
            for sym in ctor.symbols.values():
                if sym.typ.is_port() and sym.typ.get_direction() == '?':

                    assert ctor.usedef
                    stms = ctor.usedef.get_stms_defining(sym)
                    # This symbol might not be used (e.g. ancestor symbol),
                    # so we have to check if its definition statement exists.
                    if stms:
                        fail(stms.pop(), Errors.PORT_IS_NOT_USED,
                             [sym.orig_name()])

    def _set_and_check_port_direction(self, expected_di, sym):
        port_typ = sym.typ
        if not port_typ.has_direction():
            port_typ.set_direction('?')
        di = port_typ.get_direction()
        kind = port_typ.get_port_kind()
        if kind == 'external':
            if di == '?':
                port_typ.set_direction(expected_di)
                port_typ.freeze()
            elif di == 'any':
                port_typ.set_port_kind('internal')
                port_typ.set_direction('inout')
            elif di != expected_di:
                if sym.ancestor and sym.ancestor.scope is not sym.scope:
                    # the port has been accessed as opposite direction
                    # by a module includes original owner module
                    port_typ.set_port_kind('internal')
                    port_typ.set_direction('inout')
                else:
                    fail(self.current_stm, Errors.DIRECTION_IS_CONFLICTED,
                         [sym.orig_name()])
        elif kind == 'internal':
            port_typ.set_direction('inout')

        if expected_di == 'output':
            # write-write conflict
            if port_typ.has_writer():
                writer = port_typ.get_writer()
                if writer is not self.scope and writer.worker_owner is self.scope.worker_owner:
                    fail(self.current_stm, Errors.WRITING_IS_CONFLICTED,
                         [sym.orig_name()])
            else:
                if kind == 'internal':
                    assert self.scope.is_worker() or self.scope.parent.is_module()
                port_typ.set_writer(self.scope)
        elif expected_di == 'input' and port_typ.get_scope().name.startswith('polyphony.io.Queue'):
            # read-read conflict
            if port_typ.has_reader():
                reader = port_typ.get_reader()
                if reader is not self.scope and reader.worker_owner is self.scope.worker_owner:
                    fail(self.current_stm, Errors.READING_IS_CONFLICTED,
                         [sym.orig_name()])
            else:
                if kind == 'internal':
                    assert self.scope.is_worker() or self.scope.parent.is_module()
                port_typ.set_reader(self.scope)

    def _get_port_owner(self, sym):
        assert sym.typ.is_port()
        root = sym.typ.get_root_symbol()
        if root.scope.is_ctor():
            return root.scope.parent
        else:
            return root.scope

    def _check_port_direction(self, sym, func_scope):
        if func_scope.orig_name == 'wr':
            expected_di = 'output'
        else:
            expected_di = 'input'
        port_owner = self._get_port_owner(sym)
        if ((self.scope.is_worker() and not self.scope.worker_owner.is_subclassof(port_owner)) or
                not self.scope.is_worker()):
            expected_di = 'output' if expected_di == 'input' else 'input'
        if sym in self.union_ports:
            for s in self.union_ports[sym]:
                self._set_and_check_port_direction(expected_di, s)
        else:
            self._set_and_check_port_direction(expected_di, sym)

    def visit_CALL(self, ir):
        if not ir.func_scope.is_lib():
            return ir
        if ir.func_scope.is_method() and ir.func_scope.parent.is_port():
            sym = ir.func.tail()
            assert sym.typ.is_port()
            self._check_port_direction(sym, ir.func_scope)
            if self.current_stm.block.synth_params['scheduling'] == 'pipeline' and self.current_stm.block.parent:
                root_sym = sym.typ.get_root_symbol()
                root_sym.add_tag('pipelined')
        return ir

    def visit_SYSCALL(self, ir):
        if ir.sym.name.startswith('polyphony.timing.wait_'):
            if (ir.sym.name == 'polyphony.timing.wait_rising' or
                    ir.sym.name == 'polyphony.timing.wait_falling'):
                ports = ir.args
            elif ir.sym.name == 'polyphony.timing.wait_edge':
                ports = ir.args[2:]
            elif ir.sym.name == 'polyphony.timing.wait_value':
                ports = ir.args[1:]
            for _, p in ports:
                port = p.symbol().typ
                assert port.is_port()
                di = port.get_direction()
                kind = port.get_port_kind()
                if kind == 'external':
                    port_owner = self._get_port_owner(p.symbol())
                    assert di != '?'
                    #port.set_direction('input')
                    #port.freeze()
                    if ((self.scope.is_worker() and not self.scope.worker_owner.is_subclassof(port_owner)) or
                            not self.scope.is_worker()):
                        if di == 'input':
                            fail(self.current_stm, Errors.CANNOT_WAIT_INPUT)
                    else:
                        if di == 'output':
                            fail(self.current_stm, Errors.CANNOT_WAIT_OUTPUT)
        return ir

    def visit_PHI(self, ir):
        if ir.var.symbol().typ.is_port():
            for arg in ir.args:
                self.union_ports[ir.var.symbol()].add(arg.symbol())
        super().visit_PHI(ir)

class FlattenPortList(IRTransformer):
    def visit_MREF(self, ir):
        memsym = ir.mem.symbol()
        memtyp = memsym.typ
        assert memtyp.is_seq()
        elm_t = memtyp.get_element()
        if not elm_t.is_object():
            return ir
        if not elm_t.get_scope().is_port():
            return ir
        if not ir.offset.is_a(CONST):
            return ir
        portname = '{}_{}'.format(memsym.name, ir.offset.value)
        scope = ir.mem.symbol().scope
        portsym = scope.find_sym(portname)
        assert portsym
        ir.mem.set_symbol(portsym)
        return ir.mem
<|endoftext|>


<commit_before>"""Server Module

Main Listening Server Component
"""

from datetime import datetime
from socket import has_ipv6
from logging import getLogger
from collections import defaultdict

from circuits import Event, Component, Timer

from circuits.net.sockets import TCPServer, TCP6Server

from circuits.protocols.irc import response, IRC

from pathlib import Path

from .models import User
from . import __name__, __url__, __version__

class setup(Event):
    """setup Event"""

class Server(Component):

    channel = "server"

    info = u"QLD, Australia"
    network = u"ShortCircuit"
    host = u"daisy.shortcircuit.net.au"
    created = datetime.utcnow()

    motd = Path("motd.txt")

    url = unicode(__url__)
    name = unicode(__name__)
    version = unicode(__version__)

    features = (
        u"NETWORK={0}".format(network),
    )

    def init(self, config, db):
        self.config = config
        self.db = db

        self.logger = getLogger(__name__)

        self.buffers = defaultdict(bytes)

        self.port = config["port"]

        if has_ipv6:
            self.address = "::"
            self.Transport = TCP6Server
        else:
            self.address = "0.0.0.0"
            self.Transport = TCPServer

        self.bind = (self.address, self.port)

        self.fire(setup())

    def setup(self):
        try:
            self.transport = self.Transport(
                self.bind,
                channel=self.channel
            ).register(self)

            self.protocol = IRC(
                channel=self.channel,
                getBuffer=self.buffers.__getitem__,
                updateBuffer=self.buffers.__setitem__
            ).register(self)
        except Exception as e:
            self.logger.error("Cannot start server: {0}".format(e))
            self.logger.info("Retrying in 5s ...")
            Timer(5, setup()).register(self)

    def ready(self, server, bind):
        self.logger.info(
            u"{0} v{1} ready! Listening on: {1}\n".format(
                self.name, self.version, u"{0}:{1}".format(*bind)
            )
        )

    def connect(self, sock, *args):
        host, port = args[:2]
        user = User(sock=sock, host=host, port=port)
        user.save()

    def disconnect(self, sock):
        user = User.objects.filter(sock=sock).first()
        if user is None:
            return

        nick = user.nick
        user, host = user.userinfo.user, user.userinfo.host

        quit = response.create("quit", sock, (nick, user, host), "Leavling")
        quit.complete = True
        quit.complete_channels = ("server",)

        self.fire(quit)

    def supports(self):
        return self.features
<commit_msg>fix typo<commit_after>"""Server Module

Main Listening Server Component
"""

from datetime import datetime
from socket import has_ipv6
from logging import getLogger
from collections import defaultdict

from circuits import Event, Component, Timer

from circuits.net.sockets import TCPServer, TCP6Server

from circuits.protocols.irc import response, IRC

from pathlib import Path

from .models import User
from . import __name__, __url__, __version__

class setup(Event):
    """setup Event"""

class Server(Component):

    channel = "server"

    info = u"QLD, Australia"
    network = u"ShortCircuit"
    host = u"daisy.shortcircuit.net.au"
    created = datetime.utcnow()

    motd = Path("motd.txt")

    url = unicode(__url__)
    name = unicode(__name__)
    version = unicode(__version__)

    features = (
        u"NETWORK={0}".format(network),
    )

    def init(self, config, db):
        self.config = config
        self.db = db

        self.logger = getLogger(__name__)

        self.buffers = defaultdict(bytes)

        self.port = config["port"]

        if has_ipv6:
            self.address = "::"
            self.Transport = TCP6Server
        else:
            self.address = "0.0.0.0"
            self.Transport = TCPServer

        self.bind = (self.address, self.port)

        self.fire(setup())

    def setup(self):
        try:
            self.transport = self.Transport(
                self.bind,
                channel=self.channel
            ).register(self)

            self.protocol = IRC(
                channel=self.channel,
                getBuffer=self.buffers.__getitem__,
                updateBuffer=self.buffers.__setitem__
            ).register(self)
        except Exception as e:
            self.logger.error("Cannot start server: {0}".format(e))
            self.logger.info("Retrying in 5s ...")
            Timer(5, setup()).register(self)

    def ready(self, server, bind):
        self.logger.info(
            u"{0} v{1} ready! Listening on: {1}\n".format(
                self.name, self.version, u"{0}:{1}".format(*bind)
            )
        )

    def connect(self, sock, *args):
        host, port = args[:2]
        user = User(sock=sock, host=host, port=port)
        user.save()

    def disconnect(self, sock):
        user = User.objects.filter(sock=sock).first()
        if user is None:
            return

        nick = user.nick
        user, host = user.userinfo.user, user.userinfo.host

        quit = response.create("quit", sock, (nick, user, host), "Leaving")
        quit.complete = True
        quit.complete_channels = ("server",)

        self.fire(quit)

    def supports(self):
        return self.features
<|endoftext|>


<commit_before># Module:   server
# Date:     16th August 2014
# Author:   James Mills, prologic at shortcircuit dot net dot au

"""Server Module

Main Listening Server Component
"""

from sys import stderr
from logging import getLogger
from collections import defaultdict

from bidict import bidict
from cidict import cidict

from circuits import handler, Component

from circuits.net.events import write
from circuits.net.sockets import TCPServer

from circuits.protocols.irc import reply, response, IRC

from circuits.protocols.irc.replies import (
    ERR_NOMOTD, ERR_UNKNOWNCOMMAND,
    RPL_WELCOME, RPL_YOURHOST,
)

from .models import User
from .version import version
from .plugin import BasePlugin

class Server(Component):

    channel = "server"

    network = "Test"
    host = "localhost"
    version = "ircd v{0:s}".format(version)

    def init(self, config, data):
        self.config = config
        self.data = data

        self.logger = getLogger(__name__)

        # command -> plugin
        self.command = cidict()

        # plugin name -> commands
        self.commands = cidict()

        # plugin name -> plugin
        self.plugins = cidict()

        self.data.init({
            "users": bidict(),
            "channels": cidict(),
        })

        self.buffers = defaultdict(bytes)

        if ":" in config["bind"]:
            address, port = config["bind"].split(":")
            port = int(port)
        else:
            address, port = config["bind"], 6667

        bind = (address, port)

        self.transport = TCPServer(
            bind,
            channel=self.channel
        ).register(self)

        self.protocol = IRC(
            channel=self.channel,
            getBuffer=self.buffers.__getitem__,
            updateBuffer=self.buffers.__setitem__
        ).register(self)

    @handler("registered", channel="*")
    def _on_registered(self, component, manager):
        if component.channel == "commands":
            for event in component.events():
                if event not in self.command:
                    self.command[event] = component

            if component.parent.name in self.commands:
                events = self.commands[component.parent.name]
                events = events.union(component.events())
                self.commands[component.parent.name] = events
            else:
                self.commands[component.parent.name] = set(component.events())

        if isinstance(component, BasePlugin):
            if component.name not in self.plugins:
                self.plugins[component.name] = component

    @handler("unregistered", channel="*")
    def _on_unregistered(self, component, manager):
        if component.channel == "commands":
            for event in component.events():
                if event in self.command:
                    del self.command[event]

        if isinstance(component, BasePlugin):
            if component.name in self.commands:
                del self.commands[component.name]
            if component.name in self.plugins:
                del self.plugins[component.name]

    def ready(self, server, bind):
        self.logger.info(
            "ircd v{0:s} ready! Listening on: {1:s}\n".format(
                version, "{0:s}:{1:d}".format(*bind)
            )
        )

    def connect(self, sock, host, port):
        user = User(sock, host, port)

        self.data.users[sock] = user

        self.logger.info("C: [{0:s}:{1:d}]".format(host, port))

    def disconnect(self, sock):
        if sock not in self.data.users:
            return

        user = self.data.users[sock]

        self.logger.info("D: [{0:s}:{1:d}]".format(user.host, user.port))

        nick = user.nick
        user, host = user.userinfo.user, user.userinfo.host

        yield self.call(
            response.create("quit", sock, (nick, user, host), "Leavling")
        )

        user.delete()
        user.save()

    def read(self, sock, data):
        user = self.data.users[sock]

        host, port = user.host, user.port

        self.logger.info(
            "I: [{0:s}:{1:d}] {2:s}".format(host, port, repr(data))
        )

    def write(self, sock, data):
        user = self.data.users.get(sock, User(sock))

        host, port = user.host, user.port

        self.logger.info(
            "O: [{0:s}:{1:d}] {2:s}".format(host, port, repr(data))
        )

    def broadcast(self, users, message, *exclude):
        for user in users:
            if user in exclude:
                continue

            self.fire(reply(user.sock, message))

    def signon(self, sock, source):
        self.fire(reply(sock, RPL_WELCOME(self.network)))
        self.fire(reply(sock, RPL_YOURHOST(self.host, self.version)))
        self.fire(reply(sock, ERR_NOMOTD()))

        # Force users to join #circuits
        self.fire(response.create("join", sock, source, "#circuits"))

    def reply(self, sock, message):
        user = self.data.users[sock]

        if message.add_nick:
            message.args.insert(0, user.nick or "")

        if message.prefix is None:
            message.prefix = self.host

        self.fire(write(sock, bytes(message)))

    @handler()
    def _on_event(self, event, *args, **kwargs):
        if event.name.endswith("_done"):
            return

        if isinstance(event, response):
            sock = args[0]
            if event.name not in self.command:
                event.stop()
                return self.fire(reply(sock, ERR_UNKNOWNCOMMAND(event.name)))

            self.fire(event, "commands")
<commit_msg>Fixed user delete on disconnect<commit_after># Module:   server
# Date:     16th August 2014
# Author:   James Mills, prologic at shortcircuit dot net dot au

"""Server Module

Main Listening Server Component
"""

from sys import stderr
from logging import getLogger
from collections import defaultdict

from bidict import bidict
from cidict import cidict

from circuits import handler, Component

from circuits.net.events import write
from circuits.net.sockets import TCPServer

from circuits.protocols.irc import reply, response, IRC

from circuits.protocols.irc.replies import (
    ERR_NOMOTD, ERR_UNKNOWNCOMMAND,
    RPL_WELCOME, RPL_YOURHOST,
)

from .models import User
from .version import version
from .plugin import BasePlugin

class Server(Component):

    channel = "server"

    network = "Test"
    host = "localhost"
    version = "ircd v{0:s}".format(version)

    def init(self, config, data):
        self.config = config
        self.data = data

        self.logger = getLogger(__name__)

        # command -> plugin
        self.command = cidict()

        # plugin name -> commands
        self.commands = cidict()

        # plugin name -> plugin
        self.plugins = cidict()

        self.data.init({
            "users": bidict(),
            "channels": cidict(),
        })

        self.buffers = defaultdict(bytes)

        if ":" in config["bind"]:
            address, port = config["bind"].split(":")
            port = int(port)
        else:
            address, port = config["bind"], 6667

        bind = (address, port)

        self.transport = TCPServer(
            bind,
            channel=self.channel
        ).register(self)

        self.protocol = IRC(
            channel=self.channel,
            getBuffer=self.buffers.__getitem__,
            updateBuffer=self.buffers.__setitem__
        ).register(self)

    @handler("registered", channel="*")
    def _on_registered(self, component, manager):
        if component.channel == "commands":
            for event in component.events():
                if event not in self.command:
                    self.command[event] = component

            if component.parent.name in self.commands:
                events = self.commands[component.parent.name]
                events = events.union(component.events())
                self.commands[component.parent.name] = events
            else:
                self.commands[component.parent.name] = set(component.events())

        if isinstance(component, BasePlugin):
            if component.name not in self.plugins:
                self.plugins[component.name] = component

    @handler("unregistered", channel="*")
    def _on_unregistered(self, component, manager):
        if component.channel == "commands":
            for event in component.events():
                if event in self.command:
                    del self.command[event]

        if isinstance(component, BasePlugin):
            if component.name in self.commands:
                del self.commands[component.name]
            if component.name in self.plugins:
                del self.plugins[component.name]

    def ready(self, server, bind):
        self.logger.info(
            "ircd v{0:s} ready! Listening on: {1:s}\n".format(
                version, "{0:s}:{1:d}".format(*bind)
            )
        )

    def connect(self, sock, host, port):
        user = User(sock, host, port)

        self.data.users[sock] = user

        self.logger.info("C: [{0:s}:{1:d}]".format(host, port))

    def disconnect(self, sock):
        if sock not in self.data.users:
            return

        user = self.data.users[sock]

        self.logger.info("D: [{0:s}:{1:d}]".format(user.host, user.port))

        nick = user.nick
        user, host = user.userinfo.user, user.userinfo.host

        yield self.call(
            response.create("quit", sock, (nick, user, host), "Leavling")
        )

        del self.data.users[sock]

    def read(self, sock, data):
        user = self.data.users[sock]

        host, port = user.host, user.port

        self.logger.info(
            "I: [{0:s}:{1:d}] {2:s}".format(host, port, repr(data))
        )

    def write(self, sock, data):
        user = self.data.users.get(sock, User(sock))

        host, port = user.host, user.port

        self.logger.info(
            "O: [{0:s}:{1:d}] {2:s}".format(host, port, repr(data))
        )

    def broadcast(self, users, message, *exclude):
        for user in users:
            if user in exclude:
                continue

            self.fire(reply(user.sock, message))

    def signon(self, sock, source):
        self.fire(reply(sock, RPL_WELCOME(self.network)))
        self.fire(reply(sock, RPL_YOURHOST(self.host, self.version)))
        self.fire(reply(sock, ERR_NOMOTD()))

        # Force users to join #circuits
        self.fire(response.create("join", sock, source, "#circuits"))

    def reply(self, sock, message):
        user = self.data.users[sock]

        if message.add_nick:
            message.args.insert(0, user.nick or "")

        if message.prefix is None:
            message.prefix = self.host

        self.fire(write(sock, bytes(message)))

    @handler()
    def _on_event(self, event, *args, **kwargs):
        if event.name.endswith("_done"):
            return

        if isinstance(event, response):
            sock = args[0]
            if event.name not in self.command:
                event.stop()
                return self.fire(reply(sock, ERR_UNKNOWNCOMMAND(event.name)))

            self.fire(event, "commands")
<|endoftext|>


<commit_before>#!/usr/bin/python

import yaml, string, os, codecs, urllib, json, fnmatch, re

try:
    import droplogger
except ImportError:
    # Just until I'm able to make droplogger installable
    import sys
    sys.path.append('/home/drj/MyFiles/Programming/Python/droplogger')
    import droplogger

config = droplogger.read_config()
config['white'] = ["pile"]
config['start'] = droplogger.parse_date('min')
config['end'] = droplogger.parse_date('now')

config['files'] = droplogger.get_files(**config)
entries = droplogger.read_files(**config)

oembed_file = '_data/oembed.yml'

fp = open(oembed_file,'r')
oembed = yaml.load(fp)
fp.close()

for e in entries['pile']:
    e['layout'] = "post"
    e['timestamp'] = int(e["date"].strftime('%s'))
    if "tags" in e: e['tags'] = map(unicode, e['tags'])
    if "url" in e:
        if e["url"] is None: del e["url"]
        else:
            if e['type'] in ["link", "quote"]: e['link_url'] = e.pop("url")
            else: e["embed_url"] = e.pop("url")

    embed_html = None
    if "embed_url" in e and e["type"] not in ["image","picture"]:
        embeds = [ o for o in oembed['urls'] if o['url'] == e["embed_url"] ]
        if len(embeds) == 0:
            providers = [ p for p in oembed['providers'] if "schemes" in p['endpoints'][0] and len([ s for s in p['endpoints'][0]['schemes'] if fnmatch.fnmatch(e["embed_url"], s) ]) ]
            if len(providers) > 0:
                ep = providers[0]['endpoints'][0]['url']
                oembed_url = ep + '?' + urllib.urlencode({"format":"json","url":e['embed_url']})
                try:
                    embed = json.load(urllib.urlopen(oembed_url))
                    embed['url'] = e["embed_url"]
                    oembed["urls"].append(embed)
                    fp = open(oembed_file,'w')
                    yaml.safe_dump(oembed, fp, encoding='utf-8', canonical=False, default_flow_style=False)
                    fp.close()
                except Error:
                    embed = None
                embeds = [ embed ]
        if embeds[0] is not None:
            embed_html = embeds[0]['html']
    elif "embed_url" in e and e["type"] in ["image","picture"]:
        embed_html = '' % e["embed_url"]
        if "source" in e: embed_html += '\n\n%s' % e["source"]

    slug = ''.join(c for c in e['title'].replace(' ','_') if c in ("-_.%s%s" % (string.ascii_letters, string.digits)))
    filename = "%s-%s-%s-%s.md" % (e['date'].year, e['date'].month, e['date'].day, slug)
    newfile = os.path.join('_posts', filename)
    fp = codecs.open(newfile, "w", encoding="utf-8")
    fp.write('---\n')
    yaml.safe_dump(e, fp, encoding='utf-8', default_flow_style=False)
    fp.write('\n---\n')
    if embed_html is not None:
        fp.write(embed_html)
        fp.write('\n\n')
    cont = ""
    if "text" in e and e['text'] is not None:
        cont = e['text']
    elif "note" in e and e['note'] is not None:
        cont = e['note']
    elif "notes" in e and e['notes'] is not None:
        cont = e['notes']
    if e["type"] == "quote":
        cont = re.sub('^','> ', cont.strip(), flags=re.M)
        cont += '\n> \n'
        cont += '> %s' % e['source'] if "source" in e else "Unknown"
    fp.write(cont)
    fp.close()

    # Next, let's generate archive pages
    if not os.path.isdir('archives'): os.mkdir('archives')
    day_archive = "archives/date-%s.html" % e['date'].strftime('%Y-%m-%d')
    day = {"layout":"archive"}
    day['permalink'] = e['date'].strftime('%Y/%m/%d/')
    day['archive'] = 'day'
    day['title'] = 'Archive for %s' % (e['date'].strftime('%B %d, %Y'))
    day['year'] = "%s" % e['date'].year
    day['month'] = "%s" % e['date'].month
    day['day'] = "%s" % e['date'].day
    day['year-month-day'] = e['date'].strftime('%Y-%m-%d')
    with open(day_archive, 'w') as fp:
        fp.write('---\n')
        yaml.safe_dump(day, fp, encoding='utf-8', canonical=False, default_flow_style=False)
        fp.write('\n---\n')

    month_archive = "archives/date-%s.html" % e['date'].strftime('%Y-%m')
    month = {"layout":"archive"}
    month['permalink'] = e['date'].strftime('%Y/%m/')
    month['archive'] = 'month'
    month['title'] = 'Archive for %s %s' % (e['date'].strftime('%B'), e['date'].year)
    month['year'] = "%s" % e['date'].year
    month['month'] = "%s" % e['date'].month
    month['year-month'] = e['date'].strftime('%Y-%m')
    with open(month_archive, 'w') as fp:
        fp.write('---\n')
        yaml.safe_dump(month, fp, encoding='utf-8', canonical=False, default_flow_style=False)
        fp.write('\n---\n')

    year_archive = "archives/date-%s.html" % e['date'].year
    year = {"layout":"archive"}
    year['permalink'] = e['date'].strftime('%Y/')
    year['archive'] = 'year'
    year['title'] = 'Archive for %s' % e['date'].year
    year['year'] = "%s" % e['date'].year
    with open(year_archive, 'w') as fp:
        fp.write('---\n')
        yaml.safe_dump(year, fp, encoding='utf-8', canonical=False, default_flow_style=False)
        fp.write('\n---\n')

    if "tags" in e:
        for tag in e['tags']:
            tag_archive = "archives/tag-%s.html" % urllib.quote(tag)
            tago = {"layout":"archive"}
            tago['permalink'] = "/tag/%s/" % tag
            tago['archive'] = 'tag'
            tago['title'] = 'Archive for %s' % tag
            tago['tag'] = tag
            with open(tag_archive, 'w') as fp:
                fp.write('---\n')
                yaml.safe_dump(tago, fp, encoding='utf-8', canonical=False, default_flow_style=False)
                fp.write('\n---\n')

    if "type" in e:
        type_archive = "archives/type-%s.html" % urllib.quote(e["type"])
        typeo = {"layout":"archive"}
        typeo['permalink'] = "/type/%s/" % e["type"]
        typeo['archive'] = 'type'
        typeo['title'] = 'Archive for %s posts' % e["type"]
        typeo['type'] = e["type"]
        with open(type_archive, 'w') as fp:
            fp.write('---\n')
            yaml.safe_dump(typeo, fp, encoding='utf-8', canonical=False, default_flow_style=False)
            fp.write('\n---\n')
<commit_msg>Fix build script<commit_after>#!/usr/bin/python

import yaml, string, os, codecs, urllib, json, fnmatch, re

try:
    import droplogger
except ImportError:
    # Just until I'm able to make droplogger installable
    import sys
    sys.path.append('/home/drj/MyFiles/Programming/Python/droplogger')
    import droplogger

config = droplogger.read_config()
config['white'] = ["pile"]
config['start'] = droplogger.parse_date('min')
config['end'] = droplogger.parse_date('now')

config['files'] = droplogger.get_files(**config)
entries = droplogger.read_files(**config)

oembed_file = '_data/oembed.yml'

fp = open(oembed_file,'r')
oembed = yaml.load(fp)
fp.close()

for e in entries['pile']:
    e['layout'] = "post"
    e['timestamp'] = int(e["date"].strftime('%s'))
    if "tags" in e: e['tags'] = map(unicode, e['tags'])
    if "url" in e:
        if e["url"] is None: del e["url"]
        else:
            if e['type'] in ["link", "quote"]: e['link_url'] = e.pop("url")
            else: e["embed_url"] = e.pop("url")

    embed_html = None
    if "embed_url" in e and e["type"] not in ["image","picture"]:
        embeds = [ o for o in oembed['urls'] if o['url'] == e["embed_url"] ]
        if len(embeds) == 0:
            providers = [ p for p in oembed['providers'] if "schemes" in p['endpoints'][0] and len([ s for s in p['endpoints'][0]['schemes'] if fnmatch.fnmatch(e["embed_url"], s) ]) ]
            if len(providers) > 0:
                ep = providers[0]['endpoints'][0]['url']
                oembed_url = ep + '?' + urllib.urlencode({"format":"json","url":e['embed_url']})
                try:
                    embed = json.load(urllib.urlopen(oembed_url))
                    embed['url'] = e["embed_url"]
                    oembed["urls"].append(embed)
                    fp = open(oembed_file,'w')
                    yaml.safe_dump(oembed, fp, encoding='utf-8', canonical=False, default_flow_style=False)
                    fp.close()
                except ValueError:
                    embed = None
                embeds = [ embed ]
        if embeds[0] is not None:
            embed_html = embeds[0]['html']
    elif "embed_url" in e and e["type"] in ["image","picture"]:
        embed_html = '' % e["embed_url"]
        if "source" in e: embed_html += '\n\n%s' % e["source"]

    slug = ''.join(c for c in e['title'].replace(' ','_') if c in ("-_.%s%s" % (string.ascii_letters, string.digits)))
    filename = "%s-%s-%s-%s.md" % (e['date'].year, e['date'].month, e['date'].day, slug)
    newfile = os.path.join('_posts', filename)
    fp = codecs.open(newfile, "w", encoding="utf-8")
    fp.write('---\n')
    yaml.safe_dump(e, fp, encoding='utf-8', default_flow_style=False)
    fp.write('\n---\n')
    if embed_html is not None:
        fp.write(embed_html)
        fp.write('\n\n')
    cont = ""
    if "text" in e and e['text'] is not None:
        cont = e['text']
    elif "note" in e and e['note'] is not None:
        cont = e['note']
    elif "notes" in e and e['notes'] is not None:
        cont = e['notes']
    if e["type"] == "quote":
        cont = re.sub('^','> ', cont.strip(), flags=re.M)
        cont += '\n> \n'
        cont += '> %s' % e['source'] if "source" in e else "Unknown"
    fp.write(cont)
    fp.close()

    # Next, let's generate archive pages
    if not os.path.isdir('archives'): os.mkdir('archives')
    day_archive = "archives/date-%s.html" % e['date'].strftime('%Y-%m-%d')
    day = {"layout":"archive"}
    day['permalink'] = e['date'].strftime('%Y/%m/%d/')
    day['archive'] = 'day'
    day['title'] = 'Archive for %s' % (e['date'].strftime('%B %d, %Y'))
    day['year'] = "%s" % e['date'].year
    day['month'] = "%s" % e['date'].month
    day['day'] = "%s" % e['date'].day
    day['year-month-day'] = e['date'].strftime('%Y-%m-%d')
    with open(day_archive, 'w') as fp:
        fp.write('---\n')
        yaml.safe_dump(day, fp, encoding='utf-8', canonical=False, default_flow_style=False)
        fp.write('\n---\n')

    month_archive = "archives/date-%s.html" % e['date'].strftime('%Y-%m')
    month = {"layout":"archive"}
    month['permalink'] = e['date'].strftime('%Y/%m/')
    month['archive'] = 'month'
    month['title'] = 'Archive for %s %s' % (e['date'].strftime('%B'), e['date'].year)
    month['year'] = "%s" % e['date'].year
    month['month'] = "%s" % e['date'].month
    month['year-month'] = e['date'].strftime('%Y-%m')
    with open(month_archive, 'w') as fp:
        fp.write('---\n')
        yaml.safe_dump(month, fp, encoding='utf-8', canonical=False, default_flow_style=False)
        fp.write('\n---\n')

    year_archive = "archives/date-%s.html" % e['date'].year
    year = {"layout":"archive"}
    year['permalink'] = e['date'].strftime('%Y/')
    year['archive'] = 'year'
    year['title'] = 'Archive for %s' % e['date'].year
    year['year'] = "%s" % e['date'].year
    with open(year_archive, 'w') as fp:
        fp.write('---\n')
        yaml.safe_dump(year, fp, encoding='utf-8', canonical=False, default_flow_style=False)
        fp.write('\n---\n')

    if "tags" in e:
        for tag in e['tags']:
            tag_archive = "archives/tag-%s.html" % urllib.quote(tag)
            tago = {"layout":"archive"}
            tago['permalink'] = "/tag/%s/" % tag
            tago['archive'] = 'tag'
            tago['title'] = 'Archive for %s' % tag
            tago['tag'] = tag
            with open(tag_archive, 'w') as fp:
                fp.write('---\n')
                yaml.safe_dump(tago, fp, encoding='utf-8', canonical=False, default_flow_style=False)
                fp.write('\n---\n')

    if "type" in e:
        type_archive = "archives/type-%s.html" % urllib.quote(e["type"])
        typeo = {"layout":"archive"}
        typeo['permalink'] = "/type/%s/" % e["type"]
        typeo['archive'] = 'type'
        typeo['title'] = 'Archive for %s posts' % e["type"]
        typeo['type'] = e["type"]
        with open(type_archive, 'w') as fp:
            fp.write('---\n')
            yaml.safe_dump(typeo, fp, encoding='utf-8', canonical=False, default_flow_style=False)
            fp.write('\n---\n')
<|endoftext|>


<commit_before>__version__ = '9.0.0b1'
<commit_msg>bump version<commit_after>__version__ = '9.0.0b2'
<|endoftext|>


<commit_before>import logging
import os
import pathlib
import subprocess
import sys
import time
import tkinter
from functools import partial
from tkinter import ttk
from typing import Any, Dict, List, Optional, Tuple, Union, cast

from porcupine import get_paned_window, get_tab_manager, settings, tabs, utils

log = logging.getLogger(__name__)

# If more than this many projects are opened, then the least recently opened
# project will be closed, unless a file has been opened from that project.
PROJECT_AUTOCLOSE_COUNT = 5

def run_git_status(project_root: pathlib.Path) -> Dict[pathlib.Path, str]:
    try:
        start = time.perf_counter()
        run_result = subprocess.run(
            ['git', 'status', '--ignored', '--porcelain'],
            cwd=project_root,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,   # for logging error message
            encoding=sys.getfilesystemencoding(),
            **utils.subprocess_kwargs)
        log.debug(f"git ran in {round((time.perf_counter() - start)*1000)}ms")

        if run_result.returncode != 0:
            # likely not a git repo because missing ".git" dir
            log.info(f"git failed: {run_result}")
            return {}

    except (OSError, UnicodeError):
        log.warning("can't run git", exc_info=True)
        return {}

    # Show .git as ignored, even though it actually isn't
    result = {project_root / '.git': 'git_ignored'}
    for line in run_result.stdout.splitlines():
        path = project_root / line[3:]
        if line[1] == 'M':
            result[path] = 'git_modified'
        elif line[1] == ' ':
            result[path] = 'git_added'
        elif line[:2] == '??':
            result[path] = 'git_untracked'
        elif line[:2] == '!!':
            result[path] = 'git_ignored'
        else:
            log.warning(f"unknown git status line: {repr(line)}")
    return result

class DirectoryTree(ttk.Treeview):

    def __init__(self, master: tkinter.Misc) -> None:
        super().__init__(master, selectmode='browse', show='tree')
        self.bind('<Double-Button-1>', self.on_click, add=True)
        self.bind('<<TreeviewOpen>>', self.on_click, add=True)
        self.bind('<<ThemeChanged>>', self._config_tags, add=True)
        self.column('#0', minwidth=500)   # allow scrolling sideways
        self._config_tags()
        self.git_statuses: Dict[pathlib.Path, Dict[pathlib.Path, str]] = {}

    def _config_tags(self, junk: object = None) -> None:
        fg = self.tk.eval('ttk::style look Treeview -foreground')
        bg = self.tk.eval('ttk::style look Treeview -background')
        gray = utils.mix_colors(fg, bg, 0.5)

        if sum(self.winfo_rgb(fg)) > 3*0x7fff:
            # bright foreground color
            green = '#00ff00'
            red = '#ff0000'
        else:
            green = '#007f00'
            red = '#7f0000'

        self.tag_configure('dummy', foreground=gray)
        self.tag_configure('git_modified', foreground=red)
        self.tag_configure('git_added', foreground=green)
        self.tag_configure('git_untracked', foreground='red4')
        self.tag_configure('git_ignored', foreground=gray)

    def add_project(self, root_path: pathlib.Path, *, refresh: bool = True) -> None:
        for project_item_id in self.get_children():
            path = self.get_path(project_item_id)
            if path == root_path or path in root_path.parents:
                # Project or parent project added already. Move it first to
                # avoid hiding it soon.
                self.move(project_item_id, '', 0)
                return
            if root_path in path.parents:
                # This project will replace the existing project
                self.delete(project_item_id)

        # TODO: show long paths more nicely
        if pathlib.Path.home() in root_path.parents:
            text = '~' + os.sep + str(root_path.relative_to(pathlib.Path.home()))
        else:
            text = str(root_path)

        # Add project to beginning so it won't be hidden soon
        project_item_id = self.insert('', 0, text=text, values=[root_path], tags=['dir', 'project'], open=False)
        self._insert_dummy(project_item_id)
        self.hide_old_projects()
        if refresh:
            self.refresh_everything()

    def _insert_dummy(self, parent: str) -> None:
        assert parent
        self.insert(parent, 'end', text='(empty)', tags='dummy')

    def _contains_dummy(self, parent: str) -> bool:
        children = self.get_children(parent)
        return (len(children) == 1 and self.tag_has('dummy', children[0]))

    def hide_old_projects(self, junk: object = None) -> None:
        for project_id in reversed(self.get_children('')):
            project_path = self.get_path(project_id)

            if (
                not project_path.is_dir()
            ) or (
                len(self.get_children('')) > PROJECT_AUTOCLOSE_COUNT
                and not any(
                    isinstance(tab, tabs.FileTab)
                    and tab.path is not None
                    and project_path in tab.path.parents
                    for tab in get_tab_manager().tabs()
                )
            ):
                self.delete(project_id)

        # Settings is a weird place for this, but easier than e.g. using a cache file.
        settings.set_('directory_tree_projects', [str(self.get_path(id)) for id in self.get_children()])

    def refresh_everything(self, junk: object = None) -> None:
        log.debug("refreshing begins")
        self.hide_old_projects()

        # This must not be an iterator, otherwise thread calls self.get_path which does tkinter stuff
        paths = {child_id: self.get_path(child_id) for child_id in self.get_children()}

        def thread_target() -> Dict[pathlib.Path, Dict[pathlib.Path, str]]:
            return {path: run_git_status(path) for path in paths.values()}

        def done_callback(success: bool, result: Union[str, Dict[pathlib.Path, Dict[pathlib.Path, str]]]) -> None:
            if success and set(self.get_children()) == paths.keys():
                assert not isinstance(result, str)
                self.git_statuses = result
                self.open_and_refresh_directory(None, '')
                log.debug("refreshing done")
            elif success:
                log.info("projects added/removed while refreshing, assuming another fresh is coming soon")
            else:
                log.error(f"error in git status running thread\n{result}")

        utils.run_in_thread(thread_target, done_callback, check_interval_ms=25)

    def open_and_refresh_directory(self, dir_path: Optional[pathlib.Path], dir_id: str) -> None:
        if self._contains_dummy(dir_id):
            self.delete(self.get_children(dir_id)[0])

        path2id = {self.get_path(id): id for id in self.get_children(dir_id)}
        if dir_path is None:
            # refreshing an entire project
            assert not dir_id
            new_paths = set(path2id.keys())
        else:
            new_paths = set(dir_path.iterdir())
            if not new_paths:
                self._insert_dummy(dir_id)
                return

        # TODO: handle changing directory to file
        for path in list(path2id.keys() - new_paths):
            self.delete(path2id.pop(path))
        for path in list(new_paths - path2id.keys()):
            tag = 'dir' if path.is_dir() else 'file'
            path2id[path] = self.insert(dir_id, 'end', text=path.name, values=[path], tags=tag, open=False)
            if path.is_dir():
                assert dir_path is not None
                self._insert_dummy(path2id[path])

        project_roots = set(map(self.get_path, self.get_children()))

        for child_path, child_id in path2id.items():
            relevant_parents = [child_path]
            parent_iterator = iter(child_path.parents)
            while relevant_parents[-1] not in project_roots:
                relevant_parents.append(next(parent_iterator))
            git_status = self.git_statuses[relevant_parents[-1]]

            old_tags = set(self.item(child_id, 'tags'))
            new_tags = {tag for tag in old_tags if not tag.startswith('git_')}

            for path in relevant_parents:
                if path in git_status:
                    new_tags.add(git_status[path])
                    break
            else:
                # Handle directories containing files with different statuses
                child_tags = {
                    status
                    for subpath, status in git_status.items()
                    if status in {'git_added', 'git_modified'}
                    and str(subpath).startswith(str(child_path))  # optimization
                    and child_path in subpath.parents
                }
                if child_tags == {'git_added', 'git_modified'}:
                    new_tags.add('git_modified')
                else:
                    assert len(child_tags) <= 1
                    new_tags |= child_tags

            if old_tags != new_tags:
                self.item(child_id, tags=list(new_tags))

            if 'dir' in new_tags and not self._contains_dummy(child_id):
                self.open_and_refresh_directory(child_path, child_id)

        if dir_path is not None:
            assert set(self.get_children(dir_id)) == set(path2id.values())
            for index, (path, child_id) in enumerate(sorted(path2id.items(), key=self._sorting_key)):
                self.move(child_id, dir_id, index)

    def _sorting_key(self, path_id_pair: Tuple[pathlib.Path, str]) -> Tuple[Any, ...]:
        path, item_id = path_id_pair
        tags = self.item(item_id, 'tags')

        git_tags = [tag for tag in tags if tag.startswith('git_')]
        assert len(git_tags) < 2
        git_tag = git_tags[0] if git_tags else None

        return (
            ['git_added', 'git_modified', None, 'git_untracked', 'git_ignored'].index(git_tag),
            1 if 'dir' in tags else 2,
            str(path),
        )

    def on_click(self, event: tkinter.Event) -> None:
        try:
            [selected_id] = self.selection()
        except ValueError:
            # nothing selected, can happen when double-clicking something else than one of the items
            return

        if self.tag_has('dir', selected_id):
            self.open_and_refresh_directory(self.get_path(selected_id), selected_id)
        elif self.tag_has('file', selected_id):
            get_tab_manager().add_tab(tabs.FileTab.open_file(get_tab_manager(), self.get_path(selected_id)))

    def get_path(self, item_id: str) -> pathlib.Path:
        assert not self.tag_has('dummy', item_id)
        return pathlib.Path(self.item(item_id, 'values')[0])

def on_new_tab(tree: DirectoryTree, tab: tabs.Tab) -> None:
    if isinstance(tab, tabs.FileTab):
        def path_callback(junk: object = None) -> None:
            assert isinstance(tab, tabs.FileTab)
            if tab.path is not None:
                # Please avoid using find_project_root elsewhere. It doesn't
                # work with nested projects, for example.
                tree.add_project(utils.find_project_root(tab.path))

        path_callback()

        tab.bind('<<PathChanged>>', path_callback, add=True)
        tab.bind('<<PathChanged>>', tree.hide_old_projects, add=True)
        tab.bind('<Destroy>', tree.hide_old_projects, add=True)

        # https://github.com/python/typeshed/issues/5010
        tab.bind('<<Save>>', (lambda event: cast(None, tab.after_idle(tree.refresh_everything))), add=True)
        tab.textwidget.bind('<FocusIn>', tree.refresh_everything, add=True)

def setup() -> None:
    # TODO: add something for finding a file by typing its name?
    container = ttk.Frame(get_paned_window())

    # Packing order matters. The widget packed first is always visible.
    scrollbar = ttk.Scrollbar(container)
    scrollbar.pack(side='right', fill='y')
    tree = DirectoryTree(container)
    tree.pack(side='left', fill='both', expand=True)
    tree.bind('<FocusIn>', tree.refresh_everything, add=True)

    tree.config(yscrollcommand=scrollbar.set)
    scrollbar.config(command=tree.yview)

    get_paned_window().insert(get_tab_manager(), container)   # insert before tab manager
    get_tab_manager().add_tab_callback(partial(on_new_tab, tree))

    settings.add_option('directory_tree_projects', [], List[str])
    string_paths = settings.get('directory_tree_projects', List[str])

    # Must reverse because last added project goes first
    for path in map(pathlib.Path, string_paths[:PROJECT_AUTOCLOSE_COUNT][::-1]):
        if path.is_absolute() and path.is_dir():
            tree.add_project(path, refresh=False)
    tree.refresh_everything()
<commit_msg>show colors on selected item (fixes #342)<commit_after>import logging
import os
import pathlib
import subprocess
import sys
import time
import tkinter
from functools import partial
from tkinter import ttk
from typing import Any, Dict, List, Optional, Tuple, Union, cast

from porcupine import get_paned_window, get_tab_manager, settings, tabs, utils

log = logging.getLogger(__name__)

# If more than this many projects are opened, then the least recently opened
# project will be closed, unless a file has been opened from that project.
PROJECT_AUTOCLOSE_COUNT = 5

def run_git_status(project_root: pathlib.Path) -> Dict[pathlib.Path, str]:
    try:
        start = time.perf_counter()
        run_result = subprocess.run(
            ['git', 'status', '--ignored', '--porcelain'],
            cwd=project_root,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,   # for logging error message
            encoding=sys.getfilesystemencoding(),
            **utils.subprocess_kwargs)
        log.debug(f"git ran in {round((time.perf_counter() - start)*1000)}ms")

        if run_result.returncode != 0:
            # likely not a git repo because missing ".git" dir
            log.info(f"git failed: {run_result}")
            return {}

    except (OSError, UnicodeError):
        log.warning("can't run git", exc_info=True)
        return {}

    # Show .git as ignored, even though it actually isn't
    result = {project_root / '.git': 'git_ignored'}
    for line in run_result.stdout.splitlines():
        path = project_root / line[3:]
        if line[1] == 'M':
            result[path] = 'git_modified'
        elif line[1] == ' ':
            result[path] = 'git_added'
        elif line[:2] == '??':
            result[path] = 'git_untracked'
        elif line[:2] == '!!':
            result[path] = 'git_ignored'
        else:
            log.warning(f"unknown git status line: {repr(line)}")
    return result

class DirectoryTree(ttk.Treeview):

    def __init__(self, master: tkinter.Misc) -> None:
        super().__init__(master, selectmode='browse', show='tree', style='DirectoryTree.Treeview')
        self.bind('<Double-Button-1>', self.on_click, add=True)
        self.bind('<<TreeviewOpen>>', self.on_click, add=True)
        self.bind('<<TreeviewSelect>>', self.update_selection_color, add=True)
        self.bind('<<ThemeChanged>>', self._config_tags, add=True)
        self.column('#0', minwidth=500)   # allow scrolling sideways
        self._config_tags()
        self.git_statuses: Dict[pathlib.Path, Dict[pathlib.Path, str]] = {}

    def _config_tags(self, junk: object = None) -> None:
        fg = self.tk.eval('ttk::style look Treeview -foreground')
        bg = self.tk.eval('ttk::style look Treeview -background')
        gray = utils.mix_colors(fg, bg, 0.5)

        if sum(self.winfo_rgb(fg)) > 3*0x7fff:
            # bright foreground color
            green = '#00ff00'
            red = '#ff0000'
        else:
            green = '#007f00'
            red = '#7f0000'

        self.tag_configure('dummy', foreground=gray)
        self.tag_configure('git_modified', foreground=red)
        self.tag_configure('git_added', foreground=green)
        self.tag_configure('git_untracked', foreground='red4')
        self.tag_configure('git_ignored', foreground=gray)

    def update_selection_color(self, event: object = None) -> None:
        try:
            [selected_id] = self.selection()
        except ValueError:   # nothing selected
            git_tags = []
        else:
            git_tags = [tag for tag in self.item(selected_id, 'tags') if tag.startswith('git_')]

        if git_tags:
            [tag] = git_tags
            color = self.tag_configure(tag, 'foreground')
            self.tk.call('ttk::style', 'map', 'DirectoryTree.Treeview', '-foreground', ['selected', color])
        else:
            # use default colors
            self.tk.eval('ttk::style map DirectoryTree.Treeview -foreground {}')

    def add_project(self, root_path: pathlib.Path, *, refresh: bool = True) -> None:
        for project_item_id in self.get_children():
            path = self.get_path(project_item_id)
            if path == root_path or path in root_path.parents:
                # Project or parent project added already. Move it first to
                # avoid hiding it soon.
                self.move(project_item_id, '', 0)
                return
            if root_path in path.parents:
                # This project will replace the existing project
                self.delete(project_item_id)

        # TODO: show long paths more nicely
        if pathlib.Path.home() in root_path.parents:
            text = '~' + os.sep + str(root_path.relative_to(pathlib.Path.home()))
        else:
            text = str(root_path)

        # Add project to beginning so it won't be hidden soon
        project_item_id = self.insert('', 0, text=text, values=[root_path], tags=['dir', 'project'], open=False)
        self._insert_dummy(project_item_id)
        self.hide_old_projects()
        if refresh:
            self.refresh_everything()

    def _insert_dummy(self, parent: str) -> None:
        assert parent
        self.insert(parent, 'end', text='(empty)', tags='dummy')

    def _contains_dummy(self, parent: str) -> bool:
        children = self.get_children(parent)
        return (len(children) == 1 and self.tag_has('dummy', children[0]))

    def hide_old_projects(self, junk: object = None) -> None:
        for project_id in reversed(self.get_children('')):
            project_path = self.get_path(project_id)

            if (
                not project_path.is_dir()
            ) or (
                len(self.get_children('')) > PROJECT_AUTOCLOSE_COUNT
                and not any(
                    isinstance(tab, tabs.FileTab)
                    and tab.path is not None
                    and project_path in tab.path.parents
                    for tab in get_tab_manager().tabs()
                )
            ):
                self.delete(project_id)

        # Settings is a weird place for this, but easier than e.g. using a cache file.
        settings.set_('directory_tree_projects', [str(self.get_path(id)) for id in self.get_children()])

    def refresh_everything(self, junk: object = None) -> None:
        log.debug("refreshing begins")
        self.hide_old_projects()

        # This must not be an iterator, otherwise thread calls self.get_path which does tkinter stuff
        paths = {child_id: self.get_path(child_id) for child_id in self.get_children()}

        def thread_target() -> Dict[pathlib.Path, Dict[pathlib.Path, str]]:
            return {path: run_git_status(path) for path in paths.values()}

        def done_callback(success: bool, result: Union[str, Dict[pathlib.Path, Dict[pathlib.Path, str]]]) -> None:
            if success and set(self.get_children()) == paths.keys():
                assert not isinstance(result, str)
                self.git_statuses = result
                self.open_and_refresh_directory(None, '')
                self.update_selection_color()
                log.debug("refreshing done")
            elif success:
                log.info("projects added/removed while refreshing, assuming another fresh is coming soon")
            else:
                log.error(f"error in git status running thread\n{result}")

        utils.run_in_thread(thread_target, done_callback, check_interval_ms=25)

    def open_and_refresh_directory(self, dir_path: Optional[pathlib.Path], dir_id: str) -> None:
        if self._contains_dummy(dir_id):
            self.delete(self.get_children(dir_id)[0])

        path2id = {self.get_path(id): id for id in self.get_children(dir_id)}
        if dir_path is None:
            # refreshing an entire project
            assert not dir_id
            new_paths = set(path2id.keys())
        else:
            new_paths = set(dir_path.iterdir())
            if not new_paths:
                self._insert_dummy(dir_id)
                return

        # TODO: handle changing directory to file
        for path in list(path2id.keys() - new_paths):
            self.delete(path2id.pop(path))
        for path in list(new_paths - path2id.keys()):
            tag = 'dir' if path.is_dir() else 'file'
            path2id[path] = self.insert(dir_id, 'end', text=path.name, values=[path], tags=tag, open=False)
            if path.is_dir():
                assert dir_path is not None
                self._insert_dummy(path2id[path])

        project_roots = set(map(self.get_path, self.get_children()))

        for child_path, child_id in path2id.items():
            relevant_parents = [child_path]
            parent_iterator = iter(child_path.parents)
            while relevant_parents[-1] not in project_roots:
                relevant_parents.append(next(parent_iterator))
            git_status = self.git_statuses[relevant_parents[-1]]

            old_tags = set(self.item(child_id, 'tags'))
            new_tags = {tag for tag in old_tags if not tag.startswith('git_')}

            for path in relevant_parents:
                if path in git_status:
                    new_tags.add(git_status[path])
                    break
            else:
                # Handle directories containing files with different statuses
                child_tags = {
                    status
                    for subpath, status in git_status.items()
                    if status in {'git_added', 'git_modified'}
                    and str(subpath).startswith(str(child_path))  # optimization
                    and child_path in subpath.parents
                }
                if child_tags == {'git_added', 'git_modified'}:
                    new_tags.add('git_modified')
                else:
                    assert len(child_tags) <= 1
                    new_tags |= child_tags

            if old_tags != new_tags:
                self.item(child_id, tags=list(new_tags))

            if 'dir' in new_tags and not self._contains_dummy(child_id):
                self.open_and_refresh_directory(child_path, child_id)

        if dir_path is not None:
            assert set(self.get_children(dir_id)) == set(path2id.values())
            for index, (path, child_id) in enumerate(sorted(path2id.items(), key=self._sorting_key)):
                self.move(child_id, dir_id, index)

    def _sorting_key(self, path_id_pair: Tuple[pathlib.Path, str]) -> Tuple[Any, ...]:
        path, item_id = path_id_pair
        tags = self.item(item_id, 'tags')

        git_tags = [tag for tag in tags if tag.startswith('git_')]
        assert len(git_tags) < 2
        git_tag = git_tags[0] if git_tags else None

        return (
            ['git_added', 'git_modified', None, 'git_untracked', 'git_ignored'].index(git_tag),
            1 if 'dir' in tags else 2,
            str(path),
        )

    def on_click(self, event: tkinter.Event) -> None:
        try:
            [selected_id] = self.selection()
        except ValueError:
            # nothing selected, can happen when double-clicking something else than one of the items
            return

        if self.tag_has('dir', selected_id):
            self.open_and_refresh_directory(self.get_path(selected_id), selected_id)
        elif self.tag_has('file', selected_id):
            get_tab_manager().add_tab(tabs.FileTab.open_file(get_tab_manager(), self.get_path(selected_id)))

    def get_path(self, item_id: str) -> pathlib.Path:
        assert not self.tag_has('dummy', item_id)
        return pathlib.Path(self.item(item_id, 'values')[0])

def on_new_tab(tree: DirectoryTree, tab: tabs.Tab) -> None:
    if isinstance(tab, tabs.FileTab):
        def path_callback(junk: object = None) -> None:
            assert isinstance(tab, tabs.FileTab)
            if tab.path is not None:
                # Please avoid using find_project_root elsewhere. It doesn't
                # work with nested projects, for example.
                tree.add_project(utils.find_project_root(tab.path))

        path_callback()

        tab.bind('<<PathChanged>>', path_callback, add=True)
        tab.bind('<<PathChanged>>', tree.hide_old_projects, add=True)
        tab.bind('<Destroy>', tree.hide_old_projects, add=True)

        # https://github.com/python/typeshed/issues/5010
        tab.bind('<<Save>>', (lambda event: cast(None, tab.after_idle(tree.refresh_everything))), add=True)
        tab.textwidget.bind('<FocusIn>', tree.refresh_everything, add=True)

def setup() -> None:
    # TODO: add something for finding a file by typing its name?
    container = ttk.Frame(get_paned_window())

    # Packing order matters. The widget packed first is always visible.
    scrollbar = ttk.Scrollbar(container)
    scrollbar.pack(side='right', fill='y')
    tree = DirectoryTree(container)
    tree.pack(side='left', fill='both', expand=True)
    tree.bind('<FocusIn>', tree.refresh_everything, add=True)

    tree.config(yscrollcommand=scrollbar.set)
    scrollbar.config(command=tree.yview)

    get_paned_window().insert(get_tab_manager(), container)   # insert before tab manager
    get_tab_manager().add_tab_callback(partial(on_new_tab, tree))

    settings.add_option('directory_tree_projects', [], List[str])
    string_paths = settings.get('directory_tree_projects', List[str])

    # Must reverse because last added project goes first
    for path in map(pathlib.Path, string_paths[:PROJECT_AUTOCLOSE_COUNT][::-1]):
        if path.is_absolute() and path.is_dir():
            tree.add_project(path, refresh=False)
    tree.refresh_everything()
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright 2015 by Ecpy Authors, see AUTHORS for more details.
#
# Distributed under the terms of the BSD license.
#
# The full license is in the file LICENCE, distributed with this software.
# -----------------------------------------------------------------------------
# XXXX
"""

"""
from __future__ import (division, unicode_literals, print_function,
                        absolute_import)

import logging
from threading import Thread
from queue import Empty  # This is allowed thanks to the future package
from multiprocessing.queues import Queue

from atom.api import Atom, Coerced, Typed

from ...tasks.tools.task_database import TaskDatabase

class MeasureSpy(Atom):
    # XXXX here need a timer to avoid useless transfer
    """ Spy observing a task database and sending values update into a queue.

    """
    observed_entries = Coerced(set)
    observed_database = Typed(TaskDatabase)
    queue = Typed(Queue)

    def __init__(self, queue, observed_entries, observed_database):
        super(MeasureSpy, self).__init__()
        self.queue = queue
        self.observed_entries = set(observed_entries)
        self.observed_database = observed_database
        self.observed_database.observe('notifier', self.enqueue_update)

    def enqueue_update(self, change):
        new = change['value']
        if new[0] in self.observed_entries:
            self.queue.put_nowait(new)

    def close(self):
        # Simply signal the queue the working thread that the spy won't send
        # any more informations. But don't request the thread to exit this
        # is the responsability of the engine.
        self.queue.put(('', ''))

class ThreadMeasureMonitor(Thread):
    # XXXX
    """ Thread sending a queue content to the news signal of a engine.

    """

    def __init__(self, engine, queue):
        super(ThreadMeasureMonitor, self).__init__()
        self.queue = queue
        self.engine = engine

    def run(self):
        while True:
            try:
                news = self.queue.get()
                if news not in [(None, None), ('', '')]:
                    # Here news is a Signal not Event hence the syntax.
                    self.engine.news(news)
                elif news == ('', ''):
                    logger = logging.getLogger(__name__)
                    logger.debug('Spy closed')
                else:
                    break
            except Empty:
                continue
<commit_msg>Add docs.<commit_after># -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright 2015 by Ecpy Authors, see AUTHORS for more details.
#
# Distributed under the terms of the BSD license.
#
# The full license is in the file LICENCE, distributed with this software.
# -----------------------------------------------------------------------------
"""Useful tools for engines.

"""
from __future__ import (division, unicode_literals, print_function,
                        absolute_import)

import logging
from threading import Thread
from queue import Empty  # This is allowed thanks to the future package
from multiprocessing.queues import Queue

from atom.api import Atom, Coerced, Typed

from ...tasks.tools.task_database import TaskDatabase

class MeasureSpy(Atom):
    """Spy observing a task database and sending values update into a queue.

    All updates are sent immediatly as no issues have been detected so far.
    Using a timer based implementation would complicate things.

    """
    #: Set of entries for which to send notifications.
    observed_entries = Coerced(set)

    #: Reference to the database that needs to be observed.
    observed_database = Typed(TaskDatabase)

    #: Queue in which to send the updates.
    queue = Typed(Queue)

    def __init__(self, queue, observed_entries, observed_database):
        super(MeasureSpy, self).__init__(queue=queue,
                                         observed_database=observed_database,
                                         observed_entries=observed_entries)
        self.observed_database.observe('notifier', self.enqueue_update)

    def enqueue_update(self, change):
        """Put an update in the queue.

        """
        new = change['value']
        if new[0] in self.observed_entries:
            self.queue.put_nowait(new)

    def close(self):
        """Put a dummy object signaling that no more updates will be sent.

        """
        self.queue.put(('', ''))

class ThreadMeasureMonitor(Thread):
    """Thread sending a queue content to the news signal of an engine.

    """

    def __init__(self, engine, queue):
        super(ThreadMeasureMonitor, self).__init__()
        self.queue = queue
        self.engine = engine

    def run(self):
        """Send the news received from the queue to the engine news signal.

        """
        while True:
            try:
                news = self.queue.get()
                if news not in [(None, None), ('', '')]:
                    # Here news is a Signal not Event hence the syntax.
                    self.engine.news(news)
                elif news == ('', ''):
                    logger = logging.getLogger(__name__)
                    logger.debug('Spy closed')
                else:
                    break
            except Empty:
                continue
<|endoftext|>