<commit_before># encoding=utf-8
"""
mwtemplates
Copyright (c) 2012-2014 Dan Michael O. Heggø

Simple wikitext template parser and editor
"""

from __future__ import unicode_literals
from __future__ import print_function
import sys

# <root>LLorem ipsum <template>
# <title>
#     <tplarg>
#         <title>Min lille mal</title>
#     </tplarg>
# </title>
# <part>
#     <name index="1"/>
#     <value>sorona</value>
# </part>
# <part>
#     <name>Kake</name>=<value>sake</value>
# </part>
# </template>
# </root>

# <root>LLorem ipsum
# <template>
#   <title>
#       <tplarg>
#           <title>
#               Min
#               <template>
#                   <title>lille</title>
#               </template>
#               mal
#           </title>
#       </tplarg>
#       son
#   </title>
# <part>
#     <name index="1"/>
#     <value>sorona</value>
# </part>
# <part>
#     <name>Kake</name>=<value>sake</value>
# </part>
# </template>
# </root>

import logging
import re
from lxml import etree
try:
    from io import StringIO  # Python 3
except ImportError:
    from StringIO import StringIO  # Python 2
from mwtemplates.preprocessor import preprocessToXml

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

class TemplateParseError(Exception):
    pass

def most_common(lst):
    return max(set(lst), key=lst.count)

def get_whitespace(txt):
    """
    Returns a list containing the whitespace to the left and
    right of a string as its two elements
    """

    # if the entire parameter is whitespace
    rall = re.search(r'^([\s])+$', txt)
    if rall:
        tmp = txt.split('\n', 1)
        if len(tmp) == 2:
            return (tmp[0], '\n' + tmp[1])  # left, right
        else:
            return ('', tmp[0])  # left, right
    left = ''
    # find whitespace to the left of the parameter
    rlm = re.search(r'^([\s])+', txt)
    if rlm:
        left = rlm.group(0)
    right = ''
    # find whitespace to the right of the parameter
    rrm = re.search(r'([\s])+$', txt)
    if rrm:
        right = rrm.group(0)
    return (left, right)

# try:
#     self.doc = etree.parse(StringIO(docstr), parser)
#     #self.doc = etree.parse(self.filename, parser)
#     for e in parser.error_log:
#         print "Warning: "+e.message
# except etree.XMLSyntaxError:
#     print "Failed to parse xml file: ",filename
#     for e in parser.error_log:
#         print "Warning: "+e.message
#     sys.exit(2)

def get_wikitext(node):
    tmp = ''
    if node.text is not None:
        tmp += node.text
    for child in node:
        if child.tag == 'tplarg':
            tmp += '{{{%s}}}' % get_wikitext(child)
        elif child.tag == 'template':
            tmp += '{{%s}}' % get_wikitext(child)
        elif child.tag in ['title', 'name', 'value']:
            tmp += get_wikitext(child)
        elif child.tag == 'part':
            tmp += '|' + get_wikitext(child)
        else:
            raise TemplateParseError("Found unknown tag: %s"
                                     % etree.tostring(child))
        if child.tail is not None:
            tmp += child.tail
    return tmp

# def get_arg(node):
#    node.xpath('name')[0]
#    node.xpath('value')[0]

class Templates(object):
    """
    Dictionary-like class to hold templates.
    The first character of the key is case-insensitive, to mimic MediaWiki,
    where {{Hello}} and {{hello}} are considered the same template, while
    {{Hello}} and {{HEllo}} are not.
    """

    def __init__(self, editor, doc):
        self.editor = editor
        self.doc = doc
        # self._entries = self._templates()

    def _templates(self):
        templates = []
        for node in self.doc.findall('.//template'):
            q = Template(node, self.editor)
            if q.key != '':
                templates.append(q)
        return templates

    # def add(self, node):
    #     self._entries.append(Template(node, self.editor))

    def __contains__(self, tpl_name):
        tpl_name = tpl_name[0].upper() + tpl_name[1:]
        for tpl in self._templates():
            if tpl.key == tpl_name:
                return True
        return False

    def __getitem__(self, tpl_name):
        tpl_name = tpl_name[0].upper() + tpl_name[1:]
        return [tpl for tpl in self._templates() if tpl.key == tpl_name]
        # raise KeyError

    def __repr__(self):
        return self._templates().__repr__()

    def __len__(self):
        return len(self._templates())

    def keys(self):
        return list(set([tpl.key for tpl in self._templates()]))

    def items(self):
        return [(key, self[key]) for key in self.keys()]

    def iteritems(self):
        for key in self.keys():
            yield (key, self[key])

    # def __contains__(self, x):
    #     return self.get_key(x) in self._entries

    # def __getitem__(self, x):
    #     return self._entries[self.get_key(x)]

    # def __setitem__(self, x, val):
    #     self._entries[self.get_key(x)] = val

    # def values():
    #     return self._entries.values()

    # def items(self):
    #     return self._entries.items()

class Parameters(object):
    """
    Dictionary-like class to hold a template's parameters.
    """

    def __init__(self, template):
        self._entries = []
        self.template = template

    def __contains__(self, param):
        # param is a Parameter object or a string
        if param in self._entries:
            return True
        return param in self.keys()

    def __getitem__(self, param_name):
        for param in self._entries:
            if param.key == param_name:
                return param
        raise KeyError('No such parameter')

    def get(self, param_name, default=None):
        try:
            return self.__getitem__(param_name)
        except KeyError:
            return default

    def __setitem__(self, name, val):
        # python2   <->  python3
        # 'unicode' <-> 'str'
        # 'str'     <-> 'bytes'
        if type(val).__name__ in ['unicode', 'str', 'bytes' 'int']:
            for entry in self._entries:
                if entry.key == name:
                    # parameter changed:
                    entry.edit(val)
                    return
            # new parameter:
            name_ws, value_ws = self.find_whitespace_pattern()
            # print '"' + whitespace[0] + '", "' + whitespace[1] + '"'
            name = name_ws[0] + name + name_ws[1]
            val = value_ws[0] + val + value_ws[1]
            parentnode = self.template.node
            node = etree.XML('<part><name>%s</name>=<value>%s</value></part>'
                             % (name, val))
            parentnode.append(node)
            self.add(node)
        else:
            raise TypeError

    def __delitem__(self, param_name):
        param = self.__getitem__(param_name)
        return self.remove(param)

    def remove(self, param):
        # param can be a Parameter object or a string

        if param in self.keys():
            param = self.__getitem__(param)

        i = self.index(param)
        logger.debug('Removing parameter "%s"', param.key)
        self.template.node.remove(param.node)
        del self._entries[i]

    def __repr__(self):
        return '<Parameters: %s>' % (', '.join(['%s="%s"' % (x.key, x.value) for x in self._entries]))

    def keys(self):
        # Return all parameter keys, including duplicates
        return [param.key for param in self._entries]

    def index(self, param):
        return self._entries.index(param)

    def __len__(self):
        return len(self._entries)

    def __iter__(self):
        # Iterate over all parameters, including duplicates
        for param in self._entries:
            yield param

    def items(self):
        # All parameters, including duplicates
        return [(param.key, param) for param in self._entries]

    def iteritems(self):
        # Iterate over all parameters, including duplicates
        for param in self._entries:
            yield (param.key, param)

    def add(self, node):
        self._entries.append(Parameter(node))

    def find_whitespace_pattern(self):
        """
        Try to find a whitespace pattern in the existing parameters
        to be applied to a newly added parameter
        """
        name_ws = []
        value_ws = []
        for entry in self._entries:
            name_ws.append(get_whitespace(entry.name))
            if entry.value != '':
                value_ws.append(get_whitespace(entry._value))  # _value is unstripped
        if len(value_ws) >= 1:
            value_ws = most_common(value_ws)
        else:
            value_ws = ('', ' ')
        if len(name_ws) >= 1:
            name_ws = most_common(name_ws)
        else:
            name_ws = (' ', '')
        return name_ws, value_ws

class Parameter(object):
    """
    Class to hold a single template parameter.
    """

    def __init__(self, node):
        self.node = node
        namenode = node.xpath('name')[0]
        self._name = get_wikitext(namenode)
        self._value = get_wikitext(node.xpath('value')[0])
        idx = namenode.get('index')
        if idx:
            self._index = int(idx)
        else:
            self._index = -1
        logger.debug('  added parameter: %s=%s',
                     self._name.strip(), self._value.strip())

    def edit(self, val):
        logger.debug('Parameter "%s" changed: "%s" -> "%s"',
                     self.key, self.value.strip(), val)
        whitespace = get_whitespace(self._value)
        self._value = whitespace[0] + val + whitespace[1]
        # self.node.xpath('value')[0].text = self._value
        self.node.replace(self.node.xpath('value')[0],
                          etree.XML('<value>' + self._value + '</value>'))

    # @property
    # def index(self):
    #    return self._index

    @property
    def name(self):
        return self._name

    @property
    def key(self):
        if self._index != -1:
            return self._index
        else:
            tmp = self.name.strip()
            if tmp.isnumeric():
                return int(tmp)
            else:
                return tmp

    @property
    def value(self):
        return self._value.strip()

    @value.setter
    def value(self, val):
        self.edit(val)

    def __eq__(self, param):
        if type(param).__name__ == 'unicode':
            return self.__unicode__() == param
        elif type(param).__name__ == 'str':
            return self.__str__() == param
        elif type(param).__name__ == 'int':
            try:
                return int(self.__unicode__()) == param
            except ValueError:
                return False
        else:
            return False

    def __ne__(self, param):
        return not self.__eq__(param)

    def __unicode__(self):
        return self._value.strip()

    def __str__(self):
        if sys.version_info > (3, 0):
            return self.__unicode__()
        else:
            return self.__unicode__().encode('utf-8')

    def __float__(self):
        return float(self.__unicode__())

    def __int__(self):
        return int(self.__unicode__())

    def __repr__(self):
        return self.value

