# All rights reserved.

import supybot.conf as conf
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.registry as registry
import supybot.utils.web as web
import supybot.log as log
import sys
import time
from random import randrange
import simplejson
from urllib import urlencode
from BeautifulSoup import BeautifulStoneSoup
from languages import Languages, validateLanguage

HEADERS = dict(ua = 'Zoia/1.0 (Supybot/0.83; http://example.com)', referer = 'http://example.com')

class TranslationError(Exception):
    def __init__(self, code=None, value=None, url=None, stack=None):
        self.code = code
        self.value = value
        self.u

        self.stack = stack

    def __str__(self):
        if self.code == None:
            return self.value
        else:
            return "%d: %s" % (self.code, self.value)

class TranslationParty(callbacks.Plugin):
    """Add the help for "@plugin help TranslationParty" here
    This should describe *how* to use this plugin."""
    threaded = True

    def _getJsonResponse(self,url,retries = 2):
        try:
            log.debug('Retrieving: %s' % (url))
            doc = web.getUrl(url, headers=HEADERS)
            log.debug('Response: %s' % (doc))
            response = simplejson.loads(doc)
            return response
        except web.Error, e:
            log.warning('Error: %s',str(e))
            if retries > 0:
                log.warning('Retries left: %d' % (retries))
                return self._getJsonResponse(url,retries=retries-1)

    def _translate(self, from_lang, to_lang, text):
        q = unicode(text, 'utf8')
        q = q.encode('utf8')
        params = {
            'langpair': u'|'.join((from_lang,to_lang)),
            'q': q,
            'v': '1.0',
        }
        url = 'http://example.com?' + urlencode(params)
        response = self._getJsonResponse(url)
        if response['responseStatus'] == 404:
            log.warning('Waiting 1/2 second and retrying...')
            time.sleep(0.5)
            response = self._getJsonResponse(url)

        if response['responseStatus'] == 200:
            translation = unicode(BeautifulStoneSoup(response['responseData']['translatedText'],convertEntities=BeautifulStoneSoup.HTML_ENTITIES)).encode('utf8')
            return translation
        else:
            raise TranslationError(response['responseStatus'], response['responseDetails'], url)

    def _party(self, langs, text, max_translations = 50):
        delay = self.registryValue('delay')
        stack = [{'lang': langs[0], 'text': text}]
        def iterate():
            l = list(reversed(langs))
            while len(l) > 0:
                f = l.pop()
                if len(l) == 0:
                    t = langs[0]
                else:
                    t = l[-1]
                stack.append({'lang': t, 'text': self._translate(f,t,stack[-1]['text'])})
                time.sleep(delay)

        def equilibrium():
            if len(stack) < (len(langs) * 2):
                return False

            for i in range(-1,-(len(langs)+1),-1):
                if stack[i]['text'] != stack[i-len(langs)]['text']:
                    return False
            return True

        try:
            iterate()

            while (len(stack) < max_translations) and not equilibrium():
                iterate()
            return(stack)
        except TranslationError, e:
            e.stack = stack
            raise e

    def languages(self, irc, msg, args, query):
        """[<stem>]
        List languages known to TranslationParty that begin with <stem>"""
        if query == None:
            query = ''
        else:
            query = query.upper()

        result = map(lambda x: x.title(), sorted(filter(lambda x: x.startswith(query), Languages)))
        irc.reply(', '.join(result).encode('utf-8'))
    languages = wrap(languages, [optional('somethingWithoutSpaces')])

    def translationparty(self, irc, msg, args, opts, text):
        """[--lang <language>[,...]] [--show <none|one|all>] [--max <int>] [--verbose] <text>
        Try to find equilibrium in back-and-forth translations of <text>. (Defaults: --lang ja --show none --max 50)"""
        input_langs = ['ja']
        show = 'none'
        max_translations = 50
        announce = False

        if len(text) > 1000:
            irc.reply('The text to be translated cannot exceed 1000 characters. Your request contains %d characters' % (len(text)))
        else:
            for (opt,arg) in opts:
                if opt == 'lang':
                    input_langs = arg.split(',')
                if opt == 'max':
                    max_translations = arg
                if opt == 'verbose':
                    announce = True
                if opt == 'show':
                    show = arg

            try:
                langs = ['en']
                for l in input_langs:
                    iso_code = validateLanguage(l)
                    if iso_code == None:
                        raise TranslationError(value="Unknown language: %s" % (l))
                    langs.append(iso_code)

                result = self._party(langs, text, max_translations)

                if announce:
                    if len(result) < max_translations:
                        irc.reply("Equilibrium found!")
                    else:
                        irc.reply("It is doubtful that this phrase will ever reach equilibrium.")

                texts = map(lambda x: x['text'],result)
                if show == 'all':
                    irc.reply(" -> ".join(texts).encode('utf8'))
                elif show == 'one':
                    irc.reply(" -> ".join((texts[0],texts[-1])).encode('utf8'))
                else:
                    # STILL TOO VERBOSE
                    irc.reply(texts[-1].encode('utf8'))
            except TranslationError, e:
                irc.reply(e)
                log.error(str(e))
                if e.stack is not None:
                    texts = map(lambda x: '[%s] %s' % (x['lang'],x['text']),e.stack)
                    log.debug("Stack: %s" % (" -> ".join(texts).encode('utf8')))
                if e.url is not None:
                    log.debug("Last URL: %s" % (e.url))

    translationparty = wrap(translationparty, [getopts({'lang':'somethingWithoutSpaces','show':("literal", ("none","one","all")),'max':'int','verbose':''}), 'text'])

Class = TranslationParty


        if query == None:
            query = ''
        else:
            query = query.upper()

        result = map(lambda x: x.title(), sorted(filter(lambda x: x.startswith(query), Languages)))
        irc.reply(', '.join(result).encode('utf-8'))

# All rights reserved.

import supybot.conf as conf
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.registry as registry
import supybot.utils.web as web
import supybot.log as log
import sys
import time
from random import randrange
import simplejson
from urllib import urlencode
from BeautifulSoup import BeautifulStoneSoup
from languages import Languages, validateLanguage

HEADERS = dict(ua = 'Zoia/1.0 (Supybot/0.83; http://example.com)', referer = 'http://example.com')

class TranslationError(Exception):
    def __init__(self, code=None, value=None, url=None, stack=None):
        self.code = code
        self.value = value
        self.url = url
        self.stack = stack

    def __str__(self):
        if self.code == None:
            return self.value
        else:
            return "%d: %s" % (self.code, self.value)

class TranslationParty(callbacks.Plugin):
    """Add the help for "@plugin help TranslationParty" here
    This should describe *how* to use this plugin."""
    threaded = True

    def _getJsonResponse(self,url,retries = 2):
        try:
            log.debug('Retrieving: %s' % (url))
            doc = web.getUrl(url, headers=HEADERS)
            log.debug('Response: %s' % (doc))
            response = simplejson.loads(doc)
            return response
        except web.Error, e:
            log.warning('Error: %s',str(e))
            if retries > 0:
                log.warning('Retries left: %d' % (retries))
                return self._getJsonResponse(url,retries=retries-1)

    def _translate(self, from_lang, to_lang, text):
        q = unicode(text, 'utf8')
        q = q.encode('utf8')
        params = {
            'langpair': u'|'.join((from_lang,to_lang)),
            'q': q,
            'v': '1.0',
        }
        url = 'http://example.com?' + urlencode(params)
        response = self._getJsonResponse(url)
        if response['responseStatus'] == 404:
            log.warning('Waiting 1/2 second and retrying...')
            time.sleep(0.5)
            response = self._getJsonResponse(url)

        if response['responseStatus'] == 200:
            translation = unicode(BeautifulStoneSoup(response['responseData']['translatedText'],convertEntities=BeautifulStoneSoup.HTML_ENTITIES)).encode('utf8')
            return translation
        else:
            raise TranslationError(response['responseStatus'], response['responseDetails'], url)

    def _party(self, langs, text, max_translations = 50):
        delay = self.registryValue('delay')
        stack = [{'lang': langs[0], 'text': text}]
        def iterate():
            l = list(reversed(langs))
            while len(l) > 0:
                f = l.pop()
                if len(l) == 0:
                    t = langs[0]
                else:
                    t = l[-1]
                stack.append({'lang': t, 'text': self._translate(f,t,stack[-1]['text'])})
                time.sleep(delay)

        def equilibrium():
            if len(stack) < (len(langs) * 2):
                return False

            for i in range(-1,-(len(langs)+1),-1):
                if stack[i]['text'] != stack[i-len(langs)]['text']:
                    return False
            return True

        try:
            iterate()

            while (len(stack) < max_translations) and not equilibrium():
                iterate()
            return(stack)
        except TranslationError, e:
            e.stack = stack
            raise e

    def languages(self, irc, msg, args, query):
        """[<stem>]
        List languages known to TranslationParty that begin with <stem>"""

    languages = wrap(languages, [optional('somethingWithoutSpaces')])

    def translationparty(self, irc, msg, args, opts, text):
        """[--lang <language>[,...]] [--show <none|one|all>] [--max <int>] [--verbose] <text>
        Try to find equilibrium in back-and-forth translations of <text>. (Defaults: --lang ja --show none --max 50)"""
        input_langs = ['ja']
        show = 'none'
        max_translations = 50
        announce = False

        if len(text) > 1000:
            irc.reply('The text to be translated cannot exceed 1000 characters. Your request contains %d characters' % (len(text)))
        else:
            for (opt,arg) in opts:
                if opt == 'lang':
                    input_langs = arg.split(',')
                if opt == 'max':
                    max_translations = arg
                if opt == 'verbose':
                    announce = True
                if opt == 'show':
                    show = arg

            try:
                langs = ['en']
                for l in input_langs:
                    iso_code = validateLanguage(l)
                    if iso_code == None:
                        raise TranslationError(value="Unknown language: %s" % (l))
                    langs.append(iso_code)

                result = self._party(langs, text, max_translations)

                if announce:
                    if len(result) < max_translations:
                        irc.reply("Equilibrium found!")
                    else:
                        irc.reply("It is doubtful that this phrase will ever reach equilibrium.")

                texts = map(lambda x: x['text'],result)
                if show == 'all':
                    irc.reply(" -> ".join(texts).encode('utf8'))
                elif show == 'one':
                    irc.reply(" -> ".join((texts[0],texts[-1])).encode('utf8'))
                else:
                    # STILL TOO VERBOSE
                    irc.reply(texts[-1].encode('utf8'))
            except TranslationError, e:
                irc.reply(e)
                log.error(str(e))
                if e.stack is not None:
                    texts = map(lambda x: '[%s] %s' % (x['lang'],x['text']),e.stack)
                    log.debug("Stack: %s" % (" -> ".join(texts).encode('utf8')))
                if e.url is not None:
                    log.debug("Last URL: %s" % (e.url))

    translationparty = wrap(translationparty, [getopts({'lang':'somethingWithoutSpaces','show':("literal", ("none","one","all")),'max':'int','verbose':''}), 'text'])