class Template(object):
    """
    Class to hold a single template.
    """

    def __init__(self, node, editor):

        logger.debug('Initiating Template object')

        self.node = node
        self.editor = editor
        self._name = ''
        self.parameters = Parameters(self)
        for elem in node:
            if elem.tag == 'title':
                self._name = get_wikitext(elem)
                logger.debug('  name: %s', self.name)
            elif elem.tag == 'part':
                self.parameters.add(elem)

    def __repr__(self):
        return '<Template:"%s" at line %d>' % (self.key, self.node.sourceline)

    @property
    def key(self):
        tmp = self._name.strip()
        tmp = re.sub(r'^(?:[Mm]al|[Tt]emplate):', '', tmp)
        if len(tmp) == 0:
            return ''
        return tmp[0].upper() + tmp[1:]

    @property
    def name(self):
        return self.key

    @name.setter
    def name(self, val):
        whitespace = get_whitespace(self._name)
        self._name = whitespace[0] + val + whitespace[1]
        self.node.xpath('title')[0].text = self._name

    def has_param(self, name):
        """ Returns True if the parameter is defined and non-empty """
        return name in self.parameters and self.parameters[name] != ''

    def get_anonymous_parameters(self):
        """
        Returns the anonymous (non-named) parameters as a normal list, but with
        indices starting on 1, not 0, to comply with standard mediawiki practice.
        """
        tmp = {}
        for param in self.parameters:
            if type(param.key) == int:
                tmp[param.key] = param.value
        lst = [None]
        for param in range(1, max(tmp.keys()) + 1):
            lst.append(tmp[param])
        return lst

    def __unicode__(self):
        tmp = '{{%s' % self.name
        for param in self.parameters:
            tmp += '\n |%s=%s' % (param.key, param.value)
        if len(self.parameters) > 0:
            tmp += '\n'
        tmp += '}}'

        return tmp

    def __str__(self):
        if sys.version_info > (3, 0):
            return self.__unicode__()
        else:
            return self.__unicode__().encode('utf-8')

    def remove(self):
        self.__del__()

    def __del__(self):
        logger.debug('Removing node "%s"', self.key)
        parent = self.node.getparent()
        prevnode = None
        for node in parent.getchildren():
            if node == self.node:
                if prevnode is None:
                    if parent.text is None:
                        parent.text = self.node.tail.lstrip()
                    else:
                        parent.text += self.node.tail.lstrip()

                else:
                    if prevnode.tail is None:
                        prevnode.tail = self.node.tail.lstrip()
                    else:
                        prevnode.tail += self.node.tail.lstrip()
                parent.remove(self.node)
                return
            else:
                prevnode = node

class TemplateEditor(object):
    r"""
    TemplateEditor is the main class to work with templates in some wikitext.

    Example:

    >>> wikitext = '{{infobox country\n' \
    ...    + '|name=Fantasia\n' \
    ...    + '|population_census=4,830,300 }}'
    >>> editor = TemplateEditor(wikitext)
    >>> tpl = editor.templates['Infobox country'][0]
    >>> tpl.parameters['population_census'] = '5,033,676'
    >>> print(editor.wikitext())
    {{infobox country
    |name=Fantasia
    |population_census=5,033,676 }}
    """

    def __init__(self, text):
        xml = preprocessToXml(text)
        parser = etree.XMLParser()
        self.text = text
        self.doc = etree.parse(StringIO(xml), parser)
        # root = self.doc.xpath('/root')[0]

        self.templates = Templates(self, self.doc)

        # self.scan_for_templates()

    def xml(self):
        # for key, val in self.templates.items():
        #     for tpl in val:
        #         tpl.save()
        return etree.tostring(self.doc)

    def wikitext(self):
        # for key, val in self.templates.items():
        #     for tpl in val:
        #         tpl.save()
        txt = get_wikitext(self.doc.xpath('/root')[0])
        return txt
<commit_msg>Fix memory leak by using weakref<commit_after># encoding=utf-8
"""
mwtemplates
Copyright (c) 2012-2014 Dan Michael O. Heggø

Simple wikitext template parser and editor
"""

from __future__ import unicode_literals
from __future__ import print_function
import sys
import weakref
# <root>LLorem ipsum <template>
# <title>
#     <tplarg>
#         <title>Min lille mal</title>
#     </tplarg>
# </title>
# <part>
#     <name index="1"/>
#     <value>sorona</value>
# </part>
# <part>
#     <name>Kake</name>=<value>sake</value>
# </part>
# </template>
# </root>

# <root>LLorem ipsum
# <template>
#   <title>
#       <tplarg>
#           <title>
#               Min
#               <template>
#                   <title>lille</title>
#               </template>
#               mal
#           </title>
#       </tplarg>
#       son
#   </title>
# <part>
#     <name index="1"/>
#     <value>sorona</value>
# </part>
# <part>
#     <name>Kake</name>=<value>sake</value>
# </part>
# </template>
# </root>

import logging
import re
from lxml import etree
try:
    from io import StringIO  # Python 3
except ImportError:
    from StringIO import StringIO  # Python 2
from mwtemplates.preprocessor import preprocessToXml

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

class TemplateParseError(Exception):
    pass

def most_common(lst):
    return max(set(lst), key=lst.count)

def get_whitespace(txt):
    """
    Returns a list containing the whitespace to the left and
    right of a string as its two elements
    """

    # if the entire parameter is whitespace
    rall = re.search(r'^([\s])+$', txt)
    if rall:
        tmp = txt.split('\n', 1)
        if len(tmp) == 2:
            return (tmp[0], '\n' + tmp[1])  # left, right
        else:
            return ('', tmp[0])  # left, right
    left = ''
    # find whitespace to the left of the parameter
    rlm = re.search(r'^([\s])+', txt)
    if rlm:
        left = rlm.group(0)
    right = ''
    # find whitespace to the right of the parameter
    rrm = re.search(r'([\s])+$', txt)
    if rrm:
        right = rrm.group(0)
    return (left, right)

# try:
#     self.doc = etree.parse(StringIO(docstr), parser)
#     #self.doc = etree.parse(self.filename, parser)
#     for e in parser.error_log:
#         print "Warning: "+e.message
# except etree.XMLSyntaxError:
#     print "Failed to parse xml file: ",filename
#     for e in parser.error_log:
#         print "Warning: "+e.message
#     sys.exit(2)

def get_wikitext(node):
    tmp = ''
    if node.text is not None:
        tmp += node.text
    for child in node:
        if child.tag == 'tplarg':
            tmp += '{{{%s}}}' % get_wikitext(child)
        elif child.tag == 'template':
            tmp += '{{%s}}' % get_wikitext(child)
        elif child.tag in ['title', 'name', 'value']:
            tmp += get_wikitext(child)
        elif child.tag == 'part':
            tmp += '|' + get_wikitext(child)
        else:
            raise TemplateParseError("Found unknown tag: %s"
                                     % etree.tostring(child))
        if child.tail is not None:
            tmp += child.tail
    return tmp

# def get_arg(node):
#    node.xpath('name')[0]
#    node.xpath('value')[0]

class Templates(object):
    """
    Dictionary-like class to hold templates.
    The first character of the key is case-insensitive, to mimic MediaWiki,
    where {{Hello}} and {{hello}} are considered the same template, while
    {{Hello}} and {{HEllo}} are not.
    """

    def __init__(self, editor, doc):

        # Make child-parent reference weak to avoid memory leak
        self.editor = weakref.ref(editor)

        self.doc = doc
        # self._entries = self._templates()

    def _templates(self):
        templates = []
        for node in self.doc.findall('.//template'):
            q = Template(node, self.editor)
            if q.key != '':
                templates.append(q)
        return templates

    # def add(self, node):
    #     self._entries.append(Template(node, self.editor))

    def __contains__(self, tpl_name):
        tpl_name = tpl_name[0].upper() + tpl_name[1:]
        for tpl in self._templates():
            if tpl.key == tpl_name:
                return True
        return False

    def __getitem__(self, tpl_name):
        tpl_name = tpl_name[0].upper() + tpl_name[1:]
        return [tpl for tpl in self._templates() if tpl.key == tpl_name]
        # raise KeyError

    def __repr__(self):
        return self._templates().__repr__()

    def __len__(self):
        return len(self._templates())

    def keys(self):
        return list(set([tpl.key for tpl in self._templates()]))

    def items(self):
        return [(key, self[key]) for key in self.keys()]

    def iteritems(self):
        for key in self.keys():
            yield (key, self[key])

    # def __contains__(self, x):
    #     return self.get_key(x) in self._entries

    # def __getitem__(self, x):
    #     return self._entries[self.get_key(x)]

    # def __setitem__(self, x, val):
    #     self._entries[self.get_key(x)] = val

    # def values():
    #     return self._entries.values()

    # def items(self):
    #     return self._entries.items()