Class = TranslationParty


if show == 'all':
                    irc.reply(" -> ".join(texts).encode('utf8'))
                elif show == 'one':
                    irc.reply(" -> ".join((texts[0],texts[-1])).encode('utf8'))
                else:
                    # STILL TOO VERBOSE
                    irc.reply(texts[-1].encode('utf8'))

# All rights reserved.

import supybot.conf as conf
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.registry as registry
import supybot.utils.web as web
import supybot.log as log
import sys
import time
from random import randrange
import simplejson
from urllib import urlencode
from BeautifulSoup import BeautifulStoneSoup
from languages import Languages, validateLanguage

HEADERS = dict(ua = 'Zoia/1.0 (Supybot/0.83; http://example.com)', referer = 'http://example.com')

class TranslationError(Exception):
    def __init__(self, code=None, value=None, url=None, stack=None):
        self.code = code
        self.value = value
        self.url = url
        self.stack = stack

    def __str__(self):
        if self.code == None:
            return self.value
        else:
            return "%d: %s" % (self.code, self.value)

class TranslationParty(callbacks.Plugin):
    """Add the help for "@plugin help TranslationParty" here
    This should describe *how* to use this plugin."""
    threaded = True

    def _getJsonResponse(self,url,retries = 2):
        try:
            log.debug('Retrieving: %s' % (url))
            doc = web.getUrl(url, headers=HEADERS)
            log.debug('Response: %s' % (doc))
            response = simplejson.loads(doc)
            return response
        except web.Error, e:
            log.warning('Error: %s',str(e))
            if retries > 0:
                log.warning('Retries left: %d' % (retries))
                return self._getJsonResponse(url,retries=retries-1)

    def _translate(self, from_lang, to_lang, text):
        q = unicode(text, 'utf8')
        q = q.encode('utf8')
        params = {
            'langpair': u'|'.join((from_lang,to_lang)),
            'q': q,
            'v': '1.0',
        }
        url = 'http://example.com?' + urlencode(params)
        response = self._getJsonResponse(url)
        if response['responseStatus'] == 404:
            log.warning('Waiting 1/2 second and retrying...')
            time.sleep(0.5)
            response = self._getJsonResponse(url)

        if response['responseStatus'] == 200:
            translation = unicode(BeautifulStoneSoup(response['responseData']['translatedText'],convertEntities=BeautifulStoneSoup.HTML_ENTITIES)).encode('utf8')
            return translation
        else:
            raise TranslationError(response['responseStatus'], response['responseDetails'], url)

    def _party(self, langs, text, max_translations = 50):
        delay = self.registryValue('delay')
        stack = [{'lang': langs[0], 'text': text}]
        def iterate():
            l = list(reversed(langs))
            while len(l) > 0:
                f = l.pop()
                if len(l) == 0:
                    t = langs[0]
                else:
                    t = l[-1]
                stack.append({'lang': t, 'text': self._translate(f,t,stack[-1]['text'])})
                time.sleep(delay)

        def equilibrium():
            if len(stack) < (len(langs) * 2):
                return False

            for i in range(-1,-(len(langs)+1),-1):
                if stack[i]['text'] != stack[i-len(langs)]['text']:
                    return False
            return True

        try:
            iterate()

            while (len(stack) < max_translations) and not equilibrium():
                iterate()
            return(stack)
        except TranslationError, e:
            e.stack = stack
            raise e

    def languages(self, irc, msg, args, query):
        """[<stem>]
        List languages known to TranslationParty that begin with <stem>"""
        if query == None:
            query = ''
        else:
            query = query.upper()

        result = map(lambda x: x.title(), sorted(filter(lambda x: x.startswith(query), Languages)))
        irc.reply(', '.join(result).encode('utf-8'))
    languages = wrap(languages, [optional('somethingWithoutSpaces')])

    def translationparty(self, irc, msg, args, opts, text):
        """[--lang <language>[,...]] [--show <none|one|all>] [--max <int>] [--verbose] <text>
        Try to find equilibrium in back-and-forth translations of <text>. (Defaults: --lang ja --show none --max 50)"""
        input_langs = ['ja']
        show = 'none'
        max_translations = 50
        announce = False

        if len(text) > 1000:
            irc.reply('The text to be translated cannot exceed 1000 characters. Your request contains %d characters' % (len(text)))
        else:
            for (opt,arg) in opts:
                if opt == 'lang':
                    input_langs = arg.split(',')
                if opt == 'max':
                    max_translations = arg
                if opt == 'verbose':
                    announce = True
                if opt == 'show':
                    show = arg

            try:
                langs = ['en']
                for l in input_langs:
                    iso_code = validateLanguage(l)
                    if iso_code == None:
                        raise TranslationError(value="Unknown language: %s" % (l))
                    langs.append(iso_code)

                result = self._party(langs, text, max_translations)

                if announce:
                    if len(result) < max_translations:
                        irc.reply("Equilibrium found!")
                    else:
                        irc.reply("It is doubtful that this phrase will ever reach equilibrium.")

                texts = map(lambda x: x['text'],result)

            except TranslationError, e:
                irc.reply(e)
                log.error(str(e))
                if e.stack is not None:
                    texts = map(lambda x: '[%s] %s' % (x['lang'],x['text']),e.stack)
                    log.debug("Stack: %s" % (" -> ".join(texts).encode('utf8')))
                if e.url is not None:
                    log.debug("Last URL: %s" % (e.url))

    translationparty = wrap(translationparty, [getopts({'lang':'somethingWithoutSpaces','show':("literal", ("none","one","all")),'max':'int','verbose':''}), 'text'])

Class = TranslationParty


for i in range(-1,-(len(langs)+1),-1):
                if stack[i]['text'] != stack[i-len(langs)]['text']:
                    return False

# All rights reserved.

import supybot.conf as conf
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.registry as registry
import supybot.utils.web as web
import supybot.log as log
import sys
import time
from random import randrange
import simplejson
from urllib import urlencode
from BeautifulSoup import BeautifulStoneSoup
from languages import Languages, validateLanguage

HEADERS = dict(ua = 'Zoia/1.0 (Supybot/0.83; http://example.com)', referer = 'http://example.com')

class TranslationError(Exception):
    def __init__(self, code=None, value=None, url=None, stack=None):
        self.code = code
        self.value = value
        self.url = url
        self.stack = stack

    def __str__(self):
        if self.code == None:
            return self.value
        else:
            return "%d: %s" % (self.code, self.value)

class TranslationParty(callbacks.Plugin):
    """Add the help for "@plugin help TranslationParty" here
    This should describe *how* to use this plugin."""
    threaded = True

    def _getJsonResponse(self,url,retries = 2):
        try:
            log.debug('Retrieving: %s' % (url))
            doc = web.getUrl(url, headers=HEADERS)
            log.debug('Response: %s' % (doc))
            response = simplejson.loads(doc)
            return response
        except web.Error, e:
            log.warning('Error: %s',str(e))
            if retries > 0:
                log.warning('Retries left: %d' % (retries))
                return self._getJsonResponse(url,retries=retries-1)

    def _translate(self, from_lang, to_lang, text):
        q = unicode(text, 'utf8')
        q = q.encode('utf8')
        params = {
            'langpair': u'|'.join((from_lang,to_lang)),
            'q': q,
            'v': '1.0',
        }
        url = 'http://example.com?' + urlencode(params)
        response = self._getJsonResponse(url)
        if response['responseStatus'] == 404:
            log.warning('Waiting 1/2 second and retrying...')
            time.sleep(0.5)
            response = self._getJsonResponse(url)

        if response['responseStatus'] == 200:
            translation = unicode(BeautifulStoneSoup(response['responseData']['translatedText'],convertEntities=BeautifulStoneSoup.HTML_ENTITIES)).encode('utf8')
            return translation
        else:
            raise TranslationError(response['responseStatus'], response['responseDetails'], url)

    def _party(self, langs, text, max_translations = 50):
        delay = self.registryValue('delay')
        stack = [{'lang': langs[0], 'text': text}]
        def iterate():
            l = list(reversed(langs))
            while len(l) > 0:
                f = l.pop()
                if len(l) == 0:
                    t = langs[0]
                else:
                    t = l[-1]
                stack.append({'lang': t, 'text': self._translate(f,t,stack[-1]['text'])})
                time.sleep(delay)

        def equilibrium():
            if len(stack) < (len(langs) * 2):
                return False

            return True

        try:
            iterate()

            while (len(stack) < max_translations) and not equilibrium():
                iterate()
            return(stack)
        except TranslationError, e:
            e.stack = stack
            raise e

    def languages(self, irc, msg, args, query):
        """[<stem>]
        List languages known to TranslationParty that begin with <stem>"""
        if query == None:
            query = ''
        else:
            query = query.upper()

        result = map(lambda x: x.title(), sorted(filter(lambda x: x.startswith(query), Languages)))
        irc.reply(', '.join(result).encode('utf-8'))
    languages = wrap(languages, [optional('somethingWithoutSpaces')])

    def translationparty(self, irc, msg, args, opts, text):
        """[--lang <language>[,...]] [--show <none|one|all>] [--max <int>] [--verbose] <text>
        Try to find equilibrium in back-and-forth translations of <text>. (Defaults: --lang ja --show none --max 50)"""
        input_langs = ['ja']
        show = 'none'
        max_translations = 50
        announce = False

        if len(text) > 1000:
            irc.reply('The text to be translated cannot exceed 1000 characters. Your request contains %d characters' % (len(text)))
        else:
            for (opt,arg) in opts:
                if opt == 'lang':
                    input_langs = arg.split(',')
                if opt == 'max':
                    max_translations = arg
                if opt == 'verbose':
                    announce = True
                if opt == 'show':
                    show = arg

            try:
                langs = ['en']
                for l in input_langs:
                    iso_code = validateLanguage(l)
                    if iso_code == None:
                        raise TranslationError(value="Unknown language: %s" % (l))
                    langs.append(iso_code)

                result = self._party(langs, text, max_translations)

                if announce:
                    if len(result) < max_translations:
                        irc.reply("Equilibrium found!")
                    else:
                        irc.reply("It is doubtful that this phrase will ever reach equilibrium.")

                texts = map(lambda x: x['text'],result)
                if show == 'all':
                    irc.reply(" -> ".join(texts).encode('utf8'))
                elif show == 'one':
                    irc.reply(" -> ".join((texts[0],texts[-1])).encode('utf8'))
                else:
                    # STILL TOO VERBOSE
                    irc.reply(texts[-1].encode('utf8'))
            except TranslationError, e:
                irc.reply(e)
                log.error(str(e))
                if e.stack is not None:
                    texts = map(lambda x: '[%s] %s' % (x['lang'],x['text']),e.stack)
                    log.debug("Stack: %s" % (" -> ".join(texts).encode('utf8')))
                if e.url is not None:
                    log.debug("Last URL: %s" % (e.url))

    translationparty = wrap(translationparty, [getopts({'lang':'somethingWithoutSpaces','show':("literal", ("none","one","all")),'max':'int','verbose':''}), 'text'])