class Parameters(object):
    """
    Dictionary-like class to hold a template's parameters.
    """

    def __init__(self, template):
        self._entries = []

        # Make child-parent reference weak to avoid memory leak
        self.template = weakref.ref(template)

    def __contains__(self, param):
        # param is a Parameter object or a string
        if param in self._entries:
            return True
        return param in self.keys()

    def __getitem__(self, param_name):
        for param in self._entries:
            if param.key == param_name:
                return param
        raise KeyError('No such parameter')

    def get(self, param_name, default=None):
        try:
            return self.__getitem__(param_name)
        except KeyError:
            return default

    def __setitem__(self, name, val):
        # python2   <->  python3
        # 'unicode' <-> 'str'
        # 'str'     <-> 'bytes'
        if type(val).__name__ in ['unicode', 'str', 'bytes' 'int']:
            for entry in self._entries:
                if entry.key == name:
                    # parameter changed:
                    entry.edit(val)
                    return
            # new parameter:
            name_ws, value_ws = self.find_whitespace_pattern()
            # print '"' + whitespace[0] + '", "' + whitespace[1] + '"'
            name = name_ws[0] + name + name_ws[1]
            val = value_ws[0] + val + value_ws[1]
            parentnode = self.template().node
            node = etree.XML('<part><name>%s</name>=<value>%s</value></part>'
                             % (name, val))
            parentnode.append(node)
            self.add(node)
        else:
            raise TypeError

    def __delitem__(self, param_name):
        param = self.__getitem__(param_name)
        return self.remove(param)

    def remove(self, param):
        # param can be a Parameter object or a string

        if param in self.keys():
            param = self.__getitem__(param)

        i = self.index(param)
        logger.debug('Removing parameter "%s"', param.key)
        self.template().node.remove(param.node)
        del self._entries[i]

    def __repr__(self):
        return '<Parameters: %s>' % (', '.join(['%s="%s"' % (x.key, x.value) for x in self._entries]))

    def keys(self):
        # Return all parameter keys, including duplicates
        return [param.key for param in self._entries]

    def index(self, param):
        return self._entries.index(param)

    def __len__(self):
        return len(self._entries)

    def __iter__(self):
        # Iterate over all parameters, including duplicates
        for param in self._entries:
            yield param

    def items(self):
        # All parameters, including duplicates
        return [(param.key, param) for param in self._entries]

    def iteritems(self):
        # Iterate over all parameters, including duplicates
        for param in self._entries:
            yield (param.key, param)

    def add(self, node):
        self._entries.append(Parameter(node))

    def find_whitespace_pattern(self):
        """
        Try to find a whitespace pattern in the existing parameters
        to be applied to a newly added parameter
        """
        name_ws = []
        value_ws = []
        for entry in self._entries:
            name_ws.append(get_whitespace(entry.name))
            if entry.value != '':
                value_ws.append(get_whitespace(entry._value))  # _value is unstripped
        if len(value_ws) >= 1:
            value_ws = most_common(value_ws)
        else:
            value_ws = ('', ' ')
        if len(name_ws) >= 1:
            name_ws = most_common(name_ws)
        else:
            name_ws = (' ', '')
        return name_ws, value_ws

class Parameter(object):
    """
    Class to hold a single template parameter.
    """

    def __init__(self, node):
        self.node = node
        namenode = node.xpath('name')[0]
        self._name = get_wikitext(namenode)
        self._value = get_wikitext(node.xpath('value')[0])
        idx = namenode.get('index')
        if idx:
            self._index = int(idx)
        else:
            self._index = -1
        logger.debug('  added parameter: %s=%s',
                     self._name.strip(), self._value.strip())

    def edit(self, val):
        logger.debug('Parameter "%s" changed: "%s" -> "%s"',
                     self.key, self.value.strip(), val)
        whitespace = get_whitespace(self._value)
        self._value = whitespace[0] + val + whitespace[1]
        # self.node.xpath('value')[0].text = self._value
        self.node.replace(self.node.xpath('value')[0],
                          etree.XML('<value>' + self._value + '</value>'))

    # @property
    # def index(self):
    #    return self._index

    @property
    def name(self):
        return self._name

    @property
    def key(self):
        if self._index != -1:
            return self._index
        else:
            tmp = self.name.strip()
            if tmp.isnumeric():
                return int(tmp)
            else:
                return tmp

    @property
    def value(self):
        return self._value.strip()

    @value.setter
    def value(self, val):
        self.edit(val)

    def __eq__(self, param):
        if type(param).__name__ == 'unicode':
            return self.__unicode__() == param
        elif type(param).__name__ == 'str':
            return self.__str__() == param
        elif type(param).__name__ == 'int':
            try:
                return int(self.__unicode__()) == param
            except ValueError:
                return False
        else:
            return False

    def __ne__(self, param):
        return not self.__eq__(param)

    def __unicode__(self):
        return self._value.strip()

    def __str__(self):
        if sys.version_info > (3, 0):
            return self.__unicode__()
        else:
            return self.__unicode__().encode('utf-8')

    def __float__(self):
        return float(self.__unicode__())

    def __int__(self):
        return int(self.__unicode__())

    def __repr__(self):
        return self.value

class Template(object):
    """
    Class to hold a single template.
    """

    def __init__(self, node, editor):

        logger.debug('Initiating Template object')

        self.node = node
        self.editor = editor
        self._name = ''
        self.parameters = Parameters(self)
        for elem in node:
            if elem.tag == 'title':
                self._name = get_wikitext(elem)
                logger.debug('  name: %s', self.name)
            elif elem.tag == 'part':
                self.parameters.add(elem)

    def __repr__(self):
        return '<Template:"%s" at line %d>' % (self.key, self.node.sourceline)

    @property
    def key(self):
        tmp = self._name.strip()
        tmp = re.sub(r'^(?:[Mm]al|[Tt]emplate):', '', tmp)
        if len(tmp) == 0:
            return ''
        return tmp[0].upper() + tmp[1:]

    @property
    def name(self):
        return self.key

    @name.setter
    def name(self, val):
        whitespace = get_whitespace(self._name)
        self._name = whitespace[0] + val + whitespace[1]
        self.node.xpath('title')[0].text = self._name

    def has_param(self, name):
        """ Returns True if the parameter is defined and non-empty """
        return name in self.parameters and self.parameters[name] != ''

    def get_anonymous_parameters(self):
        """
        Returns the anonymous (non-named) parameters as a normal list, but with
        indices starting on 1, not 0, to comply with standard mediawiki practice.
        """
        tmp = {}
        for param in self.parameters:
            if type(param.key) == int:
                tmp[param.key] = param.value
        lst = [None]
        for param in range(1, max(tmp.keys()) + 1):
            lst.append(tmp[param])
        return lst

    def __unicode__(self):
        tmp = '{{%s' % self.name
        for param in self.parameters:
            tmp += '\n |%s=%s' % (param.key, param.value)
        if len(self.parameters) > 0:
            tmp += '\n'
        tmp += '}}'

        return tmp

    def __str__(self):
        if sys.version_info > (3, 0):
            return self.__unicode__()
        else:
            return self.__unicode__().encode('utf-8')

    def remove(self):
        logger.debug('Removing node "%s"', self.key)
        parent = self.node.getparent()
        prevnode = None
        for node in parent.getchildren():
            if node == self.node:
                if prevnode is None:
                    if parent.text is None:
                        parent.text = self.node.tail.lstrip()
                    else:
                        parent.text += self.node.tail.lstrip()

                else:
                    if prevnode.tail is None:
                        prevnode.tail = self.node.tail.lstrip()
                    else:
                        prevnode.tail += self.node.tail.lstrip()
                parent.remove(self.node)
                return
            else:
                prevnode = node

class TemplateEditor(object):
    r"""
    TemplateEditor is the main class to work with templates in some wikitext.

    Example:

    >>> wikitext = '{{infobox country\n' \
    ...    + '|name=Fantasia\n' \
    ...    + '|population_census=4,830,300 }}'
    >>> editor = TemplateEditor(wikitext)
    >>> tpl = editor.templates['Infobox country'][0]
    >>> tpl.parameters['population_census'] = '5,033,676'
    >>> print(editor.wikitext())
    {{infobox country
    |name=Fantasia
    |population_census=5,033,676 }}
    """

    def __init__(self, text):
        xml = preprocessToXml(text)
        parser = etree.XMLParser()
        self.text = text
        self.doc = etree.parse(StringIO(xml), parser)
        # root = self.doc.xpath('/root')[0]

        self.templates = Templates(self, self.doc)

        # self.scan_for_templates()

    def xml(self):
        # for key, val in self.templates.items():
        #     for tpl in val:
        #         tpl.save()
        return etree.tostring(self.doc)

    def wikitext(self):
        # for key, val in self.templates.items():
        #     for tpl in val:
        #         tpl.save()
        txt = get_wikitext(self.doc.xpath('/root')[0])
        return txt
<|endoftext|>


<commit_before>from __future__ import unicode_literals

import os
import shutil
import subprocess
from tempfile import NamedTemporaryFile

from django.contrib.staticfiles import finders
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.files.base import ContentFile
from django.utils.encoding import smart_bytes
from django.utils.six import string_types, text_type

from pipeline.conf import settings
from pipeline.exceptions import CompilerError
from pipeline.utils import to_class, set_std_streams_blocking

class Compiler(object):
    def __init__(self, storage=None, verbose=False):
        if storage is None:
            storage = staticfiles_storage
        self.storage = storage
        self.verbose = verbose

    @property
    def compilers(self):
        return [to_class(compiler) for compiler in settings.COMPILERS]

    def compile(self, paths, compiler_options={}, force=False):
        def _compile(input_path):
            for compiler in self.compilers:
                compiler = compiler(verbose=self.verbose, storage=self.storage)
                if compiler.match_file(input_path):
                    try:
                        infile = self.storage.path(input_path)
                    except NotImplementedError:
                        infile = finders.find(input_path)
                    outfile = compiler.output_path(infile, compiler.output_extension)
                    outdated = compiler.is_outdated(infile, outfile)
                    compiler.compile_file(infile, outfile,
                                          outdated=outdated, force=force,
                                          **compiler_options)

                    return compiler.output_path(input_path, compiler.output_extension)
            else:
                return input_path

        try:
            import multiprocessing
            from concurrent import futures
        except ImportError:
            return list(map(_compile, paths))
        else:
            with futures.ThreadPoolExecutor(max_workers=multiprocessing.cpu_count()) as executor:
                return list(executor.map(_compile, paths))

class CompilerBase(object):
    def __init__(self, verbose, storage):
        self.verbose = verbose
        self.storage = storage

    def match_file(self, filename):
        raise NotImplementedError

    def compile_file(self, infile, outfile, outdated=False, force=False):
        raise NotImplementedError

    def save_file(self, path, content):
        return self.storage.save(path, ContentFile(smart_bytes(content)))

    def read_file(self, path):
        file = self.storage.open(path, 'rb')
        content = file.read()
        file.close()
        return content

    def output_path(self, path, extension):
        path = os.path.splitext(path)
        return '.'.join((path[0], extension))

    def is_outdated(self, infile, outfile):
        if not os.path.exists(outfile):
            return True

        try:
            return os.path.getmtime(infile) > os.path.getmtime(outfile)
        except OSError:
            return True

class SubProcessCompiler(CompilerBase):
    def execute_command(self, command, cwd=None, stdout_captured=None):
        """Execute a command at cwd, saving its normal output at
        stdout_captured. Errors, defined as nonzero return code or a failure
        to start execution, will raise a CompilerError exception with a
        description of the cause. They do not write output.

        This is file-system safe (any valid file names are allowed, even with
        spaces or crazy characters) and OS agnostic (existing and future OSes
        that Python supports should already work).

        The only thing weird here is that any incoming command arg item may
        itself be a tuple. This allows compiler implementations to look clean
        while supporting historical string config settings and maintaining
        backwards compatibility. Thus, we flatten one layer deep.
         ((env, foocomp), infile, (-arg,)) -> (env, foocomp, infile, -arg)
        """
        argument_list = []
        for flattening_arg in command:
            if isinstance(flattening_arg, string_types):
                argument_list.append(flattening_arg)
            else:
                argument_list.extend(flattening_arg)

        # The first element in argument_list is the program that will be executed; if it is '', then
        # a PermissionError will be raised. Thus empty arguments are filtered out from argument_list
        argument_list = filter(None, argument_list)
        stdout = None
        try:
            # We always catch stdout in a file, but we may not have a use for it.
            temp_file_container = cwd or os.path.dirname(stdout_captured or "") or os.getcwd()
            with NamedTemporaryFile(delete=False, dir=temp_file_container) as stdout:
                compiling = subprocess.Popen(argument_list, cwd=cwd,
                                             stdout=stdout,
                                             stderr=subprocess.PIPE)
                _, stderr = compiling.communicate()
                set_std_streams_blocking()

            if compiling.returncode != 0:
                stdout_captured = None  # Don't save erroneous result.
                raise CompilerError(
                    "{0!r} exit code {1}\n{2}".format(argument_list, compiling.returncode, stderr),
                    command=argument_list,
                    error_output=stderr)

            # User wants to see everything that happened.
            if self.verbose:
                with open(stdout.name) as out:
                    print(out.read())
                print(stderr)
        except OSError as e:
            stdout_captured = None  # Don't save erroneous result.
            raise CompilerError(e, command=argument_list,
                                error_output=text_type(e))
        finally:
            # Decide what to do with captured stdout.
            if stdout:
                if stdout_captured:
                    output_filename = os.path.join(cwd or os.curdir, stdout_captured)
                    shutil.move(stdout.name, output_filename)
                    os.chmod(output_filename, 0644)
                else:
                    os.remove(stdout.name)
<commit_msg>Use decimal representation in chmod mask to support both python 2 and 3<commit_after>from __future__ import unicode_literals

import os
import shutil
import subprocess
from tempfile import NamedTemporaryFile

from django.contrib.staticfiles import finders
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.files.base import ContentFile
from django.utils.encoding import smart_bytes
from django.utils.six import string_types, text_type

from pipeline.conf import settings
from pipeline.exceptions import CompilerError
from pipeline.utils import to_class, set_std_streams_blocking

class Compiler(object):
    def __init__(self, storage=None, verbose=False):
        if storage is None:
            storage = staticfiles_storage
        self.storage = storage
        self.verbose = verbose

    @property
    def compilers(self):
        return [to_class(compiler) for compiler in settings.COMPILERS]

    def compile(self, paths, compiler_options={}, force=False):
        def _compile(input_path):
            for compiler in self.compilers:
                compiler = compiler(verbose=self.verbose, storage=self.storage)
                if compiler.match_file(input_path):
                    try:
                        infile = self.storage.path(input_path)
                    except NotImplementedError:
                        infile = finders.find(input_path)
                    outfile = compiler.output_path(infile, compiler.output_extension)
                    outdated = compiler.is_outdated(infile, outfile)
                    compiler.compile_file(infile, outfile,
                                          outdated=outdated, force=force,
                                          **compiler_options)

                    return compiler.output_path(input_path, compiler.output_extension)
            else:
                return input_path

        try:
            import multiprocessing
            from concurrent import futures
        except ImportError:
            return list(map(_compile, paths))
        else:
            with futures.ThreadPoolExecutor(max_workers=multiprocessing.cpu_count()) as executor:
                return list(executor.map(_compile, paths))

class CompilerBase(object):
    def __init__(self, verbose, storage):
        self.verbose = verbose
        self.storage = storage

    def match_file(self, filename):
        raise NotImplementedError

    def compile_file(self, infile, outfile, outdated=False, force=False):
        raise NotImplementedError

    def save_file(self, path, content):
        return self.storage.save(path, ContentFile(smart_bytes(content)))

    def read_file(self, path):
        file = self.storage.open(path, 'rb')
        content = file.read()
        file.close()
        return content

    def output_path(self, path, extension):
        path = os.path.splitext(path)
        return '.'.join((path[0], extension))

    def is_outdated(self, infile, outfile):
        if not os.path.exists(outfile):
            return True

        try:
            return os.path.getmtime(infile) > os.path.getmtime(outfile)
        except OSError:
            return True

class SubProcessCompiler(CompilerBase):
    def execute_command(self, command, cwd=None, stdout_captured=None):
        """Execute a command at cwd, saving its normal output at
        stdout_captured. Errors, defined as nonzero return code or a failure
        to start execution, will raise a CompilerError exception with a
        description of the cause. They do not write output.

        This is file-system safe (any valid file names are allowed, even with
        spaces or crazy characters) and OS agnostic (existing and future OSes
        that Python supports should already work).

        The only thing weird here is that any incoming command arg item may
        itself be a tuple. This allows compiler implementations to look clean
        while supporting historical string config settings and maintaining
        backwards compatibility. Thus, we flatten one layer deep.
         ((env, foocomp), infile, (-arg,)) -> (env, foocomp, infile, -arg)
        """
        argument_list = []
        for flattening_arg in command:
            if isinstance(flattening_arg, string_types):
                argument_list.append(flattening_arg)
            else:
                argument_list.extend(flattening_arg)

        # The first element in argument_list is the program that will be executed; if it is '', then
        # a PermissionError will be raised. Thus empty arguments are filtered out from argument_list
        argument_list = filter(None, argument_list)
        stdout = None
        try:
            # We always catch stdout in a file, but we may not have a use for it.
            temp_file_container = cwd or os.path.dirname(stdout_captured or "") or os.getcwd()
            with NamedTemporaryFile(delete=False, dir=temp_file_container) as stdout:
                compiling = subprocess.Popen(argument_list, cwd=cwd,
                                             stdout=stdout,
                                             stderr=subprocess.PIPE)
                _, stderr = compiling.communicate()
                set_std_streams_blocking()

            if compiling.returncode != 0:
                stdout_captured = None  # Don't save erroneous result.
                raise CompilerError(
                    "{0!r} exit code {1}\n{2}".format(argument_list, compiling.returncode, stderr),
                    command=argument_list,
                    error_output=stderr)

            # User wants to see everything that happened.
            if self.verbose:
                with open(stdout.name) as out:
                    print(out.read())
                print(stderr)
        except OSError as e:
            stdout_captured = None  # Don't save erroneous result.
            raise CompilerError(e, command=argument_list,
                                error_output=text_type(e))
        finally:
            # Decide what to do with captured stdout.
            if stdout:
                if stdout_captured:
                    output_filename = os.path.join(cwd or os.curdir, stdout_captured)
                    shutil.move(stdout.name, output_filename)
                    os.chmod(output_filename, 420) # 644 in decimal
                else:
                    os.remove(stdout.name)
<|endoftext|>


<commit_before>"""
get_data.py: functions to download data for myconnectome project from Amazon S3
Options:
    --all: get all basic data needed for analysis
    --rawfunc: get raw fMRI data
    --diffusion: get raw diffusion data
    --anatomy: get raw anatomical data
    -h: print this help message
"""

import os,getopt,sys
import boto
import tarfile

basedir=os.environ['MYCONNECTOME_DIR']
try:
    assert os.path.exists(basedir)
except:
    print 'creating base directory:',basedir
    os.mkdir(basedir)

bucket_name = 'openfmri'

def get_file_from_s3(fname,outfile,logfile=None):

    # connect to the bucket
    conn = boto.connect_s3(anon=True)
    bucket = conn.get_bucket(bucket_name)
    k=boto.s3.key.Key(bucket=bucket,name=fname)
    print fname
    print outfile
    k.get_contents_to_file(open(outfile,'wb'))
    if logfile:
        open(logfile,'a').write('%s\n'%outfile)

def extract_tarball(tar_url, extract_path='.'):
    """ from http://stackoverflow.com/questions/6058786/i-want-to-extract-a-tgz-file-and-extract-any-subdirectories-that-have-files-tha"""

    print tar_url
    tar = tarfile.open(tar_url, 'r')
    for item in tar:
        tar.extract(item, extract_path)
        if item.name.find(".tgz") != -1 or item.name.find(".tar") != -1:
            extract_tarball(item.name, "./" + item.name[:item.name.rfind('/')])

def get_s3_directory(dirname,outputdir=None,verbose=True,filestem=None,logfile=None):
    conn = boto.connect_s3()
    bucket = conn.get_bucket(bucket_name)
    if not outputdir:
        outputdir=os.path.join(basedir,dirname)
    if not os.path.exists(outputdir):
        os.makedirs(outputdir)
    for f in bucket.list('ds031/'+dirname):
        if f.name=='ds031/'+dirname+'/':
            continue
        if filestem and not f.name.find(filestem)==0:
            continue
        try:
            if verbose:
                print 'downloading',f.name
            get_file_from_s3(f.name,os.path.join(outputdir,os.path.basename(f.name)),logfile=logfile)
        except:
            print 'problem getting',f.name

def get_all_data(overwrite=False,logfile=None):
    """
    grab all necessary data from S3
    note that right now, it doesn't check to make sure all files are there, just the directory
    """

    datadirs={'parcellation':'parcellation',
              'rsfmri/combined_data_scrubbed':'combined_data_scrubbed',
              'fsaverage_LR32k':'fsaverage_LR32k',
              'aseg':'aseg',
              'rsfmri/tmasks':'tmasks',
              'surface_taskdata':'surface_taskdata'}
    files={'ds031/rsfmri/subcodes.txt':'subcodes.txt',
           'ds031/rsfmri/daycodes.txt':'daycodes.txt',
           'ds031/parcellation/module_names.txt':'parcellation/module_names.txt'}

    for k in datadirs:
        outdir=os.path.join(basedir,datadirs[k])
        if not os.path.exists(outdir) or overwrite:
            get_s3_directory(k,outdir,logfile=logfile)
    for k in files:
        outfile=os.path.join(basedir,files[k])
        if not os.path.exists(outfile) or overwrite:
            get_file_from_s3(k,outfile,logfile=logfile)