Class = TranslationParty


while len(l) > 0:
                f = l.pop()
                if len(l) == 0:
                    t = langs[0]
                else:
                    t = l[-1]
                stack.append({'lang': t, 'text': self._translate(f,t,stack[-1]['text'])})
                time.sleep(delay)

# All rights reserved.

import supybot.conf as conf
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.registry as registry
import supybot.utils.web as web
import supybot.log as log
import sys
import time
from random import randrange
import simplejson
from urllib import urlencode
from BeautifulSoup import BeautifulStoneSoup
from languages import Languages, validateLanguage

HEADERS = dict(ua = 'Zoia/1.0 (Supybot/0.83; http://example.com)', referer = 'http://example.com')

class TranslationError(Exception):
    def __init__(self, code=None, value=None, url=None, stack=None):
        self.code = code
        self.value = value
        self.url = url
        self.stack = stack

    def __str__(self):
        if self.code == None:
            return self.value
        else:
            return "%d: %s" % (self.code, self.value)

class TranslationParty(callbacks.Plugin):
    """Add the help for "@plugin help TranslationParty" here
    This should describe *how* to use this plugin."""
    threaded = True

    def _getJsonResponse(self,url,retries = 2):
        try:
            log.debug('Retrieving: %s' % (url))
            doc = web.getUrl(url, headers=HEADERS)
            log.debug('Response: %s' % (doc))
            response = simplejson.loads(doc)
            return response
        except web.Error, e:
            log.warning('Error: %s',str(e))
            if retries > 0:
                log.warning('Retries left: %d' % (retries))
                return self._getJsonResponse(url,retries=retries-1)

    def _translate(self, from_lang, to_lang, text):
        q = unicode(text, 'utf8')
        q = q.encode('utf8')
        params = {
            'langpair': u'|'.join((from_lang,to_lang)),
            'q': q,
            'v': '1.0',
        }
        url = 'http://example.com?' + urlencode(params)
        response = self._getJsonResponse(url)
        if response['responseStatus'] == 404:
            log.warning('Waiting 1/2 second and retrying...')
            time.sleep(0.5)
            response = self._getJsonResponse(url)

        if response['responseStatus'] == 200:
            translation = unicode(BeautifulStoneSoup(response['responseData']['translatedText'],convertEntities=BeautifulStoneSoup.HTML_ENTITIES)).encode('utf8')
            return translation
        else:
            raise TranslationError(response['responseStatus'], response['responseDetails'], url)

    def _party(self, langs, text, max_translations = 50):
        delay = self.registryValue('delay')
        stack = [{'lang': langs[0], 'text': text}]
        def iterate():
            l = list(reversed(langs))

        def equilibrium():
            if len(stack) < (len(langs) * 2):
                return False

            for i in range(-1,-(len(langs)+1),-1):
                if stack[i]['text'] != stack[i-len(langs)]['text']:
                    return False
            return True

        try:
            iterate()

            while (len(stack) < max_translations) and not equilibrium():
                iterate()
            return(stack)
        except TranslationError, e:
            e.stack = stack
            raise e

    def languages(self, irc, msg, args, query):
        """[<stem>]
        List languages known to TranslationParty that begin with <stem>"""
        if query == None:
            query = ''
        else:
            query = query.upper()

        result = map(lambda x: x.title(), sorted(filter(lambda x: x.startswith(query), Languages)))
        irc.reply(', '.join(result).encode('utf-8'))
    languages = wrap(languages, [optional('somethingWithoutSpaces')])

    def translationparty(self, irc, msg, args, opts, text):
        """[--lang <language>[,...]] [--show <none|one|all>] [--max <int>] [--verbose] <text>
        Try to find equilibrium in back-and-forth translations of <text>. (Defaults: --lang ja --show none --max 50)"""
        input_langs = ['ja']
        show = 'none'
        max_translations = 50
        announce = False

        if len(text) > 1000:
            irc.reply('The text to be translated cannot exceed 1000 characters. Your request contains %d characters' % (len(text)))
        else:
            for (opt,arg) in opts:
                if opt == 'lang':
                    input_langs = arg.split(',')
                if opt == 'max':
                    max_translations = arg
                if opt == 'verbose':
                    announce = True
                if opt == 'show':
                    show = arg

            try:
                langs = ['en']
                for l in input_langs:
                    iso_code = validateLanguage(l)
                    if iso_code == None:
                        raise TranslationError(value="Unknown language: %s" % (l))
                    langs.append(iso_code)

                result = self._party(langs, text, max_translations)

                if announce:
                    if len(result) < max_translations:
                        irc.reply("Equilibrium found!")
                    else:
                        irc.reply("It is doubtful that this phrase will ever reach equilibrium.")

                texts = map(lambda x: x['text'],result)
                if show == 'all':
                    irc.reply(" -> ".join(texts).encode('utf8'))
                elif show == 'one':
                    irc.reply(" -> ".join((texts[0],texts[-1])).encode('utf8'))
                else:
                    # STILL TOO VERBOSE
                    irc.reply(texts[-1].encode('utf8'))
            except TranslationError, e:
                irc.reply(e)
                log.error(str(e))
                if e.stack is not None:
                    texts = map(lambda x: '[%s] %s' % (x['lang'],x['text']),e.stack)
                    log.debug("Stack: %s" % (" -> ".join(texts).encode('utf8')))
                if e.url is not None:
                    log.debug("Last URL: %s" % (e.url))

    translationparty = wrap(translationparty, [getopts({'lang':'somethingWithoutSpaces','show':("literal", ("none","one","all")),'max':'int','verbose':''}), 'text'])

Class = TranslationParty


try:
            log.debug('Retrieving: %s' % (url))
            doc = web.getUrl(url, headers=HEADERS)
            log.debug('Response: %s' % (doc))
            response = simplejson.loads(doc)
            return response
        except web.Error, e:
            log.warning('Error: %s',str(e))
            if retries > 0:
                log.warning('Retries left: %d' % (retries))
                return self._getJsonResponse(url,retries=retries-1)

# All rights reserved.

import supybot.conf as conf
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.registry as registry
import supybot.utils.web as web
import supybot.log as log
import sys
import time
from random import randrange
import simplejson
from urllib import urlencode
from BeautifulSoup import BeautifulStoneSoup
from languages import Languages, validateLanguage

HEADERS = dict(ua = 'Zoia/1.0 (Supybot/0.83; http://example.com)', referer = 'http://example.com')

class TranslationError(Exception):
    def __init__(self, code=None, value=None, url=None, stack=None):
        self.code = code
        self.value = value
        self.url = url
        self.stack = stack

    def __str__(self):
        if self.code == None:
            return self.value
        else:
            return "%d: %s" % (self.code, self.value)

class TranslationParty(callbacks.Plugin):
    """Add the help for "@plugin help TranslationParty" here
    This should describe *how* to use this plugin."""
    threaded = True

    def _getJsonResponse(self,url,retries = 2):

    def _translate(self, from_lang, to_lang, text):
        q = unicode(text, 'utf8')
        q = q.encode('utf8')
        params = {
            'langpair': u'|'.join((from_lang,to_lang)),
            'q': q,
            'v': '1.0',
        }
        url = 'http://example.com?' + urlencode(params)
        response = self._getJsonResponse(url)
        if response['responseStatus'] == 404:
            log.warning('Waiting 1/2 second and retrying...')
            time.sleep(0.5)
            response = self._getJsonResponse(url)

        if response['responseStatus'] == 200:
            translation = unicode(BeautifulStoneSoup(response['responseData']['translatedText'],convertEntities=BeautifulStoneSoup.HTML_ENTITIES)).encode('utf8')
            return translation
        else:
            raise TranslationError(response['responseStatus'], response['responseDetails'], url)

    def _party(self, langs, text, max_translations = 50):
        delay = self.registryValue('delay')
        stack = [{'lang': langs[0], 'text': text}]
        def iterate():
            l = list(reversed(langs))
            while len(l) > 0:
                f = l.pop()
                if len(l) == 0:
                    t = langs[0]
                else:
                    t = l[-1]
                stack.append({'lang': t, 'text': self._translate(f,t,stack[-1]['text'])})
                time.sleep(delay)

        def equilibrium():
            if len(stack) < (len(langs) * 2):
                return False

            for i in range(-1,-(len(langs)+1),-1):
                if stack[i]['text'] != stack[i-len(langs)]['text']:
                    return False
            return True

        try:
            iterate()

            while (len(stack) < max_translations) and not equilibrium():
                iterate()
            return(stack)
        except TranslationError, e:
            e.stack = stack
            raise e

    def languages(self, irc, msg, args, query):
        """[<stem>]
        List languages known to TranslationParty that begin with <stem>"""
        if query == None:
            query = ''
        else:
            query = query.upper()

        result = map(lambda x: x.title(), sorted(filter(lambda x: x.startswith(query), Languages)))
        irc.reply(', '.join(result).encode('utf-8'))
    languages = wrap(languages, [optional('somethingWithoutSpaces')])

    def translationparty(self, irc, msg, args, opts, text):
        """[--lang <language>[,...]] [--show <none|one|all>] [--max <int>] [--verbose] <text>
        Try to find equilibrium in back-and-forth translations of <text>. (Defaults: --lang ja --show none --max 50)"""
        input_langs = ['ja']
        show = 'none'
        max_translations = 50
        announce = False

        if len(text) > 1000:
            irc.reply('The text to be translated cannot exceed 1000 characters. Your request contains %d characters' % (len(text)))
        else:
            for (opt,arg) in opts:
                if opt == 'lang':
                    input_langs = arg.split(',')
                if opt == 'max':
                    max_translations = arg
                if opt == 'verbose':
                    announce = True
                if opt == 'show':
                    show = arg

            try:
                langs = ['en']
                for l in input_langs:
                    iso_code = validateLanguage(l)
                    if iso_code == None:
                        raise TranslationError(value="Unknown language: %s" % (l))
                    langs.append(iso_code)

                result = self._party(langs, text, max_translations)

                if announce:
                    if len(result) < max_translations:
                        irc.reply("Equilibrium found!")
                    else:
                        irc.reply("It is doubtful that this phrase will ever reach equilibrium.")

                texts = map(lambda x: x['text'],result)
                if show == 'all':
                    irc.reply(" -> ".join(texts).encode('utf8'))
                elif show == 'one':
                    irc.reply(" -> ".join((texts[0],texts[-1])).encode('utf8'))
                else:
                    # STILL TOO VERBOSE
                    irc.reply(texts[-1].encode('utf8'))
            except TranslationError, e:
                irc.reply(e)
                log.error(str(e))
                if e.stack is not None:
                    texts = map(lambda x: '[%s] %s' % (x['lang'],x['text']),e.stack)
                    log.debug("Stack: %s" % (" -> ".join(texts).encode('utf8')))
                if e.url is not None:
                    log.debug("Last URL: %s" % (e.url))

    translationparty = wrap(translationparty, [getopts({'lang':'somethingWithoutSpaces','show':("literal", ("none","one","all")),'max':'int','verbose':''}), 'text'])

Class = TranslationParty


def compile_latex(fname, tmpdirname, latexmk_options, silent=True):

"""
This file is part of labella.py.

Author: G.J.J. van den Burg
"""

import os
import shutil
import subprocess
import tempfile
import unicodedata

def uni2tex(text):
    # Courtesy of http://example.com
    accents = {
        0x0300: "`",
        0x0301: "'",
        0x0302: "^",
        0x0308: '"',
        0x030B: "H",
        0x0303: "~",
        0x0327: "c",
        0x0328: "k",
        0x0304: "=",
        0x0331: "b",
        0x0307: ".",
        0x0323: "d",
        0x030A: "r",
        0x0306: "u",
        0x030C: "v",
    }
    out = ""
    txt = tuple(text)
    i = 0
    while i < len(txt):
        char = text[i]
        code = ord(char)

        # combining marks
        if unicodedata.category(char) in ("Mn", "Mc") and code in accents:
            out += "\\%s{%s}" % (accents[code], txt[i + 1])
            i += 1
        # precomposed characters
        elif unicodedata.decomposition(char):
            base, acc = unicodedata.decomposition(char).split()
            acc = int(acc, 16)
            base = int(base, 16)
            if acc in accents:
                out += "\\%s{%s}" % (accents[acc], chr(base))
            else:
                out += char
        else:
            out += char
        i += 1
    return out

def get_latex_fontdoc(text, fontsize="11pt", preamble=""):
    tex = r"""\documentclass[preview, {fontsize}]{{standalone}}
{preamble}%
\begin{{document}}
{text}%
\newlength{{\lblwidth}}%
\newlength{{\lblheight}}%
\settowidth{{\lblwidth}}{{{text}}}%
\settoheight{{\lblheight}}{{{text}}}%
\typeout{{LABELWIDTH: \the\lblwidth}}%
\typeout{{LABELHEIGHT: \the\lblheight}}%
\end{{document}}
""".format(
        fontsize=fontsize, text=uni2tex(text), preamble=uni2tex(preamble)
    )
    return tex