def get_raw_func_data(overwrite=False):
    """
    get raw functional data from S3
    """
    outdir=os.path.join(basedir,'rsfmri/raw_nifti')
    print 'putting data in',outdir
    if not os.path.exists(outdir) or overwrite:
            get_s3_directory('rsfmri/raw_nifti',outdir)
    else:
        print 'raw_nifti dir already exists - use -o to overwrite'

def usage():
    """Print the docstring and exit with error."""
    sys.stdout.write(__doc__)
    sys.exit(2)

def main(argv):
    overwrite=False
    try:
        opts, args = getopt.getopt(argv,"ao",['all','rawfunc','rawdiffusion','anatomy-dicom','anatomy'])
    except getopt.GetoptError:
        usage()
    if len(opts)==0:
        usage()
    data_to_get=[]
    for opt, arg in opts:
        if opt == '-h':
            usage()
        if opt == '-o':
            overwrite=True
            print 'overwriting older data'
        if opt == '--all':
            data_to_get.append('all')
        if opt == '--rawfunc':
            data_to_get.append('rawfunc')
        if opt == '--anatomy-dicom':
            data_to_get.append('anatomy-dicom')
        if opt == '--anatomy':
            data_to_get.append('anatomy')
        if opt == '--rawdiffusion':
            data_to_get.append('raw_diffusion')

    logdir=os.path.join(basedir,'logs')
    if not os.path.exists(logdir):
        os.mkdir(logdir)
    logfile=os.path.join(logdir,'s3_downloads.log')

    if 'all' in data_to_get:
        print 'getting data for main analysis...'
        get_all_data(logfile=logfile)
    if 'rawfunc' in data_to_get:
        print 'getting raw functional data - will take a while...'
        get_rawfunc_data(overwrite)
    if 'anatomy-dicom' in data_to_get:
        print 'getting anatomy dicoms'
        if not os.path.exists(os.path.join(basedir,'anatomy')):
            os.mkdir(os.path.join(basedir,'anatomy'))
        if not os.path.exists(os.path.join(basedir,'anatomy/anatomy_dicoms.tgz')) or overwrite:

            get_file_from_s3('ds031/anatomy/anatomy_dicoms.tgz',os.path.join(basedir,'anatomy/anatomy_dicoms.tgz'),logfile=logfile)
    if 'anatomy' in data_to_get:
        print 'getting anatomical images'
        if not os.path.exists(os.path.join(basedir,'anatomy')):
            os.mkdir(os.path.join(basedir,'anatomy'))
        if not os.path.exists(os.path.join(basedir,'anatomy/t1w')) or overwrite:
            get_s3_directory('anatomy/t1w',os.path.join(basedir,'anatomy/t1w'),logfile=logfile)
        if not os.path.exists(os.path.join(basedir,'anatomy/t2w')) or overwrite:
            get_s3_directory('anatomy/t2w',os.path.join(basedir,'anatomy/t2w'),logfile=logfile)
    if 'rawdiffusion' in data_to_get:
        print 'not yet implemented'

if __name__ == "__main__":
   main(sys.argv[1:])
<commit_msg>added anon for dirs<commit_after>"""
get_data.py: functions to download data for myconnectome project from Amazon S3
Options:
    --all: get all basic data needed for analysis
    --rawfunc: get raw fMRI data
    --diffusion: get raw diffusion data
    --anatomy: get raw anatomical data
    -h: print this help message
"""

import os,getopt,sys
import boto
import tarfile

basedir=os.environ['MYCONNECTOME_DIR']
try:
    assert os.path.exists(basedir)
except:
    print 'creating base directory:',basedir
    os.mkdir(basedir)

bucket_name = 'openfmri'

def get_file_from_s3(fname,outfile,logfile=None):

    # connect to the bucket
    conn = boto.connect_s3(anon=True)
    bucket = conn.get_bucket(bucket_name)
    k=boto.s3.key.Key(bucket=bucket,name=fname)
    print fname
    print outfile
    k.get_contents_to_file(open(outfile,'wb'))
    if logfile:
        open(logfile,'a').write('%s\n'%outfile)

def extract_tarball(tar_url, extract_path='.'):
    """ from http://stackoverflow.com/questions/6058786/i-want-to-extract-a-tgz-file-and-extract-any-subdirectories-that-have-files-tha"""

    print tar_url
    tar = tarfile.open(tar_url, 'r')
    for item in tar:
        tar.extract(item, extract_path)
        if item.name.find(".tgz") != -1 or item.name.find(".tar") != -1:
            extract_tarball(item.name, "./" + item.name[:item.name.rfind('/')])

def get_s3_directory(dirname,outputdir=None,verbose=True,filestem=None,logfile=None):
    conn = boto.connect_s3(anon=True)
    bucket = conn.get_bucket(bucket_name)
    if not outputdir:
        outputdir=os.path.join(basedir,dirname)
    if not os.path.exists(outputdir):
        os.makedirs(outputdir)
    for f in bucket.list('ds031/'+dirname):
        if f.name=='ds031/'+dirname+'/':
            continue
        if filestem and not f.name.find(filestem)==0:
            continue
        try:
            if verbose:
                print 'downloading',f.name
            get_file_from_s3(f.name,os.path.join(outputdir,os.path.basename(f.name)),logfile=logfile)
        except:
            print 'problem getting',f.name

def get_all_data(overwrite=False,logfile=None):
    """
    grab all necessary data from S3
    note that right now, it doesn't check to make sure all files are there, just the directory
    """

    datadirs={'parcellation':'parcellation',
              'rsfmri/combined_data_scrubbed':'combined_data_scrubbed',
              'fsaverage_LR32k':'fsaverage_LR32k',
              'aseg':'aseg',
              'rsfmri/tmasks':'tmasks',
              'surface_taskdata':'surface_taskdata'}
    files={'ds031/rsfmri/subcodes.txt':'subcodes.txt',
           'ds031/rsfmri/daycodes.txt':'daycodes.txt',
           'ds031/parcellation/module_names.txt':'parcellation/module_names.txt'}

    for k in datadirs:
        outdir=os.path.join(basedir,datadirs[k])
        if not os.path.exists(outdir) or overwrite:
            get_s3_directory(k,outdir,logfile=logfile)
    for k in files:
        outfile=os.path.join(basedir,files[k])
        if not os.path.exists(outfile) or overwrite:
            get_file_from_s3(k,outfile,logfile=logfile)

def get_raw_func_data(overwrite=False):
    """
    get raw functional data from S3
    """
    outdir=os.path.join(basedir,'rsfmri/raw_nifti')
    print 'putting data in',outdir
    if not os.path.exists(outdir) or overwrite:
            get_s3_directory('rsfmri/raw_nifti',outdir)
    else:
        print 'raw_nifti dir already exists - use -o to overwrite'

def usage():
    """Print the docstring and exit with error."""
    sys.stdout.write(__doc__)
    sys.exit(2)

def main(argv):
    overwrite=False
    try:
        opts, args = getopt.getopt(argv,"ao",['all','rawfunc','rawdiffusion','anatomy-dicom','anatomy'])
    except getopt.GetoptError:
        usage()
    if len(opts)==0:
        usage()
    data_to_get=[]
    for opt, arg in opts:
        if opt == '-h':
            usage()
        if opt == '-o':
            overwrite=True
            print 'overwriting older data'
        if opt == '--all':
            data_to_get.append('all')
        if opt == '--rawfunc':
            data_to_get.append('rawfunc')
        if opt == '--anatomy-dicom':
            data_to_get.append('anatomy-dicom')
        if opt == '--anatomy':
            data_to_get.append('anatomy')
        if opt == '--rawdiffusion':
            data_to_get.append('raw_diffusion')

    logdir=os.path.join(basedir,'logs')
    if not os.path.exists(logdir):
        os.mkdir(logdir)
    logfile=os.path.join(logdir,'s3_downloads.log')

    if 'all' in data_to_get:
        print 'getting data for main analysis...'
        get_all_data(logfile=logfile)
    if 'rawfunc' in data_to_get:
        print 'getting raw functional data - will take a while...'
        get_rawfunc_data(overwrite)
    if 'anatomy-dicom' in data_to_get:
        print 'getting anatomy dicoms'
        if not os.path.exists(os.path.join(basedir,'anatomy')):
            os.mkdir(os.path.join(basedir,'anatomy'))
        if not os.path.exists(os.path.join(basedir,'anatomy/anatomy_dicoms.tgz')) or overwrite:

            get_file_from_s3('ds031/anatomy/anatomy_dicoms.tgz',os.path.join(basedir,'anatomy/anatomy_dicoms.tgz'),logfile=logfile)
    if 'anatomy' in data_to_get:
        print 'getting anatomical images'
        if not os.path.exists(os.path.join(basedir,'anatomy')):
            os.mkdir(os.path.join(basedir,'anatomy'))
        if not os.path.exists(os.path.join(basedir,'anatomy/t1w')) or overwrite:
            get_s3_directory('anatomy/t1w',os.path.join(basedir,'anatomy/t1w'),logfile=logfile)
        if not os.path.exists(os.path.join(basedir,'anatomy/t2w')) or overwrite:
            get_s3_directory('anatomy/t2w',os.path.join(basedir,'anatomy/t2w'),logfile=logfile)
    if 'rawdiffusion' in data_to_get:
        print 'not yet implemented'

if __name__ == "__main__":
   main(sys.argv[1:])
<|endoftext|>


<commit_before>from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout, QLineEdit,
                             QPushButton, QAbstractItemView, QComboBox)
from PyQt5.QtGui import QFont, QStandardItem, QBrush

from electrum.util import bh2u, NotEnoughFunds, NoDynamicFeeEstimates
from electrum.i18n import _
from electrum.lnchannel import AbstractChannel, PeerState
from electrum.wallet import Abstract_Wallet
from electrum.lnutil import LOCAL, REMOTE, format_short_channel_id, LN_MAX_FUNDING_SAT
from electrum.lnworker import LNWallet