compiler = "latexmk"
    if latexmk_options:
        compiler_args = latexmk_options + [
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    else:
        compiler_args = [
            "--pdf",
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    command = [compiler] + compiler_args
    try:
        output = subprocess.check_output(command, stderr=subprocess.STDOUT)
    except (OSError, IOError) as e:
        raise (e)
    except subprocess.CalledProcessError as e:
        print(e.output.decode())
        raise (e)
    else:
        if not silent:
            print(output.decode())

def get_latex_dims(tex, latexmk_options, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        logname = os.path.join(tmpdirname, basename + ".log")
        with open(logname, "r") as fid:
            lines = fid.readlines()

        line_width = next(
            (l for l in lines if l.startswith("LABELWIDTH")), None
        )
        line_height = next(
            (l for l in lines if l.startswith("LABELHEIGHT")), None
        )

        width = line_width.strip().split(":")[-1].strip().rstrip("pt")
        height = line_height.strip().split(":")[-1].strip().rstrip("pt")
    return float(width), float(height)

def build_latex_doc(tex, latexmk_options, output_name=None, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        pdfname = os.path.join(tmpdirname, basename + ".pdf")
        if output_name:
            shutil.copy2(pdfname, output_name)

def text_dimensions(
    text, fontsize="11pt", preamble="", silent=True, latexmk_options=None
):
    tex = get_latex_fontdoc(text, fontsize=fontsize, preamble=preamble)
    width, height = get_latex_dims(
        tex, silent=silent, latexmk_options=latexmk_options
    )
    return width, height


tex = r"""\documentclass[preview, {fontsize}]{{standalone}}
{preamble}%
\begin{{document}}
{text}%
\newlength{{\lblwidth}}%
\newlength{{\lblheight}}%
\settowidth{{\lblwidth}}{{{text}}}%
\settoheight{{\lblheight}}{{{text}}}%
\typeout{{LABELWIDTH: \the\lblwidth}}%
\typeout{{LABELHEIGHT: \the\lblheight}}%
\end{{document}}
""".format(
        fontsize=fontsize, text=uni2tex(text), preamble=uni2tex(preamble)
    )
    return tex

"""
This file is part of labella.py.

Author: G.J.J. van den Burg
"""

import os
import shutil
import subprocess
import tempfile
import unicodedata

def uni2tex(text):
    # Courtesy of http://example.com
    accents = {
        0x0300: "`",
        0x0301: "'",
        0x0302: "^",
        0x0308: '"',
        0x030B: "H",
        0x0303: "~",
        0x0327: "c",
        0x0328: "k",
        0x0304: "=",
        0x0331: "b",
        0x0307: ".",
        0x0323: "d",
        0x030A: "r",
        0x0306: "u",
        0x030C: "v",
    }
    out = ""
    txt = tuple(text)
    i = 0
    while i < len(txt):
        char = text[i]
        code = ord(char)

        # combining marks
        if unicodedata.category(char) in ("Mn", "Mc") and code in accents:
            out += "\\%s{%s}" % (accents[code], txt[i + 1])
            i += 1
        # precomposed characters
        elif unicodedata.decomposition(char):
            base, acc = unicodedata.decomposition(char).split()
            acc = int(acc, 16)
            base = int(base, 16)
            if acc in accents:
                out += "\\%s{%s}" % (accents[acc], chr(base))
            else:
                out += char
        else:
            out += char
        i += 1
    return out

def get_latex_fontdoc(text, fontsize="11pt", preamble=""):

def compile_latex(fname, tmpdirname, latexmk_options, silent=True):
    compiler = "latexmk"
    if latexmk_options:
        compiler_args = latexmk_options + [
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    else:
        compiler_args = [
            "--pdf",
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    command = [compiler] + compiler_args
    try:
        output = subprocess.check_output(command, stderr=subprocess.STDOUT)
    except (OSError, IOError) as e:
        raise (e)
    except subprocess.CalledProcessError as e:
        print(e.output.decode())
        raise (e)
    else:
        if not silent:
            print(output.decode())

def get_latex_dims(tex, latexmk_options, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        logname = os.path.join(tmpdirname, basename + ".log")
        with open(logname, "r") as fid:
            lines = fid.readlines()

        line_width = next(
            (l for l in lines if l.startswith("LABELWIDTH")), None
        )
        line_height = next(
            (l for l in lines if l.startswith("LABELHEIGHT")), None
        )

        width = line_width.strip().split(":")[-1].strip().rstrip("pt")
        height = line_height.strip().split(":")[-1].strip().rstrip("pt")
    return float(width), float(height)

def build_latex_doc(tex, latexmk_options, output_name=None, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        pdfname = os.path.join(tmpdirname, basename + ".pdf")
        if output_name:
            shutil.copy2(pdfname, output_name)

def text_dimensions(
    text, fontsize="11pt", preamble="", silent=True, latexmk_options=None
):
    tex = get_latex_fontdoc(text, fontsize=fontsize, preamble=preamble)
    width, height = get_latex_dims(
        tex, silent=silent, latexmk_options=latexmk_options
    )
    return width, height


"""
This file is part of labella.py.

Author: G.J.J. van den Burg
"""

import os
import shutil
import subprocess
import tempfile
import unicodedata

def uni2tex(text):
    # Courtesy of http://example.com

    txt = tuple(text)
    i = 0
    while i < len(txt):
        char = text[i]
        code = ord(char)

        # combining marks
        if unicodedata.category(char) in ("Mn", "Mc") and code in accents:
            out += "\\%s{%s}" % (accents[code], txt[i + 1])
            i += 1
        # precomposed characters
        elif unicodedata.decomposition(char):
            base, acc = unicodedata.decomposition(char).split()
            acc = int(acc, 16)
            base = int(base, 16)
            if acc in accents:
                out += "\\%s{%s}" % (accents[acc], chr(base))
            else:
                out += char
        else:
            out += char
        i += 1
    return out

def get_latex_fontdoc(text, fontsize="11pt", preamble=""):
    tex = r"""\documentclass[preview, {fontsize}]{{standalone}}
{preamble}%
\begin{{document}}
{text}%
\newlength{{\lblwidth}}%
\newlength{{\lblheight}}%
\settowidth{{\lblwidth}}{{{text}}}%
\settoheight{{\lblheight}}{{{text}}}%
\typeout{{LABELWIDTH: \the\lblwidth}}%
\typeout{{LABELHEIGHT: \the\lblheight}}%
\end{{document}}
""".format(
        fontsize=fontsize, text=uni2tex(text), preamble=uni2tex(preamble)
    )
    return tex

def compile_latex(fname, tmpdirname, latexmk_options, silent=True):
    compiler = "latexmk"
    if latexmk_options:
        compiler_args = latexmk_options + [
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    else:
        compiler_args = [
            "--pdf",
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    command = [compiler] + compiler_args
    try:
        output = subprocess.check_output(command, stderr=subprocess.STDOUT)
    except (OSError, IOError) as e:
        raise (e)
    except subprocess.CalledProcessError as e:
        print(e.output.decode())
        raise (e)
    else:
        if not silent:
            print(output.decode())

def get_latex_dims(tex, latexmk_options, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        logname = os.path.join(tmpdirname, basename + ".log")
        with open(logname, "r") as fid:
            lines = fid.readlines()

        line_width = next(
            (l for l in lines if l.startswith("LABELWIDTH")), None
        )
        line_height = next(
            (l for l in lines if l.startswith("LABELHEIGHT")), None
        )

        width = line_width.strip().split(":")[-1].strip().rstrip("pt")
        height = line_height.strip().split(":")[-1].strip().rstrip("pt")
    return float(width), float(height)

def build_latex_doc(tex, latexmk_options, output_name=None, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        pdfname = os.path.join(tmpdirname, basename + ".pdf")
        if output_name:
            shutil.copy2(pdfname, output_name)

def text_dimensions(
    text, fontsize="11pt", preamble="", silent=True, latexmk_options=None
):
    tex = get_latex_fontdoc(text, fontsize=fontsize, preamble=preamble)
    width, height = get_latex_dims(
        tex, silent=silent, latexmk_options=latexmk_options
    )
    return width, height


"""
This file is part of labella.py.

Author: G.J.J. van den Burg
"""

import os
import shutil
import subprocess
import tempfile
import unicodedata

def uni2tex(text):
    # Courtesy of http://example.com
    accents = {
        0x0300: "`",
        0x0301: "'",
        0x0302: "^",
        0x0308: '"',
        0x030B: "H",
        0x0303: "~",
        0x0327: "c",
        0x0328: "k",
        0x0304: "=",
        0x0331: "b",
        0x0307: ".",
        0x0323: "d",
        0x030A: "r",
        0x0306: "u",
        0x030C: "v",
    }
    out = ""

    while i < len(txt):
        char = text[i]
        code = ord(char)

        # combining marks
        if unicodedata.category(char) in ("Mn", "Mc") and code in accents:
            out += "\\%s{%s}" % (accents[code], txt[i + 1])
            i += 1
        # precomposed characters
        elif unicodedata.decomposition(char):
            base, acc = unicodedata.decomposition(char).split()
            acc = int(acc, 16)
            base = int(base, 16)
            if acc in accents:
                out += "\\%s{%s}" % (accents[acc], chr(base))
            else:
                out += char
        else:
            out += char
        i += 1
    return out

def get_latex_fontdoc(text, fontsize="11pt", preamble=""):
    tex = r"""\documentclass[preview, {fontsize}]{{standalone}}
{preamble}%
\begin{{document}}
{text}%
\newlength{{\lblwidth}}%
\newlength{{\lblheight}}%
\settowidth{{\lblwidth}}{{{text}}}%
\settoheight{{\lblheight}}{{{text}}}%
\typeout{{LABELWIDTH: \the\lblwidth}}%
\typeout{{LABELHEIGHT: \the\lblheight}}%
\end{{document}}
""".format(
        fontsize=fontsize, text=uni2tex(text), preamble=uni2tex(preamble)
    )
    return tex

def compile_latex(fname, tmpdirname, latexmk_options, silent=True):
    compiler = "latexmk"
    if latexmk_options:
        compiler_args = latexmk_options + [
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    else:
        compiler_args = [
            "--pdf",
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    command = [compiler] + compiler_args
    try:
        output = subprocess.check_output(command, stderr=subprocess.STDOUT)
    except (OSError, IOError) as e:
        raise (e)
    except subprocess.CalledProcessError as e:
        print(e.output.decode())
        raise (e)
    else:
        if not silent:
            print(output.decode())

def get_latex_dims(tex, latexmk_options, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        logname = os.path.join(tmpdirname, basename + ".log")
        with open(logname, "r") as fid:
            lines = fid.readlines()

        line_width = next(
            (l for l in lines if l.startswith("LABELWIDTH")), None
        )
        line_height = next(
            (l for l in lines if l.startswith("LABELHEIGHT")), None
        )

        width = line_width.strip().split(":")[-1].strip().rstrip("pt")
        height = line_height.strip().split(":")[-1].strip().rstrip("pt")
    return float(width), float(height)

def build_latex_doc(tex, latexmk_options, output_name=None, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        pdfname = os.path.join(tmpdirname, basename + ".pdf")
        if output_name:
            shutil.copy2(pdfname, output_name)

def text_dimensions(
    text, fontsize="11pt", preamble="", silent=True, latexmk_options=None
):
    tex = get_latex_fontdoc(text, fontsize=fontsize, preamble=preamble)
    width, height = get_latex_dims(
        tex, silent=silent, latexmk_options=latexmk_options
    )
    return width, height


while i < len(txt):
        char = text[i]
        code = ord(char)

        # combining marks
        if unicodedata.category(char) in ("Mn", "Mc") and code in accents:
            out += "\\%s{%s}" % (accents[code], txt[i + 1])
            i += 1
        # precomposed characters
        elif unicodedata.decomposition(char):
            base, acc = unicodedata.decomposition(char).split()
            acc = int(acc, 16)
            base = int(base, 16)
            if acc in accents:
                out += "\\%s{%s}" % (accents[acc], chr(base))
            else:
                out += char
        else:
            out += char
        i += 1
    return out

"""
This file is part of labella.py.

Author: G.J.J. van den Burg
"""

import os
import shutil
import subprocess
import tempfile
import unicodedata

def uni2tex(text):
    # Courtesy of http://example.com
    accents = {
        0x0300: "`",
        0x0301: "'",
        0x0302: "^",
        0x0308: '"',
        0x030B: "H",
        0x0303: "~",
        0x0327: "c",
        0x0328: "k",
        0x0304: "=",
        0x0331: "b",
        0x0307: ".",
        0x0323: "d",
        0x030A: "r",
        0x0306: "u",
        0x030C: "v",
    }
    out = ""
    txt = tuple(text)
    i = 0

def get_latex_fontdoc(text, fontsize="11pt", preamble=""):
    tex = r"""\documentclass[preview, {fontsize}]{{standalone}}
{preamble}%
\begin{{document}}
{text}%
\newlength{{\lblwidth}}%
\newlength{{\lblheight}}%
\settowidth{{\lblwidth}}{{{text}}}%
\settoheight{{\lblheight}}{{{text}}}%
\typeout{{LABELWIDTH: \the\lblwidth}}%
\typeout{{LABELHEIGHT: \the\lblheight}}%
\end{{document}}
""".format(
        fontsize=fontsize, text=uni2tex(text), preamble=uni2tex(preamble)
    )
    return tex

def compile_latex(fname, tmpdirname, latexmk_options, silent=True):
    compiler = "latexmk"
    if latexmk_options:
        compiler_args = latexmk_options + [
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    else:
        compiler_args = [
            "--pdf",
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    command = [compiler] + compiler_args
    try:
        output = subprocess.check_output(command, stderr=subprocess.STDOUT)
    except (OSError, IOError) as e:
        raise (e)
    except subprocess.CalledProcessError as e:
        print(e.output.decode())
        raise (e)
    else:
        if not silent:
            print(output.decode())

def get_latex_dims(tex, latexmk_options, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        logname = os.path.join(tmpdirname, basename + ".log")
        with open(logname, "r") as fid:
            lines = fid.readlines()

        line_width = next(
            (l for l in lines if l.startswith("LABELWIDTH")), None
        )
        line_height = next(
            (l for l in lines if l.startswith("LABELHEIGHT")), None
        )

        width = line_width.strip().split(":")[-1].strip().rstrip("pt")
        height = line_height.strip().split(":")[-1].strip().rstrip("pt")
    return float(width), float(height)

def build_latex_doc(tex, latexmk_options, output_name=None, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        pdfname = os.path.join(tmpdirname, basename + ".pdf")
        if output_name:
            shutil.copy2(pdfname, output_name)

def text_dimensions(
    text, fontsize="11pt", preamble="", silent=True, latexmk_options=None
):
    tex = get_latex_fontdoc(text, fontsize=fontsize, preamble=preamble)
    width, height = get_latex_dims(
        tex, silent=silent, latexmk_options=latexmk_options
    )
    return width, height


"""
This file is part of labella.py.

Author: G.J.J. van den Burg
"""

import os
import shutil
import subprocess
import tempfile
import unicodedata

def uni2tex(text):
    # Courtesy of http://example.com
    accents = {
        0x0300: "`",
        0x0301: "'",
        0x0302: "^",
        0x0308: '"',
        0x030B: "H",
        0x0303: "~",
        0x0327: "c",
        0x0328: "k",
        0x0304: "=",
        0x0331: "b",
        0x0307: ".",
        0x0323: "d",
        0x030A: "r",
        0x0306: "u",
        0x030C: "v",
    }
    out = ""
    txt = tuple(text)
    i = 0
    while i < len(txt):
        char = text[i]
        code = ord(char)

        # combining marks
        if unicodedata.category(char) in ("Mn", "Mc") and code in accents:
            out += "\\%s{%s}" % (accents[code], txt[i + 1])
            i += 1
        # precomposed characters
        elif unicodedata.decomposition(char):
            base, acc = unicodedata.decomposition(char).split()
            acc = int(acc, 16)
            base = int(base, 16)
            if acc in accents:
                out += "\\%s{%s}" % (accents[acc], chr(base))
            else:
                out += char
        else:
            out += char
        i += 1
    return out

def get_latex_fontdoc(text, fontsize="11pt", preamble=""):
    tex = r"""\documentclass[preview, {fontsize}]{{standalone}}
{preamble}%
\begin{{document}}
{text}%
\newlength{{\lblwidth}}%
\newlength{{\lblheight}}%
\settowidth{{\lblwidth}}{{{text}}}%
\settoheight{{\lblheight}}{{{text}}}%
\typeout{{LABELWIDTH: \the\lblwidth}}%
\typeout{{LABELHEIGHT: \the\lblheight}}%
\end{{document}}
""".format(
        fontsize=fontsize, text=uni2tex(text), preamble=uni2tex(preamble)
    )
    return tex

def compile_latex(fname, tmpdirname, latexmk_options, silent=True):
    compiler = "latexmk"
    if latexmk_options:
        compiler_args = latexmk_options + [
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    else:
        compiler_args = [
            "--pdf",
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    command = [compiler] + compiler_args
    try:
        output = subprocess.check_output(command, stderr=subprocess.STDOUT)
    except (OSError, IOError) as e:
        raise (e)
    except subprocess.CalledProcessError as e:
        print(e.output.decode())
        raise (e)
    else:
        if not silent:
            print(output.decode())

def get_latex_dims(tex, latexmk_options, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        logname = os.path.join(tmpdirname, basename + ".log")
        with open(logname, "r") as fid:
            lines = fid.readlines()

        line_width = next(
            (l for l in lines if l.startswith("LABELWIDTH")), None
        )
        line_height = next(
            (l for l in lines if l.startswith("LABELHEIGHT")), None
        )

        width = line_width.strip().split(":")[-1].strip().rstrip("pt")
        height = line_height.strip().split(":")[-1].strip().rstrip("pt")
    return float(width), float(height)

def build_latex_doc(tex, latexmk_options, output_name=None, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        pdfname = os.path.join(tmpdirname, basename + ".pdf")
        if output_name:
            shutil.copy2(pdfname, output_name)

def text_dimensions(
    text, fontsize="11pt", preamble="", silent=True, latexmk_options=None
):
    tex = get_latex_fontdoc(text, fontsize=fontsize, preamble=preamble)
    width, height = get_latex_dims(
        tex, silent=silent, latexmk_options=latexmk_options
    )
    r


if latexmk_options:
        compiler_args = latexmk_options + [
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    else:
        compiler_args = [
            "--pdf",
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]

"""
This file is part of labella.py.

Author: G.J.J. van den Burg
"""

import os
import shutil
import subprocess
import tempfile
import unicodedata

def uni2tex(text):
    # Courtesy of http://example.com
    accents = {
        0x0300: "`",
        0x0301: "'",
        0x0302: "^",
        0x0308: '"',
        0x030B: "H",
        0x0303: "~",
        0x0327: "c",
        0x0328: "k",
        0x0304: "=",
        0x0331: "b",
        0x0307: ".",
        0x0323: "d",
        0x030A: "r",
        0x0306: "u",
        0x030C: "v",
    }
    out = ""
    txt = tuple(text)
    i = 0
    while i < len(txt):
        char = text[i]
        code = ord(char)

        # combining marks
        if unicodedata.category(char) in ("Mn", "Mc") and code in accents:
            out += "\\%s{%s}" % (accents[code], txt[i + 1])
            i += 1
        # precomposed characters
        elif unicodedata.decomposition(char):
            base, acc = unicodedata.decomposition(char).split()
            acc = int(acc, 16)
            base = int(base, 16)
            if acc in accents:
                out += "\\%s{%s}" % (accents[acc], chr(base))
            else:
                out += char
        else:
            out += char
        i += 1
    return out

def get_latex_fontdoc(text, fontsize="11pt", preamble=""):
    tex = r"""\documentclass[preview, {fontsize}]{{standalone}}
{preamble}%
\begin{{document}}
{text}%
\newlength{{\lblwidth}}%
\newlength{{\lblheight}}%
\settowidth{{\lblwidth}}{{{text}}}%
\settoheight{{\lblheight}}{{{text}}}%
\typeout{{LABELWIDTH: \the\lblwidth}}%
\typeout{{LABELHEIGHT: \the\lblheight}}%
\end{{document}}
""".format(
        fontsize=fontsize, text=uni2tex(text), preamble=uni2tex(preamble)
    )
    return tex

def compile_latex(fname, tmpdirname, latexmk_options, silent=True):
    compiler = "latexmk"

    command = [compiler] + compiler_args
    try:
        output = subprocess.check_output(command, stderr=subprocess.STDOUT)
    except (OSError, IOError) as e:
        raise (e)
    except subprocess.CalledProcessError as e:
        print(e.output.decode())
        raise (e)
    else:
        if not silent:
            print(output.decode())

def get_latex_dims(tex, latexmk_options, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        logname = os.path.join(tmpdirname, basename + ".log")
        with open(logname, "r") as fid:
            lines = fid.readlines()

        line_width = next(
            (l for l in lines if l.startswith("LABELWIDTH")), None
        )
        line_height = next(
            (l for l in lines if l.startswith("LABELHEIGHT")), None
        )

        width = line_width.strip().split(":")[-1].strip().rstrip("pt")
        height = line_height.strip().split(":")[-1].strip().rstrip("pt")
    return float(width), float(height)

def build_latex_doc(tex, latexmk_options, output_name=None, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        pdfname = os.path.join(tmpdirname, basename + ".pdf")
        if output_name:
            shutil.copy2(pdfname, output_name)

def text_dimensions(
    text, fontsize="11pt", preamble="", silent=True, latexmk_options=None
):
    tex = get_latex_fontdoc(text, fontsize=fontsize, preamble=preamble)
    width, height = get_latex_dims(
        tex, silent=silent, latexmk_options=latexmk_options
    )
    return width, height


try:
        output = subprocess.check_output(command, stderr=subprocess.STDOUT)
    except (OSError, IOError) as e:
        raise (e)
    except subprocess.CalledProcessError as e:
        print(e.output.decode())
        raise (e)
    else:
        if not silent:
            print(output.decode())

"""
This file is part of labella.py.

Author: G.J.J. van den Burg
"""

import os
import shutil
import subprocess
import tempfile
import unicodedata

def uni2tex(text):
    # Courtesy of http://example.com
    accents = {
        0x0300: "`",
        0x0301: "'",
        0x0302: "^",
        0x0308: '"',
        0x030B: "H",
        0x0303: "~",
        0x0327: "c",
        0x0328: "k",
        0x0304: "=",
        0x0331: "b",
        0x0307: ".",
        0x0323: "d",
        0x030A: "r",
        0x0306: "u",
        0x030C: "v",
    }
    out = ""
    txt = tuple(text)
    i = 0
    while i < len(txt):
        char = text[i]
        code = ord(char)

        # combining marks
        if unicodedata.category(char) in ("Mn", "Mc") and code in accents:
            out += "\\%s{%s}" % (accents[code], txt[i + 1])
            i += 1
        # precomposed characters
        elif unicodedata.decomposition(char):
            base, acc = unicodedata.decomposition(char).split()
            acc = int(acc, 16)
            base = int(base, 16)
            if acc in accents:
                out += "\\%s{%s}" % (accents[acc], chr(base))
            else:
                out += char
        else:
            out += char
        i += 1
    return out

def get_latex_fontdoc(text, fontsize="11pt", preamble=""):
    tex = r"""\documentclass[preview, {fontsize}]{{standalone}}
{preamble}%
\begin{{document}}
{text}%
\newlength{{\lblwidth}}%
\newlength{{\lblheight}}%
\settowidth{{\lblwidth}}{{{text}}}%
\settoheight{{\lblheight}}{{{text}}}%
\typeout{{LABELWIDTH: \the\lblwidth}}%
\typeout{{LABELHEIGHT: \the\lblheight}}%
\end{{document}}
""".format(
        fontsize=fontsize, text=uni2tex(text), preamble=uni2tex(preamble)
    )
    return tex

def compile_latex(fname, tmpdirname, latexmk_options, silent=True):
    compiler = "latexmk"
    if latexmk_options:
        compiler_args = latexmk_options + [
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    else:
        compiler_args = [
            "--pdf",
            "--outdir=" + tmpdirname,
            "--interaction=nonstopmode",
            fname,
        ]
    command = [compiler] + compiler_args

def get_latex_dims(tex, latexmk_options, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        logname = os.path.join(tmpdirname, basename + ".log")
        with open(logname, "r") as fid:
            lines = fid.readlines()

        line_width = next(
            (l for l in lines if l.startswith("LABELWIDTH")), None
        )
        line_height = next(
            (l for l in lines if l.startswith("LABELHEIGHT")), None
        )

        width = line_width.strip().split(":")[-1].strip().rstrip("pt")
        height = line_height.strip().split(":")[-1].strip().rstrip("pt")
    return float(width), float(height)

def build_latex_doc(tex, latexmk_options, output_name=None, silent=True):
    with tempfile.TemporaryDirectory() as tmpdirname:
        basename = "labella_text"
        fname = os.path.join(tmpdirname, basename + ".tex")
        with open(fname, "w") as fid:
            fid.write(tex)

        compile_latex(fname, tmpdirname, latexmk_options, silent=silent)

        pdfname = os.path.join(tmpdirname, basename + ".pdf")
        if output_name:
            shutil.copy2(pdfname, output_name)

def text_dimensions(
    text, fontsize="11pt", preamble="", silent=True, latexmk_options=None
):
    tex = get_latex_fontdoc(text, fontsize=fontsize, preamble=preamble)
    width, height = get_latex_dims(
        tex, silent=silent, latexmk_options=latexmk_options
    )
    return width, height


def static_check_sent_message_response() -> None:

from flax.protocols.protocol_message_types import ProtocolMessageTypes as pmt, ProtocolMessageTypes

NO_REPLY_EXPECTED = [
    # full_node -> full_node messages
    pmt.new_peak,
    pmt.new_transaction,
    pmt.new_unfinished_block,
    pmt.new_signage_point_or_end_of_sub_slot,
    pmt.request_mempool_transactions,
    pmt.new_compact_vdf,
    pmt.request_mempool_transactions,
]

"""
VAILD_REPLY_MESSAGE_MAP:
key: sent message type.
value: valid reply message types, from the view of the requester.
A state machine can be built from this message map.
"""

VAILD_REPLY_MESSAGE_MAP = {
    # messages for all services
    # pmt.handshake is handled in WSFlaxConnection.perform_handshake
    # full_node -> full_node protocol messages
    pmt.request_transaction: [pmt.respond_transaction],
    pmt.request_proof_of_weight: [pmt.respond_proof_of_weight],
    pmt.request_block: [pmt.respond_block, pmt.reject_block],
    pmt.request_blocks: [pmt.respond_blocks, pmt.reject_blocks],
    pmt.request_unfinished_block: [pmt.respond_unfinished_block],
    pmt.request_signage_point_or_end_of_sub_slot: [pmt.respond_signage_point, pmt.respond_end_of_sub_slot],
    pmt.request_compact_vdf: [pmt.respond_compact_vdf],
    pmt.request_peers: [pmt.respond_peers],
}

"""Check that allowed message data structures VALID_REPLY_MESSAGE_MAP and NO_REPLY_EXPECTED are consistent."""
    # Reply and non-reply sets should not overlap: This check should be static
    overlap = set(NO_REPLY_EXPECTED).intersection(set(VAILD_REPLY_MESSAGE_MAP.keys()))
    if len(overlap) != 0:
        raise AssertionError("Overlapping NO_REPLY_EXPECTED and VAILD_REPLY_MESSAGE_MAP values: {}")

def message_requires_reply(sent: ProtocolMessageTypes) -> bool:
    """Return True if message has an entry in the full node P2P message map"""
    # If we knew the peer NodeType is FULL_NODE, we could also check `sent not in NO_REPLY_EXPECTED`
    return sent in VAILD_REPLY_MESSAGE_MAP

def message_response_ok(sent: ProtocolMessageTypes, received: ProtocolMessageTypes) -> bool:
    """
    Check to see that peers respect protocol message types in reply.
    Call with received == None to indicate that we do not expect a specific reply message type.
    """
    # Errors below are runtime protocol message mismatches from peers
    if sent in VAILD_REPLY_MESSAGE_MAP:
        if received not in VAILD_REPLY_MESSAGE_MAP[sent]:
            return False

    return True

# Run `static_check_sent_message_response` to check this static invariant at import time
static_check_sent_message_response()


"""Return True if message has an entry in the full node P2P message map"""
    # If we knew the peer NodeType is FULL_NODE, we could also check `sent not in NO_REPLY_EXPECTED`
    return sent in VAILD_REPLY_MESSAGE_MAP

from flax.protocols.protocol_message_types import ProtocolMessageTypes as pmt, ProtocolMessageTypes

NO_REPLY_EXPECTED = [
    # full_node -> full_node messages
    pmt.new_peak,
    pmt.new_transaction,
    pmt.new_unfinished_block,
    pmt.new_signage_point_or_end_of_sub_slot,
    pmt.request_mempool_transactions,
    pmt.new_compact_vdf,
    pmt.request_mempool_transactions,
]

"""
VAILD_REPLY_MESSAGE_MAP:
key: sent message type.
value: valid reply message types, from the view of the requester.
A state machine can be built from this message map.
"""

VAILD_REPLY_MESSAGE_MAP = {
    # messages for all services
    # pmt.handshake is handled in WSFlaxConnection.perform_handshake
    # full_node -> full_node protocol messages
    pmt.request_transaction: [pmt.respond_transaction],
    pmt.request_proof_of_weight: [pmt.respond_proof_of_weight],
    pmt.request_block: [pmt.respond_block, pmt.reject_block],
    pmt.request_blocks: [pmt.respond_blocks, pmt.reject_blocks],
    pmt.request_unfinished_block: [pmt.respond_unfinished_block],
    pmt.request_signage_point_or_end_of_sub_slot: [pmt.respond_signage_point, pmt.respond_end_of_sub_slot],
    pmt.request_compact_vdf: [pmt.respond_compact_vdf],
    pmt.request_peers: [pmt.respond_peers],
}

def static_check_sent_message_response() -> None:
    """Check that allowed message data structures VALID_REPLY_MESSAGE_MAP and NO_REPLY_EXPECTED are consistent."""
    # Reply and non-reply sets should not overlap: This check should be static
    overlap = set(NO_REPLY_EXPECTED).intersection(set(VAILD_REPLY_MESSAGE_MAP.keys()))
    if len(overlap) != 0:
        raise AssertionError("Overlapping NO_REPLY_EXPECTED and VAILD_REPLY_MESSAGE_MAP values: {}")

def message_requires_reply(sent: ProtocolMessageTypes) -> bool:

def message_response_ok(sent: ProtocolMessageTypes, received: ProtocolMessageTypes) -> bool:
    """
    Check to see that peers respect protocol message types in reply.
    Call with received == None to indicate that we do not expect a specific reply message type.
    """
    # Errors below are runtime protocol message mismatches from peers
    if sent in VAILD_REPLY_MESSAGE_MAP:
        if received not in VAILD_REPLY_MESSAGE_MAP[sent]:
            return False

    return True

# Run `static_check_sent_message_response` to check this static invariant at import time
static_check_sent_message_response()


"""
    Check to see that peers respect protocol message types in reply.
    Call with received == None to indicate that we do not expect a specific reply message type.
    """
    # Errors below are runtime protocol message mismatches from peers

from flax.protocols.protocol_message_types import ProtocolMessageTypes as pmt, ProtocolMessageTypes

NO_REPLY_EXPECTED = [
    # full_node -> full_node messages
    pmt.new_peak,
    pmt.new_transaction,
    pmt.new_unfinished_block,
    pmt.new_signage_point_or_end_of_sub_slot,
    pmt.request_mempool_transactions,
    pmt.new_compact_vdf,
    pmt.request_mempool_transactions,
]

"""
VAILD_REPLY_MESSAGE_MAP:
key: sent message type.
value: valid reply message types, from the view of the requester.
A state machine can be built from this message map.
"""

VAILD_REPLY_MESSAGE_MAP = {
    # messages for all services
    # pmt.handshake is handled in WSFlaxConnection.perform_handshake
    # full_node -> full_node protocol messages
    pmt.request_transaction: [pmt.respond_transaction],
    pmt.request_proof_of_weight: [pmt.respond_proof_of_weight],
    pmt.request_block: [pmt.respond_block, pmt.reject_block],
    pmt.request_blocks: [pmt.respond_blocks, pmt.reject_blocks],
    pmt.request_unfinished_block: [pmt.respond_unfinished_block],
    pmt.request_signage_point_or_end_of_sub_slot: [pmt.respond_signage_point, pmt.respond_end_of_sub_slot],
    pmt.request_compact_vdf: [pmt.respond_compact_vdf],
    pmt.request_peers: [pmt.respond_peers],
}

def static_check_sent_message_response() -> None:
    """Check that allowed message data structures VALID_REPLY_MESSAGE_MAP and NO_REPLY_EXPECTED are consistent."""
    # Reply and non-reply sets should not overlap: This check should be static
    overlap = set(NO_REPLY_EXPECTED).intersection(set(VAILD_REPLY_MESSAGE_MAP.keys()))
    if len(overlap) != 0:
        raise AssertionError("Overlapping NO_REPLY_EXPECTED and VAILD_REPLY_MESSAGE_MAP values: {}")

def message_requires_reply(sent: ProtocolMessageTypes) -> bool:
    """Return True if message has an entry in the full node P2P message map"""
    # If we knew the peer NodeType is FULL_NODE, we could also check `sent not in NO_REPLY_EXPECTED`
    return sent in VAILD_REPLY_MESSAGE_MAP

def message_response_ok(sent: ProtocolMessageTypes, received: ProtocolMessageTypes) -> bool:

    if sent in VAILD_REPLY_MESSAGE_MAP:
        if received not in VAILD_REPLY_MESSAGE_MAP[sent]:
            return False

    return True

# Run `static_check_sent_message_response` to check this static invariant at import time
static_check_sent_message_response()


if sent in VAILD_REPLY_MESSAGE_MAP:
        if received not in VAILD_REPLY_MESSAGE_MAP[sent]:
            return False

from flax.protocols.protocol_message_types import ProtocolMessageTypes as pmt, ProtocolMessageTypes

NO_REPLY_EXPECTED = [
    # full_node -> full_node messages
    pmt.new_peak,
    pmt.new_transaction,
    pmt.new_unfinished_block,
    pmt.new_signage_point_or_end_of_sub_slot,
    pmt.request_mempool_transactions,
    pmt.new_compact_vdf,
    pmt.request_mempool_transactions,
]

"""
VAILD_REPLY_MESSAGE_MAP:
key: sent message type.
value: valid reply message types, from the view of the requester.
A state machine can be built from this message map.
"""

VAILD_REPLY_MESSAGE_MAP = {
    # messages for all services
    # pmt.handshake is handled in WSFlaxConnection.perform_handshake
    # full_node -> full_node protocol messages
    pmt.request_transaction: [pmt.respond_transaction],
    pmt.request_proof_of_weight: [pmt.respond_proof_of_weight],
    pmt.request_block: [pmt.respond_block, pmt.reject_block],
    pmt.request_blocks: [pmt.respond_blocks, pmt.reject_blocks],
    pmt.request_unfinished_block: [pmt.respond_unfinished_block],
    pmt.request_signage_point_or_end_of_sub_slot: [pmt.respond_signage_point, pmt.respond_end_of_sub_slot],
    pmt.request_compact_vdf: [pmt.respond_compact_vdf],
    pmt.request_peers: [pmt.respond_peers],
}

def static_check_sent_message_response() -> None:
    """Check that allowed message data structures VALID_REPLY_MESSAGE_MAP and NO_REPLY_EXPECTED are consistent."""
    # Reply and non-reply sets should not overlap: This check should be static
    overlap = set(NO_REPLY_EXPECTED).intersection(set(VAILD_REPLY_MESSAGE_MAP.keys()))
    if len(overlap) != 0:
        raise AssertionError("Overlapping NO_REPLY_EXPECTED and VAILD_REPLY_MESSAGE_MAP values: {}")

def message_requires_reply(sent: ProtocolMessageTypes) -> bool:
    """Return True if message has an entry in the full node P2P message map"""
    # If we knew the peer NodeType is FULL_NODE, we could also check `sent not in NO_REPLY_EXPECTED`
    return sent in VAILD_REPLY_MESSAGE_MAP

def message_response_ok(sent: ProtocolMessageTypes, received: ProtocolMessageTypes) -> bool:
    """
    Check to see that peers respect protocol message types in reply.
    Call with received == None to indicate that we do not expect a specific reply message type.
    """
    # Errors below are runtime protocol message mismatches from peers

    return True

# Run `static_check_sent_message_response` to check this static invariant at import time
static_check_sent_message_response()


from flax.protocols.protocol_message_types import ProtocolMessageTypes as pmt, ProtocolMessageTypes

NO_REPLY_EXPECTED = [
    # full_node -> full_node messages
    pmt.new_peak,
    pmt.new_transaction,
    pmt.new_unfinished_block,
    pmt.new_signage_point_or_end_of_sub_slot,
    pmt.request_mempool_transactions,
    pmt.new_compact_vdf,
    pmt.request_mempool_transactions,
]

"""
VAILD_REPLY_MESSAGE_MAP:
key: sent message type.
value: valid reply message types, from the view of the requester.
A state machine can be built from this message map.
"""

VAILD_REPLY_MESSAGE_MAP = {
    # messages for all services
    # pmt.handshake is handled in WSFlaxConnection.perform_handshake
    # full_node -> full_node protocol messages
    pmt.request_transaction: [pmt.respond_transaction],
    pmt.request_proof_of_weight: [pmt.respond_proof_of_weight],
    pmt.request_block: [pmt.respond_block, pmt.reject_block],
    pmt.request_blocks: [pmt.respond_blocks, pmt.reject_blocks],
    pmt.request_unfinished_block: [pmt.respond_unfinished_block],
    pmt.request_signage_point_or_end_of_sub_slot: [pmt.respond_signage_point, pmt.respond_end_of_sub_slot],
    pmt.request_compact_vdf: [pmt.respond_compact_vdf],
    pmt.request_peers: [pmt.respond_peers],
}

def static_check_sent_message_response() -> None:
    """Check that allowed message data structures VALID_REPLY_MESSAGE_MAP and NO_REPLY_EXPECTED are consistent."""
    # Reply and non-reply sets should not overlap: This check should be static
    overlap = set(NO_REPLY_EXPECTED).intersection(set(VAILD_REPLY_MESSAGE_MAP.keys()))
    if len(overlap) != 0:
        raise AssertionError("Overlapping NO_REPLY_EXPECTED and VAILD_REPLY_MESSAGE_MAP values: {}")

def message_requires_reply(sent: ProtocolMessageTypes) -> bool:
    """Return True if message has an entry in the full node P2P message map"""
    # If we knew the peer NodeType is FULL_NODE, we could also check `sent not in NO_REPLY_EXPECTED`
    return sent in VAILD_REPLY_MESSAGE_MAP

def message_response_ok(sent: ProtocolMessageTypes, received: ProtocolMessageTypes) -> bool:
    """
    Check to see that peers respect protocol message types in reply.
    Call with received == None to indicate that we do not expect a specific reply message type.
    """
    # Errors below are runtime protocol message mismatches from peers
    if sent in VAILD_REPLY_MESSAGE_MAP:
        if received not in VAILD_REPLY_MESSAGE_MAP[sent]:
            return False

# Run `static_check_sent_message_response` to check this static invariant at import time
static_check_sent_message_response()


from flax.protocols.protocol_message_types import ProtocolMessageTypes as pmt, ProtocolMessageTypes

NO_REPLY_EXPECTED = [
    # full_node -> full_node messages
    pmt.new_peak,
    pmt.new_transaction,
    pmt.new_unfinished_block,
    pmt.new_signage_point_or_end_of_sub_slot,
    pmt.request_mempool_transactions,
    pmt.new_compact_vdf,
    pmt.request_mempool_transactions,
]

"""
VAILD_REPLY_MESSAGE_MAP:
key: sent message type.
value: valid reply message types, from the view of the requester.
A state machine can be built from this message map.
"""

VAILD_REPLY_MESSAGE_MAP = {
    # messages for all services
    # pmt.handshake is handled in WSFlaxConnection.perform_handshake
    # full_node -> full_node protocol messages
    pmt.request_transaction: [pmt.respond_transaction],
    pmt.request_proof_of_weight: [pmt.respond_proof_of_weight],
    pmt.request_block: [pmt.respond_block, pmt.reject_block],
    pmt.request_blocks: [pmt.respond_blocks, pmt.reject_blocks],
    pmt.request_unfinished_block: [pmt.respond_unfinished_block],
    pmt.request_signage_point_or_end_of_sub_slot: [pmt.respond_signage_point, pmt.respond_end_of_sub_slot],
    pmt.request_compact_vdf: [pmt.respond_compact_vdf],
    pmt.request_peers: [pmt.respond_peers],
}

def static_check_sent_message_response() -> None:
    """Check that allowed message data structures VALID_REPLY_MESSAGE_MAP and NO_REPLY_EXPECTED are consistent."""
    # Reply and non-reply sets should not overlap: This check should be static
    overlap = se

    if len(overlap) != 0:
        raise AssertionError("Overlapping NO_REPLY_EXPECTED and VAILD_REPLY_MESSAGE_MAP values: {}")

def message_requires_reply(sent: ProtocolMessageTypes) -> bool:
    """Return True if message has an entry in the full node P2P message map"""
    # If we knew the peer NodeType is FULL_NODE, we could also check `sent not in NO_REPLY_EXPECTED`
    return sent in VAILD_REPLY_MESSAGE_MAP

def message_response_ok(sent: ProtocolMessageTypes, received: ProtocolMessageTypes) -> bool:
    """
    Check to see that peers respect protocol message types in reply.
    Call with received == None to indicate that we do not expect a specific reply message type.
    """
    # Errors below are runtime protocol message mismatches from peers
    if sent in VAILD_REPLY_MESSAGE_MAP:
        if received not in VAILD_REPLY_MESSAGE_MAP[sent]:
            return False

    return True

# Run `static_check_sent_message_response` to check this static invariant at import time
static_check_sent_message_response()


    # Reply and non-reply sets should not overlap: This check should be static
    overlap = set(NO_REPLY_EXPECTED).intersection(set(VAILD_REPLY_MESSAGE_MAP.keys()))
    if len(overlap) != 0:
        raise AssertionError("Overlapping NO_REPLY_EXPECTED and VAILD_REPLY_MESSAGE_MAP values: {}")

from flax.protocols.protocol_message_types import ProtocolMessageTypes as pmt, ProtocolMessageTypes

NO_REPLY_EXPECTED = [
    # full_node -> full_node messages
    pmt.new_peak,
    pmt.new_transaction,
    pmt.new_unfinished_block,
    pmt.new_signage_point_or_end_of_sub_slot,
    pmt.request_mempool_transactions,
    pmt.new_compact_vdf,
    pmt.request_mempool_transactions,
]

"""
VAILD_REPLY_MESSAGE_MAP:
key: sent message type.
value: valid reply message types, from the view of the requester.
A state machine can be built from this message map.
"""

VAILD_REPLY_MESSAGE_MAP = {
    # messages for all services
    # pmt.handshake is handled in WSFlaxConnection.perform_handshake
    # full_node -> full_node protocol messages
    pmt.request_transaction: [pmt.respond_transaction],
    pmt.request_proof_of_weight: [pmt.respond_proof_of_weight],
    pmt.request_block: [pmt.respond_block, pmt.reject_block],
    pmt.request_blocks: [pmt.respond_blocks, pmt.reject_blocks],
    pmt.request_unfinished_block: [pmt.respond_unfinished_block],
    pmt.request_signage_point_or_end_of_sub_slot: [pmt.respond_signage_point, pmt.respond_end_of_sub_slot],
    pmt.request_compact_vdf: [pmt.respond_compact_vdf],
    pmt.request_peers: [pmt.respond_peers],
}

def static_check_sent_message_response() -> None:
    """Check that allowed message data structures VALID_REPLY_MESSAGE_MAP and NO_REPLY_EXPECTED are consistent."""

def message_requires_reply(sent: ProtocolMessageTypes) -> bool:
    """Return True if message has an entry in the full node P2P message map"""
    # If we knew the peer NodeType is FULL_NODE, we could also check `sent not in NO_REPLY_EXPECTED`
    return sent in VAILD_REPLY_MESSAGE_MAP

def message_response_ok(sent: ProtocolMessageTypes, received: ProtocolMessageTypes) -> bool:
    """
    Check to see that peers respect protocol message types in reply.
    Call with received == None to indicate that we do not expect a specific reply message type.
    """
    # Errors below are runtime protocol message mismatches from peers
    if sent in VAILD_REPLY_MESSAGE_MAP:
        if received not in VAILD_REPLY_MESSAGE_MAP[sent]:
            return False

    return True

# Run `static_check_sent_message_response` to check this static invariant at import time
static_check_sent_message_response()