from .util import (MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton,
                   EnterButton, WaitingDialog, MONOSPACE_FONT, ColorScheme)
from .amountedit import BTCAmountEdit, FreezableLineEdit
from .util import WWLabel
from .fee_slider import FeeSlider, FeeComboBox

import asyncio
from .util import read_QIcon

class SwapDialog(WindowModalDialog):

    def __init__(self, window):
        WindowModalDialog.__init__(self, window, _('Submarine Swap'))
        self.window = window
        self.config = window.config
        self.swap_manager = self.window.wallet.lnworker.swap_manager
        self.network = window.network
        vbox = QVBoxLayout(self)
        vbox.addWidget(WWLabel('Swap lightning funds for on-chain funds if you need to increase your receiving capacity. This service is powered by the Boltz backend.'))
        self.send_amount_e = BTCAmountEdit(self.window.get_decimal_point)
        self.recv_amount_e = BTCAmountEdit(self.window.get_decimal_point)
        self.send_button = QPushButton('')
        self.recv_button = QPushButton('')
        self.send_follows = False
        self.is_reverse = True
        self.send_amount_e.follows = False
        self.recv_amount_e.follows = False
        self.send_button.clicked.connect(self.toggle_direction)
        self.recv_button.clicked.connect(self.toggle_direction)
        self.send_amount_e.textChanged.connect(self.on_send_edited)
        self.recv_amount_e.textChanged.connect(self.on_recv_edited)
        fee_slider = FeeSlider(self.window, self.config, self.fee_slider_callback)
        fee_combo = FeeComboBox(fee_slider)
        fee_slider.update()
        self.fee_label = QLabel()
        self.percentage_label = QLabel()
        h = QGridLayout()
        h.addWidget(QLabel(_('You send')+':'), 2, 0)
        h.addWidget(self.send_amount_e, 2, 1)
        h.addWidget(self.send_button, 2, 2)
        h.addWidget(QLabel(_('You receive')+':'), 3, 0)
        h.addWidget(self.recv_amount_e, 3, 1)
        h.addWidget(self.recv_button, 3, 2)
        h.addWidget(QLabel(_('Swap fee')+':'), 4, 0)
        h.addWidget(self.percentage_label, 4, 1)
        h.addWidget(QLabel(_('Mining fees')+':'), 5, 0)
        h.addWidget(self.fee_label, 5, 1)
        h.addWidget(fee_slider, 6, 1)
        h.addWidget(fee_combo, 6, 2)
        vbox.addLayout(h)
        vbox.addStretch(1)
        ok_button = OkButton(self)
        ok_button.setDefault(True)
        vbox.addLayout(Buttons(CancelButton(self), ok_button))
        self.update()

    def fee_slider_callback(self, dyn, pos, fee_rate):
        if dyn:
            if self.config.use_mempool_fees():
                self.config.set_key('depth_level', pos, False)
            else:
                self.config.set_key('fee_level', pos, False)
        else:
            self.config.set_key('fee_per_kb', fee_rate, False)
        if self.send_follows:
            self.on_recv_edited()
        else:
            self.on_send_edited()
        self.update()

    def toggle_direction(self):
        self.is_reverse = not self.is_reverse
        self.send_amount_e.setAmount(None)
        self.recv_amount_e.setAmount(None)
        self.update()

    def on_send_edited(self):
        if self.send_amount_e.follows:
            return
        self.send_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
        amount = self.send_amount_e.get_amount()
        self.recv_amount_e.follows = True
        self.recv_amount_e.setAmount(self.swap_manager.get_recv_amount(amount, self.is_reverse))
        self.recv_amount_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
        self.recv_amount_e.follows = False
        self.send_follows = False

    def on_recv_edited(self):
        if self.recv_amount_e.follows:
            return
        self.recv_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
        amount = self.recv_amount_e.get_amount()
        self.send_amount_e.follows = True
        self.send_amount_e.setAmount(self.swap_manager.get_send_amount(amount, self.is_reverse))
        self.send_amount_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
        self.send_amount_e.follows = False
        self.send_follows = True

    def update(self):
        sm = self.swap_manager
        self.send_button.setIcon(read_QIcon("lightning.png" if self.is_reverse else "bitcoin.png"))
        self.recv_button.setIcon(read_QIcon("lightning.png" if not self.is_reverse else "bitcoin.png"))
        fee = sm.lockup_fee + sm.get_claim_fee() if self.is_reverse else sm.normal_fee
        self.fee_label.setText(self.window.format_amount(fee) + ' ' + self.window.base_unit())
        self.percentage_label.setText('%.2f'%sm.percentage + '%')

    def run(self):
        self.window.run_coroutine_from_thread(self.swap_manager.get_pairs(), lambda x: self.update())
        if not self.exec_():
            return
        if self.is_reverse:
            lightning_amount = self.send_amount_e.get_amount()
            onchain_amount = self.recv_amount_e.get_amount() + self.swap_manager.get_claim_fee()
            coro = self.swap_manager.reverse_swap(lightning_amount, onchain_amount)
            self.window.run_coroutine_from_thread(coro)
        else:
            lightning_amount = self.recv_amount_e.get_amount()
            onchain_amount = self.send_amount_e.get_amount()
            self.window.protect(self.do_normal_swap, (lightning_amount, onchain_amount))

    def do_normal_swap(self, lightning_amount, onchain_amount, password):
        coro = self.swap_manager.normal_swap(lightning_amount, onchain_amount, password)
        self.window.run_coroutine_from_thread(coro)
<commit_msg>swaps: add safeguards to gui<commit_after>from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout, QLineEdit,
                             QPushButton, QAbstractItemView, QComboBox)
from PyQt5.QtGui import QFont, QStandardItem, QBrush

from electrum.util import bh2u, NotEnoughFunds, NoDynamicFeeEstimates
from electrum.i18n import _
from electrum.lnchannel import AbstractChannel, PeerState
from electrum.wallet import Abstract_Wallet
from electrum.lnutil import LOCAL, REMOTE, format_short_channel_id, LN_MAX_FUNDING_SAT
from electrum.lnworker import LNWallet

from .util import (MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton,
                   EnterButton, WaitingDialog, MONOSPACE_FONT, ColorScheme)
from .amountedit import BTCAmountEdit, FreezableLineEdit
from .util import WWLabel
from .fee_slider import FeeSlider, FeeComboBox

import asyncio
from .util import read_QIcon

CANNOT_RECEIVE_WARNING = """
The requested amount is higher than what you can receive in your currently open channels.
If you continue, your funds will be locked until the remote server can find a path to pay you.
If the swap cannot be performed after 24h, you will be refunded.
Do you want to continue?
"""

class SwapDialog(WindowModalDialog):

    def __init__(self, window):
        WindowModalDialog.__init__(self, window, _('Submarine Swap'))
        self.window = window
        self.config = window.config
        self.lnworker = self.window.wallet.lnworker
        self.swap_manager = self.lnworker.swap_manager
        self.network = window.network
        vbox = QVBoxLayout(self)
        vbox.addWidget(WWLabel('Swap lightning funds for on-chain funds if you need to increase your receiving capacity. This service is powered by the Boltz backend.'))
        self.send_amount_e = BTCAmountEdit(self.window.get_decimal_point)
        self.recv_amount_e = BTCAmountEdit(self.window.get_decimal_point)
        self.send_button = QPushButton('')
        self.recv_button = QPushButton('')
        self.send_follows = False
        self.is_reverse = True
        self.send_amount_e.follows = False
        self.recv_amount_e.follows = False
        self.send_button.clicked.connect(self.toggle_direction)
        self.recv_button.clicked.connect(self.toggle_direction)
        self.send_amount_e.textChanged.connect(self.on_send_edited)
        self.recv_amount_e.textChanged.connect(self.on_recv_edited)
        fee_slider = FeeSlider(self.window, self.config, self.fee_slider_callback)
        fee_combo = FeeComboBox(fee_slider)
        fee_slider.update()
        self.fee_label = QLabel()
        self.percentage_label = QLabel()
        h = QGridLayout()
        h.addWidget(QLabel(_('You send')+':'), 2, 0)
        h.addWidget(self.send_amount_e, 2, 1)
        h.addWidget(self.send_button, 2, 2)
        h.addWidget(QLabel(_('You receive')+':'), 3, 0)
        h.addWidget(self.recv_amount_e, 3, 1)
        h.addWidget(self.recv_button, 3, 2)
        h.addWidget(QLabel(_('Swap fee')+':'), 4, 0)
        h.addWidget(self.percentage_label, 4, 1)
        h.addWidget(QLabel(_('Mining fees')+':'), 5, 0)
        h.addWidget(self.fee_label, 5, 1)
        h.addWidget(fee_slider, 6, 1)
        h.addWidget(fee_combo, 6, 2)
        vbox.addLayout(h)
        vbox.addStretch(1)
        ok_button = OkButton(self)
        ok_button.setDefault(True)
        vbox.addLayout(Buttons(CancelButton(self), ok_button))
        self.update()

    def fee_slider_callback(self, dyn, pos, fee_rate):
        if dyn:
            if self.config.use_mempool_fees():
                self.config.set_key('depth_level', pos, False)
            else:
                self.config.set_key('fee_level', pos, False)
        else:
            self.config.set_key('fee_per_kb', fee_rate, False)
        if self.send_follows:
            self.on_recv_edited()
        else:
            self.on_send_edited()
        self.update()

    def toggle_direction(self):
        self.is_reverse = not self.is_reverse
        self.send_amount_e.setAmount(None)
        self.recv_amount_e.setAmount(None)
        self.update()

    def on_send_edited(self):
        if self.send_amount_e.follows:
            return
        self.send_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
        send_amount = self.send_amount_e.get_amount()
        recv_amount = self.swap_manager.get_recv_amount(send_amount, self.is_reverse)
        if self.is_reverse and send_amount and send_amount > self.lnworker.num_sats_can_send():
            recv_amount = None
        self.recv_amount_e.follows = True
        self.recv_amount_e.setAmount(recv_amount)
        self.recv_amount_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
        self.recv_amount_e.follows = False
        self.send_follows = False

    def on_recv_edited(self):
        if self.recv_amount_e.follows:
            return
        self.recv_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
        recv_amount = self.recv_amount_e.get_amount()
        send_amount = self.swap_manager.get_send_amount(recv_amount, self.is_reverse)
        if self.is_reverse and send_amount and send_amount > self.lnworker.num_sats_can_send():
            send_amount = None
        self.send_amount_e.follows = True
        self.send_amount_e.setAmount(send_amount)
        self.send_amount_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
        self.send_amount_e.follows = False
        self.send_follows = True

    def update(self):
        sm = self.swap_manager
        self.send_button.setIcon(read_QIcon("lightning.png" if self.is_reverse else "bitcoin.png"))
        self.recv_button.setIcon(read_QIcon("lightning.png" if not self.is_reverse else "bitcoin.png"))
        fee = sm.lockup_fee + sm.get_claim_fee() if self.is_reverse else sm.normal_fee
        self.fee_label.setText(self.window.format_amount(fee) + ' ' + self.window.base_unit())
        self.percentage_label.setText('%.2f'%sm.percentage + '%')

    def run(self):
        self.window.run_coroutine_from_thread(self.swap_manager.get_pairs(), lambda x: self.update())
        if not self.exec_():
            return
        if self.is_reverse:
            lightning_amount = self.send_amount_e.get_amount()
            onchain_amount = self.recv_amount_e.get_amount()
            if lightning_amount is None or onchain_amount is None:
                return
            coro = self.swap_manager.reverse_swap(lightning_amount, onchain_amount + self.swap_manager.get_claim_fee())
            self.window.run_coroutine_from_thread(coro)
        else:
            lightning_amount = self.recv_amount_e.get_amount()
            onchain_amount = self.send_amount_e.get_amount()
            if lightning_amount is None or onchain_amount is None:
                return
            if lightning_amount > self.lnworker.num_sats_can_receive():
                if not self.window.question(CANNOT_RECEIVE_WARNING):
                    return
            self.window.protect(self.do_normal_swap, (lightning_amount, onchain_amount))

    def do_normal_swap(self, lightning_amount, onchain_amount, password):
        coro = self.swap_manager.normal_swap(lightning_amount, onchain_amount, password)
        self.window.run_coroutine_from_thread(coro)
<|endoftext|>


<commit_before>from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout, QLineEdit,
                             QPushButton, QAbstractItemView, QComboBox)
from PyQt5.QtGui import QFont, QStandardItem, QBrush

from electrum.util import bh2u, NotEnoughFunds, NoDynamicFeeEstimates
from electrum.i18n import _
from electrum.lnchannel import AbstractChannel, PeerState
from electrum.wallet import Abstract_Wallet
from electrum.lnutil import LOCAL, REMOTE, format_short_channel_id, LN_MAX_FUNDING_SAT
from electrum.lnworker import LNWallet

from .util import (MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton,
                   EnterButton, WaitingDialog, MONOSPACE_FONT, ColorScheme)
from .amountedit import BTCAmountEdit, FreezableLineEdit
from .util import WWLabel
from .fee_slider import FeeSlider

import asyncio
from .util import read_QIcon

class SwapDialog(WindowModalDialog):

    def __init__(self, window):
        WindowModalDialog.__init__(self, window, _('Submarine Swap'))
        self.window = window
        self.config = window.config
        self.swap_manager = self.window.wallet.lnworker.swap_manager
        self.network = window.network
        self.normal_fee = 0
        self.lockup_fee = 0
        self.claim_fee = self.swap_manager.get_tx_fee()
        self.percentage = 0
        self.min_amount = 0
        self.max_amount = 0
        vbox = QVBoxLayout(self)
        vbox.addWidget(WWLabel('Swap lightning funds for on-chain funds if you need to increase your receiving capacity. This service is powered by the Boltz backend.'))
        self.send_amount_e = BTCAmountEdit(self.window.get_decimal_point)
        self.recv_amount_e = BTCAmountEdit(self.window.get_decimal_point)
        self.send_button = QPushButton('')
        self.recv_button = QPushButton('')
        self.send_follows = False
        self.is_reverse = True
        self.send_amount_e.follows = False
        self.recv_amount_e.follows = False
        self.send_button.clicked.connect(self.toggle_direction)
        self.recv_button.clicked.connect(self.toggle_direction)
        self.send_amount_e.textChanged.connect(self.on_send_edited)
        self.recv_amount_e.textChanged.connect(self.on_recv_edited)
        h = QGridLayout()
        h.addWidget(QLabel(_('You send')+':'), 2, 0)
        h.addWidget(self.send_amount_e, 2, 1)
        h.addWidget(self.send_button, 2, 2)
        h.addWidget(QLabel(_('You receive')+':'), 3, 0)
        h.addWidget(self.recv_amount_e, 3, 1)
        h.addWidget(self.recv_button, 3, 2)
        self.min_label = QLabel('')
        self.max_label = QLabel('')
        self.fee_label = QLabel()
        self.percentage_label = QLabel()
        h.addWidget(QLabel(_('Min amount')+':'), 4, 0)
        h.addWidget(self.min_label, 4, 1)
        h.addWidget(QLabel(_('Max amount')+':'), 5, 0)
        h.addWidget(self.max_label, 5, 1)
        h.addWidget(QLabel(_('Mining fee')+':'), 4, 2)
        h.addWidget(self.fee_label, 4, 3)
        h.addWidget(QLabel(_('Swap fee')+':'), 5, 2)
        h.addWidget(self.percentage_label, 5, 3)
        vbox.addLayout(h)
        fee_slider = FeeSlider(self.window, self.config, self.fee_slider_callback)
        fee_slider.update()
        h.addWidget(fee_slider, 6, 1)
        vbox.addStretch(1)
        ok_button = OkButton(self)
        ok_button.setDefault(True)
        vbox.addLayout(Buttons(CancelButton(self), ok_button))
        self.update()

    def fee_slider_callback(self, dyn, pos, fee_rate):
        if dyn:
            if self.config.use_mempool_fees():
                self.config.set_key('depth_level', pos, False)
            else:
                self.config.set_key('fee_level', pos, False)
        else:
            self.config.set_key('fee_per_kb', fee_rate, False)
        # read claim_fee from config
        self.claim_fee = self.swap_manager.get_tx_fee()
        if self.send_follows:
            self.on_recv_edited()
        else:
            self.on_send_edited()
        self.update()

    def toggle_direction(self):
        self.is_reverse = not self.is_reverse
        self.send_amount_e.setAmount(None)
        self.recv_amount_e.setAmount(None)
        self.update()

    def on_send_edited(self):
        if self.send_amount_e.follows:
            return
        self.send_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
        amount = self.send_amount_e.get_amount()
        self.recv_amount_e.follows = True
        self.recv_amount_e.setAmount(self.get_recv_amount(amount))
        self.recv_amount_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
        self.recv_amount_e.follows = False
        self.send_follows = False

    def on_recv_edited(self):
        if self.recv_amount_e.follows:
            return
        self.recv_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
        amount = self.recv_amount_e.get_amount()
        self.send_amount_e.follows = True
        self.send_amount_e.setAmount(self.get_send_amount(amount))
        self.send_amount_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
        self.send_amount_e.follows = False
        self.send_follows = True

    def on_pairs(self, pairs):
        fees = pairs['pairs']['BTC/BTC']['fees']
        self.percentage = fees['percentage']
        self.normal_fee = fees['minerFees']['baseAsset']['normal']
        self.lockup_fee = fees['minerFees']['baseAsset']['reverse']['lockup']
        #self.claim_fee = fees['minerFees']['baseAsset']['reverse']['claim']
        limits = pairs['pairs']['BTC/BTC']['limits']
        self.min_amount = limits['minimal']
        self.max_amount = limits['maximal']
        self.update()

    def update(self):
        self.send_button.setIcon(read_QIcon("lightning.png" if self.is_reverse else "bitcoin.png"))
        self.recv_button.setIcon(read_QIcon("lightning.png" if not self.is_reverse else "bitcoin.png"))
        fee = self.lockup_fee + self.claim_fee if self.is_reverse else self.normal_fee
        self.fee_label.setText(self.window.format_amount(fee) + ' ' + self.window.base_unit())
        self.percentage_label.setText('%.2f'%self.percentage + '%')
        self.min_label.setText(self.window.format_amount(self.min_amount) + ' ' + self.window.base_unit())
        self.max_label.setText(self.window.format_amount(self.max_amount) + ' ' + self.window.base_unit())

    def set_minimum(self):
        self.send_amount_e.setAmount(self.min_amount)

    def set_maximum(self):
        self.send_amount_e.setAmount(self.max_amount)

    def get_recv_amount(self, send_amount):
        if send_amount is None:
            return
        if send_amount < self.min_amount or send_amount > self.max_amount:
            return
        x = send_amount
        if self.is_reverse:
            x = int(x * (100 - self.percentage) / 100)
            x -= self.lockup_fee
            x -= self.claim_fee
        else:
            x -= self.normal_fee
            x = int(x * (100 - self.percentage) / 100)
        if x < 0:
            return
        return x

    def get_send_amount(self, recv_amount):
        if not recv_amount:
            return
        x = recv_amount
        if self.is_reverse:
            x += self.lockup_fee
            x += self.claim_fee
            x = int(x * 100 / (100 - self.percentage)) + 1
        else:
            x = int(x * 100 / (100 - self.percentage)) + 1
            x += self.normal_fee
        return x

    def run(self):
        self.window.run_coroutine_from_thread(self.swap_manager.get_pairs(), self.on_pairs)
        if not self.exec_():
            return
        if self.is_reverse:
            lightning_amount = self.send_amount_e.get_amount()
            onchain_amount = self.recv_amount_e.get_amount() + self.claim_fee
            coro = self.swap_manager.reverse_swap(lightning_amount, onchain_amount)
            self.window.run_coroutine_from_thread(coro)
        else:
            lightning_amount = self.recv_amount_e.get_amount()
            onchain_amount = self.send_amount_e.get_amount()
            self.window.protect(self.do_normal_swap, (lightning_amount, onchain_amount))

    def do_normal_swap(self, lightning_amount, onchain_amount, password):
        coro = self.swap_manager.normal_swap(lightning_amount, onchain_amount, password)
        self.window.run_coroutine_from_thread(coro)
<commit_msg>swaps: add fee_combo, hide min/max<commit_after>from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout, QLineEdit,
                             QPushButton, QAbstractItemView, QComboBox)
from PyQt5.QtGui import QFont, QStandardItem, QBrush

from electrum.util import bh2u, NotEnoughFunds, NoDynamicFeeEstimates
from electrum.i18n import _
from electrum.lnchannel import AbstractChannel, PeerState
from electrum.wallet import Abstract_Wallet
from electrum.lnutil import LOCAL, REMOTE, format_short_channel_id, LN_MAX_FUNDING_SAT
from electrum.lnworker import LNWallet

from .util import (MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton,
                   EnterButton, WaitingDialog, MONOSPACE_FONT, ColorScheme)
from .amountedit import BTCAmountEdit, FreezableLineEdit
from .util import WWLabel
from .fee_slider import FeeSlider, FeeComboBox

import asyncio
from .util import read_QIcon

class SwapDialog(WindowModalDialog):

    def __init__(self, window):
        WindowModalDialog.__init__(self, window, _('Submarine Swap'))
        self.window = window
        self.config = window.config
        self.swap_manager = self.window.wallet.lnworker.swap_manager
        self.network = window.network
        self.normal_fee = 0
        self.lockup_fee = 0
        self.claim_fee = self.swap_manager.get_tx_fee()
        self.percentage = 0
        self.min_amount = 0
        self.max_amount = 0
        vbox = QVBoxLayout(self)
        vbox.addWidget(WWLabel('Swap lightning funds for on-chain funds if you need to increase your receiving capacity. This service is powered by the Boltz backend.'))
        self.send_amount_e = BTCAmountEdit(self.window.get_decimal_point)
        self.recv_amount_e = BTCAmountEdit(self.window.get_decimal_point)
        self.send_button = QPushButton('')
        self.recv_button = QPushButton('')
        self.send_follows = False
        self.is_reverse = True
        self.send_amount_e.follows = False
        self.recv_amount_e.follows = False
        self.send_button.clicked.connect(self.toggle_direction)
        self.recv_button.clicked.connect(self.toggle_direction)
        self.send_amount_e.textChanged.connect(self.on_send_edited)
        self.recv_amount_e.textChanged.connect(self.on_recv_edited)
        fee_slider = FeeSlider(self.window, self.config, self.fee_slider_callback)
        fee_combo = FeeComboBox(fee_slider)
        fee_slider.update()
        self.fee_label = QLabel()
        self.percentage_label = QLabel()
        h = QGridLayout()
        h.addWidget(QLabel(_('You send')+':'), 2, 0)
        h.addWidget(self.send_amount_e, 2, 1)
        h.addWidget(self.send_button, 2, 2)
        h.addWidget(QLabel(_('You receive')+':'), 3, 0)
        h.addWidget(self.recv_amount_e, 3, 1)
        h.addWidget(self.recv_button, 3, 2)
        h.addWidget(QLabel(_('Swap fee')+':'), 4, 0)
        h.addWidget(self.percentage_label, 4, 1)
        h.addWidget(QLabel(_('Mining fees')+':'), 5, 0)
        h.addWidget(self.fee_label, 5, 1)
        h.addWidget(fee_slider, 6, 1)
        h.addWidget(fee_combo, 6, 2)
        vbox.addLayout(h)
        vbox.addStretch(1)
        ok_button = OkButton(self)
        ok_button.setDefault(True)
        vbox.addLayout(Buttons(CancelButton(self), ok_button))
        self.update()

    def fee_slider_callback(self, dyn, pos, fee_rate):
        if dyn:
            if self.config.use_mempool_fees():
                self.config.set_key('depth_level', pos, False)
            else:
                self.config.set_key('fee_level', pos, False)
        else:
            self.config.set_key('fee_per_kb', fee_rate, False)
        # read claim_fee from config
        self.claim_fee = self.swap_manager.get_tx_fee()
        if self.send_follows:
            self.on_recv_edited()
        else:
            self.on_send_edited()
        self.update()

    def toggle_direction(self):
        self.is_reverse = not self.is_reverse
        self.send_amount_e.setAmount(None)
        self.recv_amount_e.setAmount(None)
        self.update()

    def on_send_edited(self):
        if self.send_amount_e.follows:
            return
        self.send_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
        amount = self.send_amount_e.get_amount()
        self.recv_amount_e.follows = True
        self.recv_amount_e.setAmount(self.get_recv_amount(amount))
        self.recv_amount_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
        self.recv_amount_e.follows = False
        self.send_follows = False

    def on_recv_edited(self):
        if self.recv_amount_e.follows:
            return
        self.recv_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
        amount = self.recv_amount_e.get_amount()
        self.send_amount_e.follows = True
        self.send_amount_e.setAmount(self.get_send_amount(amount))
        self.send_amount_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
        self.send_amount_e.follows = False
        self.send_follows = True

    def on_pairs(self, pairs):
        fees = pairs['pairs']['BTC/BTC']['fees']
        self.percentage = fees['percentage']
        self.normal_fee = fees['minerFees']['baseAsset']['normal']
        self.lockup_fee = fees['minerFees']['baseAsset']['reverse']['lockup']
        #self.claim_fee = fees['minerFees']['baseAsset']['reverse']['claim']
        limits = pairs['pairs']['BTC/BTC']['limits']
        self.min_amount = limits['minimal']
        self.max_amount = limits['maximal']
        self.update()

    def update(self):
        self.send_button.setIcon(read_QIcon("lightning.png" if self.is_reverse else "bitcoin.png"))
        self.recv_button.setIcon(read_QIcon("lightning.png" if not self.is_reverse else "bitcoin.png"))
        fee = self.lockup_fee + self.claim_fee if self.is_reverse else self.normal_fee
        self.fee_label.setText(self.window.format_amount(fee) + ' ' + self.window.base_unit())
        self.percentage_label.setText('%.2f'%self.percentage + '%')

    def set_minimum(self):
        self.send_amount_e.setAmount(self.min_amount)

    def set_maximum(self):
        self.send_amount_e.setAmount(self.max_amount)

    def get_recv_amount(self, send_amount):
        if send_amount is None:
            return
        if send_amount < self.min_amount or send_amount > self.max_amount:
            return
        x = send_amount
        if self.is_reverse:
            x = int(x * (100 - self.percentage) / 100)
            x -= self.lockup_fee
            x -= self.claim_fee
        else:
            x -= self.normal_fee
            x = int(x * (100 - self.percentage) / 100)
        if x < 0:
            return
        return x

    def get_send_amount(self, recv_amount):
        if not recv_amount:
            return
        x = recv_amount
        if self.is_reverse:
            x += self.lockup_fee
            x += self.claim_fee
            x = int(x * 100 / (100 - self.percentage)) + 1
        else:
            x = int(x * 100 / (100 - self.percentage)) + 1
            x += self.normal_fee
        return x

    def run(self):
        self.window.run_coroutine_from_thread(self.swap_manager.get_pairs(), self.on_pairs)
        if not self.exec_():
            return
        if self.is_reverse:
            lightning_amount = self.send_amount_e.get_amount()
            onchain_amount = self.recv_amount_e.get_amount() + self.claim_fee
            coro = self.swap_manager.reverse_swap(lightning_amount, onchain_amount)
            self.window.run_coroutine_from_thread(coro)
        else:
            lightning_amount = self.recv_amount_e.get_amount()
            onchain_amount = self.send_amount_e.get_amount()
            self.window.protect(self.do_normal_swap, (lightning_amount, onchain_amount))

    def do_normal_swap(self, lightning_amount, onchain_amount, password):
        coro = self.swap_manager.normal_swap(lightning_amount, onchain_amount, password)
        self.window.run_coroutine_from_thread(coro)
<|endoftext|>


<commit_before>'''Text progress bar library for Python.

A text progress bar is typically used to display the progress of a long
running operation, providing a visual cue that processing is underway.

The ProgressBar class manages the current progress, and the format of the line
is given by a number of widgets. A widget is an object that may display
differently depending on the state of the progress bar. There are three types
of widgets:

 - a string, which always shows itself

 - a ProgressBarWidget, which may return a different value every time its
   update method is called

 - a ProgressBarWidgetHFill, which is like ProgressBarWidget, except it
   expands to fill the remaining width of the line.

The progressbar module is very easy to use, yet very powerful. It will also
automatically enable features like auto-resizing when the system supports it.
'''

import datetime

__package_name__ = 'progressbar2'
__author__ = 'Rick van Hattem'
__author_email__ = 'Wolph@Wol.ph'
__url__ = 'https://github.com/WoLpH/python-progressbar'
__date__ = str(datetime.date.today())
__version__ = '3.0.1'
<commit_msg>enabled several new widgets and python 3.3, 3.4, 3.5 support<commit_after>'''Text progress bar library for Python.

A text progress bar is typically used to display the progress of a long
running operation, providing a visual cue that processing is underway.

The ProgressBar class manages the current progress, and the format of the line
is given by a number of widgets. A widget is an object that may display
differently depending on the state of the progress bar. There are three types
of widgets:

 - a string, which always shows itself

 - a ProgressBarWidget, which may return a different value every time its
   update method is called

 - a ProgressBarWidgetHFill, which is like ProgressBarWidget, except it
   expands to fill the remaining width of the line.

The progressbar module is very easy to use, yet very powerful. It will also
automatically enable features like auto-resizing when the system supports it.
'''

import datetime

__package_name__ = 'progressbar2'
__author__ = 'Rick van Hattem'
__author_email__ = 'Wolph@Wol.ph'
__url__ = 'https://github.com/WoLpH/python-progressbar'
__date__ = str(datetime.date.today())
__version__ = '3.1.0'
<|endoftext|>