code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

"""
 autoxml is a metaclass for automatic XML translation, using
 a miniature type system. (w00t!) This is based on an excellent
 high-level XML processing prototype that Gurer prepared.

 Method names are mixedCase for compatibility with minidom,
 an old library.
"""

import formatter
import inspect
import io
# System
import locale
import re
import sys

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

# INARY
import inary.errors
import inary.sxml.xmlext as xmlext
import inary.sxml.xmlfile as xmlfile
import inary.context as ctx
import inary.util as util
import inary.oo as oo

class Error(inary.errors.Error):
    pass

# requirement specs

mandatory, optional = list(range(2))  # poor man's enum

# basic types

String = str
Text = bytes
Integer = int
Long = int
Float = float

class datatype(type):
    def __init__(cls, name, bases, dict):
        """entry point for metaclass code"""
        # standard initialization
        super(autoxml, cls).__init__(name, bases, dict)

class LocalText(dict):
    """Handles XML tags with localized text"""

    def __init__(self, tag="", req=optional):
        self.tag = tag
        self.req = req
        dict.__init__(self)

    def decode(self, node, errs, where=""):
        # flags, tag name, instance attribute
        assert self.tag != ''
        nodes = xmlext.getAllNodes(node, self.tag)
        if not nodes:
            if self.req == mandatory:
                errs.append(where + ': ' + _("At least one '{}' tag should have local text.").format(self.tag))
        else:
            for node in nodes:
                lang = xmlext.getNodeAttribute(node, 'xml:lang')
                c = xmlext.getNodeText(node)
                if not c:
                    errs.append(where + ': ' + _("'{0}' language of tag '{1}' is empty.").format(lang, self.tag))
                # FIXME: check for dups and 'en'
                if not lang:
                    lang = 'en'
                self[lang] = str(c)

    def encode(self, node, errs):
        assert self.tag != ''
        for key in list(self.keys()):
            newnode = xmlext.addNode(node, self.tag)
            xmlext.setNodeAttribute(newnode, 'xml:lang', key)
            xmlext.addText(newnode, '', self[key])

    # FIXME: maybe more appropriate for inary.util
    @staticmethod
    def get_lang():
        try:
            (lang, encoding) = locale.getlocale()
            if not lang:
                (lang, encoding) = locale.getdefaultlocale()
            if lang is None:  # stupid python means it is C locale
                return 'en'
            else:
                return lang[0:2]
        except KeyboardInterrupt:
            raise
        except Exception as e:  # FIXME: what exception could we catch here, replace with that.
            raise Error(_('LocalText: unable to get either current or default locale.'))

    def errors(self, where=str()):
        errs = []
        langs = [LocalText.get_lang(), 'en', 'tr', ]
        if list(self.keys()) and not util.any(lambda x: x in self, langs):
            errs.append(where + ': ' + _(
                "Tag should have at least the current locale, or failing that an English or Turkish version."))
        # FIXME: check if all entries are unicode
        return errs

    def format(self, f, errs):
        L = LocalText.get_lang()
        if L in self:
            f.add_flowing_data(self[L])
        elif 'en' in self:
            # fallback to English, blah
            f.add_flowing_data(self['en'])
        elif 'tr' in self:
            # fallback to Turkish
            f.add_flowing_data(self['tr'])
        else:
            errs.append(_("Tag should have at least the current locale, or failing that an English or Turkish version."))

    # FIXME: factor out these common routines
    def print_text(self, file=sys.stdout):
        w = Writer(file)  # plain text
        f = formatter.AbstractFormatter(w)
        errs = []
        self.format(f, errs)
        if errs:
            for x in errs:
                ctx.ui.warning(x)

    def __str__(self):
        L = LocalText.get_lang()
        if L in self:
            return str(self[L])
        elif 'en' in self:
            # fallback to English, blah
            return str(self['en'])
        elif 'tr' in self:
            # fallback to Turkish
            return str(self['tr'])
        else:
            return str()

class Writer(formatter.DumbWriter):
    """adds unicode support"""

    def __init__(self, file=None, maxcol=78):
        formatter.DumbWriter.__init__(self, file, maxcol)

    def send_literal_data(self, data):
        self.file.write(data.encode("utf-8"))
        i = data.rfind('\n')
        if i >= 0:
            self.col = 0
            data = data[i + 1:]
        data = data.expandtabs()
        self.col += len(data)
        self.atbreak = 0

class autoxml(oo.autosuper, oo.autoprop):
    """High-level automatic XML transformation interface for xmlfile.
    The idea is to declare a class for each XML tag. Inside the
    class the tags and attributes nested in the tag are further
    elaborated. A simple example follows:

    class Employee(metaclass=autoxml):
         t_Name = [xmlfile.Text, xmlfile.mandatory]
         a_Type = [xmlfile.Integer, xmlfile.optional]

    This class defines a tag and an attribute nested in Employee
    class. Name is a string and type is an integer, called basic
    types.
    While the tag is mandatory, the attribute may be left out.

    Other basic types supported are: xmlfile.Float, xmlfile.Double
    and (not implemented yet): xmlfile.Binary

    By default, the class name is taken as the corresponding tag,
    which may be overridden by defining a tag attribute. Thus,
    the same tag may also be written as:

    class EmployeeXML:
        ...
        tag = 'Employee'
        ...

    In addition to basic types, we allow for two kinds of complex
    types: class types and list types.

    A declared class can be nested in another class as follows

    class Position(metaclass=autoxml):
         t_Name = [xmlfile.Text, xmlfile.mandatory]
         t_Description = [xmlfile.Text, xmlfile.optional]

    which we can add to our Employee class.

    class Employee(metaclass=autoxml):
         t_Name = [xmlfile.Text, xmlfile.mandatory]
         a_Type = [xmlfile.Integer, xmlfile.optional]
         t_Position = [Position, xmlfile.mandatory]

    Note some unfortunate redundancy here with Position; this is
    justified by the implementation (kidding). Still, you might
    want to assign a different name than the class name that
    goes in there, which may be fully qualified.

    There is more! Suppose we want to define a company, with
    of course many employees.

    class Company(metaclass=autoxml):
        t_Employees = [ [Employee], xmlfile.mandatory, 'Employees/Employee']

    Logically, inside the Company/Employees tag, we will have several
    Employee tags, which are inserted to the Employees instance variable of
    Company in order of appearance. We can define lists of any other valid
    type. Here we used a list of an autoxml class defined above.

    The mandatory flag here asserts that at least one such record
    is to be found.

    You see, it works like magic, when it works of course. All of it
    done without a single brain exploding.

    """

    def __init__(cls, name, bases, dict):
        """entry point for metaclass code"""
        # print 'generating class', name

        # standard initialization
        super().__init__(name, bases, dict)

        xmlfile_support = xmlfile.XmlFile in bases

        cls.autoxml_bases = [base for base in bases if isinstance(base, autoxml)]

        # TODO: initialize class attribute __xml_tags
        # setattr(cls, 'xml_variables', [])

        # default class tag is class name
        if 'tag' not in dict:
            cls.tag = name

        # generate helper routines, for each XML component
        names = []
        inits = []
        decoders = []
        encoders = []
        errorss = []
        formatters = []

        # FIXME: What is this? Remove this crap and try to fix autoxml, if can not be fixed then
        # really throw whole autoxml to junk. But not this.

        # read declaration order from source
        # code contributed by bahadir kandemir
        try:
            fn = re.compile('\s*([tas]_[a-zA-Z]+).*').findall

            inspect.linecache.clearcache()
            lines = list(filter(fn, inspect.getsourcelines(cls)[0]))
            decl_order = [x.split()[0] for x in lines]
        except IOError:
            decl_order = list(dict.keys())

        # there should be at most one str member, and it should be
        # the first to process

        order = [x for x in decl_order if not x.startswith('s_')]

        # find string member
        str_members = [x for x in decl_order if x.startswith('s_')]
        if len(str_members) > 1:
            raise Error('Only one str member can be defined')
        elif len(str_members) == 1:
            order.insert(0, str_members[0])

        for var in order:
            if var.startswith('t_') or var.startswith('a_') or var.startswith('s_'):
                name = var[2:]
                if var.startswith('a_'):
                    x = autoxml.gen_attr_member(cls, name)
                elif var.startswith('t_'):
                    x = autoxml.gen_tag_member(cls, name)
                elif var.startswith('s_'):
                    x = autoxml.gen_str_member(cls, name)
                (name, init, decoder, encoder, errors, format_x) = x
                names.append(name)
                inits.append(init)
                decoders.append(decoder)
                encoders.append(encoder)
                errorss.append(errors)
                formatters.append(format_x)

        # generate top-level helper functions
        cls.initializers = inits

        def initialize(self, uri=None, keepDoc=False, tmpDir='/tmp',
                       **args):
            if xmlfile_support:
                if 'tag' in args:
                    xmlfile.XmlFile.__init__(self, tag=args['tag'])
                else:
                    xmlfile.XmlFile.__init__(self, tag=cls.tag)
            for base in cls.autoxml_bases:
                base.__init__(self)
            for init in inits:
                init(self)
            for x in list(args.keys()):
                setattr(self, x, args[x])
            # init hook
            if hasattr(self, 'init'):
                self.init(tag)
            if xmlfile_support and uri:
                self.read(uri, keepDoc, tmpDir)

        cls.__init__ = initialize

        cls.decoders = decoders

        def decode(self, node, errs, where=String(cls.tag)):
            for base in cls.autoxml_bases:
                base.decode(self, node, errs, where)
            for decode_member in decoders:  # self.__class__.decoders:
                decode_member(self, node, errs, where)
            if hasattr(self, 'decode_hook'):
                self.decode_hook(node, errs, where)

        cls.decode = decode

        cls.encoders = encoders

        def encode(self, node, errs):
            for base in cls.autoxml_bases:
                base.encode(self, node, errs)
            for encode_member in encoders:  # self.__class__.encoders:
                encode_member(self, node, errs)
            if hasattr(self, 'encode_hook'):
                self.encode_hook(node, errs)

        cls.encode = encode

        cls.errorss = errorss

        def errors(self, where=String(name)):
            errs = []
            for base in cls.autoxml_bases:
                errs.extend(base.errors(self, where))
            for errors in errorss:  # self.__class__.errorss:
                errs.extend(errors(self, where))
            if hasattr(self, 'errors_hook'):
                errs.extend(self.errors_hook(where))
            return errs

        cls.errors = errors

        def check(self):
            errs = self.errors()
            if errs:
                errs.append(_("autoxml.check: '{}' errors.").format(len(errs)))
                raise Error(*errs)

        cls.check = check

        cls.formatters = formatters

        def format(self, f, errs):
            for base in cls.autoxml_bases:
                base.format(self, f, errs)
            for formatter in formatters:  # self.__class__.formatters:
                formatter(self, f, errs)

        cls.format = format

        def print_text(self, file=sys.stdout):
            w = Writer(file)  # plain text
            f = formatter.AbstractFormatter(w)
            errs = []
            self.format(f, errs)
            if errs:
                for x in errs:
                    ctx.ui.warning(x)

        cls.print_text = print_text
        if '__str__' not in dict:
            def str(self):
                strfile = io.StringIO()
                self.print_text(strfile)
                str = strfile.getvalue()
                strfile.close()
                return str

            cls.__str__ = str

        if '__eq__' not in dict:
            def equal(self, other):
                # handle None
                if other is None:
                    return False  # well, must be False at this point :)
                for name in names:
                    try:
                        if getattr(self, name) != getattr(other, name):
                            return False
                    except KeyboardInterrupt:
                        raise
                    except Exception as e:  # FIXME: what exception could we catch here, replace with that.
                        return False
                return True

            def notequal(self, other):
                return not self.__eq__(other)

            cls.__eq__ = equal
            cls.__ne__ = notequal

        if xmlfile_support:
            def parse(self, xml, keepDoc=False):
                """parse XML string and decode it into a python object"""
                self.parsexml(xml)
                errs = []
                self.decode(self.rootNode(), errs)
                if errs:
                    errs.append(_("autoxml.parse: String '{}' has errors.").format(xml))
                    raise Error(*errs)
                if hasattr(self, 'read_hook'):
                    self.read_hook(errs)

                if not keepDoc:
                    self.unlink()  # get rid of the tree

                errs = self.errors()
                if errs:
                    errs.append(_("autoxml.parse: String '{}' has errors.").format(xml))

            def read(self, uri, keepDoc=False, tmpDir='/tmp',
                     sha1sum=False, compress=None, sign=None, copylocal=False, nodecode=False):
                """read XML file and decode it into a python object"""
                read_xml = self.readxml(uri, tmpDir, sha1sum=sha1sum,
                                        compress=compress, sign=sign, copylocal=copylocal)

                if nodecode:
                    return read_xml

                errs = []
                self.decode(self.rootNode(), errs)
                if errs:
                    errs.append(_("autoxml.read: File '{}' has errors.").format(uri))
                    raise Error(*errs)
                if hasattr(self, 'read_hook'):
                    self.read_hook(errs)

                if not keepDoc:
                    self.unlink()  # get rid of the tree

                errs = self.errors()
                if errs:
                    errs.append(_("autoxml.read: File '{}' has errors.").format(uri))
                    raise Error(*errs)

            def write(self, uri, keepDoc=False, tmpDir='/tmp',
                      sha1sum=False, compress=None, sign=None):
                """encode the contents of the python object into an XML file"""
                errs = self.errors()
                if errs:
                    errs.append(_("autoxml.write: object validation has failed."))
                    raise Error(*errs)
                errs = []
                self.newDocument()
                self.encode(self.rootNode(), errs)
                if hasattr(self, 'write_hook'):
                    self.write_hook(errs)
                if errs:
                    errs.append(_("autoxml.write: File encoding '{}' has errors.").format(uri))
                    raise Error(*errs)
                self.writexml(uri, tmpDir, sha1sum=sha1sum, compress=compress, sign=sign)
                if not keepDoc:
                    self.unlink()  # get rid of the tree

            cls.read = read
            cls.write = write
            cls.parse = parse

    def gen_attr_member(cls, attr):
        """generate readers and writers for an attribute member"""
        # print 'attr:', attr
        spec = getattr(cls, 'a_' + attr)
        tag_type = spec[0]
        assert type(tag_type) == type(type)

        def readtext(node, attr):
            return xmlext.getNodeAttribute(node, attr)

        def writetext(node, attr, text):
            # print 'write attr', attr, text
            xmlext.setNodeAttribute(node, attr, text)

        anonfuns = cls.gen_anon_basic(attr, spec, readtext, writetext)
        return cls.gen_named_comp(attr, spec, anonfuns)

    def gen_tag_member(cls, tag):
        """generate helper funs for tag member of class"""
        # print 'tag:', tag
        spec = getattr(cls, 't_' + tag)
        anonfuns = cls.gen_tag(tag, spec)
        return cls.gen_named_comp(tag, spec, anonfuns)

    def gen_tag(cls, tag, spec):
        """generate readers and writers for the tag"""
        tag_type = spec[0]
        if type(tag_type) is type and \
                tag_type in autoxml.basic_cons_map:
            def readtext(node, tagpath):
                # print 'read tag', node, tagpath
                return xmlext.getNodeText(node, tagpath)

            def writetext(node, tagpath, text):
                # print 'write tag', node, tagpath, text
                xmlext.addText(node, tagpath, text)

            return cls.gen_anon_basic(tag, spec, readtext, writetext)
        elif type(tag_type) is list:
            return cls.gen_list_tag(tag, spec)
        elif tag_type is LocalText:
            return cls.gen_insetclass_tag(tag, spec)
        elif type(tag_type) is autoxml or type(tag_type) is type:
            return cls.gen_class_tag(tag, spec)
        else:
            raise Error(_('gen_tag: unrecognized tag type {} in spec.').format(str(tag_type)))

    def gen_str_member(cls, token):
        """generate readers and writers for a string member"""
        spec = getattr(cls, 's_' + token)
        tag_type = spec[0]
        assert type(tag_type) == type(type)

        def readtext(node, blah):
            try:
                node.normalize()  # iksemel doesn't have this
            except:
                pass
            return xmlext.getNodeText(node)

        def writetext(node, blah, text):
            xmlext.addText(node, "", text)

        anonfuns = cls.gen_anon_basic(token, spec, readtext, writetext)
        return cls.gen_named_comp(token, spec, anonfuns)

    def gen_named_comp(cls, token, spec, anonfuns):
        """generate a named component tag/attr. a decoration of
        anonymous functions that do not bind to variable names"""
        name = cls.mixed_case(token)
        token_type = spec[0]
        req = spec[1]
        (init_a, decode_a, encode_a, errors_a, format_a) = anonfuns

        def init(self):
            """initialize component"""
            setattr(self, name, init_a())

        def decode(self, node, errs, where):
            """decode component from DOM node"""
            setattr(self, name, decode_a(node, errs, where + '.' + str(name)))

        def encode(self, node, errs):
            """encode self inside, possibly new, DOM node using xml"""
            if hasattr(self, name):
                value = getattr(self, name)
            else:
                value = None
            encode_a(node, value, errs)

        def errors(self, where):
            """return errors in the object"""
            errs = []
            if hasattr(self, name) and getattr(self, name) is not None:
                value = getattr(self, name)
                errs.extend(errors_a(value, where + '.' + name))
            else:
                if req == mandatory:
                    errs.append(where + ': ' + _('Mandatory variable {} not available.').format(name))
            return errs

        def format(self, f, errs):
            if hasattr(self, name):
                value = getattr(self, name)
                f.add_literal_data(token + ': ')
                format_a(value, f, errs)
                f.add_line_break()
            else:
                if req == mandatory:
                    errs.append(_('Mandatory variable {} not available.').format(name))

        return name, init, decode, encode, errors, format

    @staticmethod
    def mixed_case(identifier):
        """helper function to turn token name into mixed case"""
        if identifier is "":
            return ""
        else:
            if identifier[0] == 'I':
                lowly = 'i'  # because of pythonic idiots we can't choose locale in lower
            else:
                lowly = identifier[0].lower()
            return lowly + identifier[1:]

    @staticmethod
    def tagpath_head_last(tagpath):
        """returns split of the tag path into last tag and the rest"""
        try:
            lastsep = tagpath.rindex('/')
        except ValueError as e:
            return '', tagpath
        return tagpath[:lastsep], tagpath[lastsep + 1:]

    def parse_spec(cls, token, spec):
        """decompose member specification"""
        name = cls.mixed_case(token)
        token_type = spec[0]
        req = spec[1]

        if len(spec) >= 3:
            path = spec[2]  # an alternative path specified
        elif type(token_type) is type([]):
            if type(token_type[0]) is autoxml:
                # if list of class, by default nested like in most PSPEC
                path = token + '/' + token_type[0].tag
            else:
                # if list of ordinary type, just take the name for
                path = token
        elif type(token_type) is autoxml:
            # if a class, by default its tag
            path = token_type.tag
        else:
            path = token  # otherwise it's the same name as
            # the token
        return name, token_type, req, path

    def gen_anon_basic(cls, token, spec, readtext, writetext):
        """Generate a tag or attribute with one of the basic
        types like integer. This has got to be pretty generic
        so that we can invoke it from the complex types such as Class
        and List. The readtext and writetext arguments achieve
        the DOM text access for this datatype."""

        name, token_type, req, tagpath = cls.parse_spec(token, spec)

        def initialize():
            """default value for all basic types is None"""
            return None

        def decode(node, errs, where):
            """decode from DOM node, the value, watching the spec"""
            text = readtext(node, token)
            # print 'read text ', text
            if text:
                try:
                    value = autoxml.basic_cons_map[token_type](text)
                except KeyboardInterrupt:
                    raise
                except Exception as e:  # FIXME: what exception could we catch here, replace with that.
                    value = None
                    errs.append(where + ': ' + _('Type mismatch: read text cannot be decoded.'))
                return value
            else:
                if req == mandatory:
                    errs.append(where + ': ' + _('Mandatory token {} not available.').format(token))
                return None

        def encode(node, value, errs):
            """encode given value inside DOM node"""
            if value is not None:
                writetext(node, token, str(value))
            else:
                if req == mandatory:
                    errs.append(_('Mandatory token {} not available.').format(token))

        def errors(value, where):
            errs = []
            if value and not isinstance(value, token_type):
                errs.append(where + ': ' + _('Type mismatch. Expected {0}, got {1}').format(token_type, type(value)))
            return errs

        def format(value, f, errs):
            """format value for pretty printing"""
            f.add_literal_data(str(value))

        return initialize, decode, encode, errors, format

    def gen_class_tag(cls, tag, spec):
        """generate a class datatype"""
        name, tag_type, req, path = cls.parse_spec(tag, spec)

        def make_object():
            obj = tag_type.__new__(tag_type)
            obj.__init__(tag=tag, req=req)
            return obj

        def init():
            return make_object()

        def decode(node, errs, where):
            node = xmlext.getNode(node, tag)
            if node:
                try:
                    obj = make_object()
                    obj.decode(node, errs, where)
                    return obj
                except Error:
                    errs.append(where + ': ' + _('Type mismatch: DOM cannot be decoded.'))
            else:
                if req == mandatory:
                    errs.append(where + ': ' + _('Mandatory argument not available.'))
            return None

        def encode(node, obj, errs):
            if node and obj:
                try:
                    # FIXME: this doesn't look pretty
                    classnode = xmlext.newNode(node, tag)
                    obj.encode(classnode, errs)
                    xmlext.addNode(node, '', classnode)
                except Error:
                    if req == mandatory:
                        # note: we can receive an error if obj has no content
                        errs.append(_('Object cannot be encoded.'))
            else:
                if req == mandatory:
                    errs.append(_('Mandatory argument not available.'))

        def errors(obj, where):
            return obj.errors(where)

        def format(obj, f, errs):
            if obj:
                try:
                    obj.format(f, errs)
                except Error:
                    if req == mandatory:
                        errs.append(_('Object cannot be formatted.'))
            else:
                if req == mandatory:
                    errs.append(_('Mandatory argument not available.'))

        return init, decode, encode, errors, format

    def gen_list_tag(cls, tag, spec):
        """generate a list datatype. stores comps in tag/comp_tag"""
        name, tag_type, req, path = cls.parse_spec(tag, spec)

        pathcomps = path.split('/')
        comp_tag = pathcomps.pop()
        list_tagpath = util.makepath(pathcomps, sep='/', relative=True)

        if len(tag_type) != 1:
            raise Error(_('List type must contain only one element.'))

        x = cls.gen_tag(comp_tag, [tag_type[0], mandatory])
        (init_item, decode_item, encode_item, errors_item, format_item) = x

        def init():
            return []

        def decode(node, errs, where):
            l = []
            nodes = xmlext.getAllNodes(node, path)
            # print node, tag + '/' + comp_tag, nodes
            if len(nodes) == 0 and req == mandatory:
                errs.append(
                    where + ': ' + _('Mandatory list "{0}" under "{1}" node is empty.').format(path, node.name()))
            ix = 1
            for node in nodes:
                dummy = xmlext.newNode(node, "Dummy")
                xmlext.addNode(dummy, '', node)
                l.append(decode_item(dummy, errs, where + str("[{}]".format(ix))))
                ix += 1
            return l

        def encode(node, l, errs):
            if l:
                for item in l:
                    if list_tagpath:
                        listnode = xmlext.addNode(node, list_tagpath, branch=False)
                    else:
                        listnode = node
                    encode_item(listnode, item, errs)
                    # encode_item(node, item, errs)
            else:
                if req is mandatory:
                    errs.append(_('Mandatory list "{0}" under "{1}" node is empty.').format(path, node.name()))

        def errors(l, where):
            errs = []
            ix = 1
            for node in l:
                errs.extend(errors_item(node, where + '[{}]'.format(ix)))
                ix += 1
            return errs

        def format(l, f, errs):
            l.sort()
            for node in l:
                format_item(node, f, errs)
                f.add_literal_data(' ')

        return init, decode, encode, errors, format

    def gen_insetclass_tag(cls, tag, spec):
        """generate a class datatype that is highly integrated
           don't worry if that means nothing to you. this is a silly
           hack to implement local text quickly. it's not the most
           elegant thing in the world. it's basically a copy of
           class tag"""
        name, tag_type, req, path = cls.parse_spec(tag, spec)

        def make_object():
            obj = tag_type.__new__(tag_type)
            obj.__init__(tag=tag, req=req)
            return obj

        def init():
            return make_object()

        def decode(node, errs, where):
            if node:
                try:
                    obj = make_object()
                    obj.decode(node, errs, where)
                    return obj
                except Error:
                    errs.append(where + ': ' + _('Type mismatch: DOM cannot be decoded.'))
            else:
                if req == mandatory:
                    errs.append(where + ': ' + _('Mandatory argument not available.'))
            return None

        def encode(node, obj, errs):
            if node and obj:
                try:
                    # FIXME: this doesn't look pretty
                    obj.encode(node, errs)
                except Error:
                    if req == mandatory:
                        # note: we can receive an error if obj has no content
                        errs.append(_('Object cannot be encoded.'))
            else:
                if req == mandatory:
                    errs.append(_('Mandatory argument not available.'))

        def errors(obj, where):
            return obj.errors(where)

        def format(obj, f, errs):
            if obj:
                try:
                    obj.format(f, errs)
                except Error:
                    if req == mandatory:
                        errs.append(_('Object cannot be formatted.'))
            else:
                if req == mandatory:
                    errs.append(_('Mandatory argument not available.'))

        return init, decode, encode, errors, format

    basic_cons_map = {
        bytes: bytes,
        str: str,
        int: int,
        float: float
    }


code:
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018, Suleyman POYRAZ (Zaryob)
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

try:
    from .xmlext_iks import *

except:
    from .xmlext_minidom import *

repo_name: SulinOS/inary

license: mit


code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

"""
 xmlext is a helper module for accessing XML files using
 iksemel . It is a convenient wrapper for some
 DOM functions, and provides path based get/add functions
 as in KDE API.

 function names are mixedCase for compatibility with iksemel,
 an 'old library'

 this implementation uses iksemel
"""

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

import inary
import ciksemel as iks

parse = iks.parse
parseString = iks.parseString
newDocument = iks.newDocument

class XMLError(inary.errors.Error):
    pass

def getAllNodes(node, tagPath):
    """retrieve all nodes that match a given tag path."""
    tags = tagPath.split('/')
    if len(tags) == 0:
        return []
    nodeList = [node]  # basis case
    for tag in tags:
        results = [getTagByName(x, tag) for x in nodeList]
        nodeList = []
        for x in results:
            nodeList.extend(x)
            pass  # emacs indentation error, keep it here
        if len(nodeList) == 0:
            return []
    return nodeList

def getNodeAttribute(node, attrname):
    """get named attribute from DOM node"""
    return node.getAttribute(attrname)

def getAttributeList(node):
    return node.attributes()

def setNodeAttribute(node, attrname, value):
    """get named attribute from DOM node"""
    return node.setAttribute(attrname, value)

def getChildElts(parent):
    """get only child elements"""
    return [x for x in parent.tags()]

def getTagByName(parent, childName):
    return [x for x in parent.tags(childName)]

def getNodeText(node, tagpath=""):
    """get the first child and expect it to be text!"""
    if tagpath != "":
        node = getNode(node, tagpath)
        if not node:
            return None

    child = node.firstChild()
    if not child:
        return None
    if child.type() == iks.DATA:
        # KLUDGE: FIXME: python 2.x bug: force decoding as UTF-8
        child_data = child.data()
        # print('child_data=', child_data.strip())
        return child_data.strip()  # in any case, strip whitespaces...
    else:
        raise XMLError(_("getNodeText: Expected text node, got something else!"))

def getChildText(node_s, tagpath):
    """get the text of a child at the end of a tag path"""
    node = getNode(node_s, tagpath)
    if not node:
        return None
    return getNodeText(node)

def getNode(node, tagpath):
    """returns the *first* matching node for given tag path."""

    if tagpath == "":
        return node

    assert type(tagpath) == str
    tags = tagpath.split('/')
    assert len(tags) > 0

    # iterative code to search for the path
    for tag in tags:
        currentNode = None
        for child in node.tags():
            if child.name() == tag:
                currentNode = child
                break
        if not currentNode:
            return None
        else:
            node = currentNode
    return currentNode

def createTagPath(node, tags):
    """create new child at the end of a tag chain starting from node
    no matter what"""
    if len(tags) == 0:
        return node
    for tag in tags:
        node = node.insertTag(tag)
    return node

def addTagPath(node, tags, newnode=None):
    """add newnode at the end of a tag chain, smart one"""
    node = createTagPath(node, tags)
    if newnode:  # node to add specified
        node.insertNode(newnode)
    return node

def addNode(node, tagpath, newnode=None, branch=True):
    """add a new node at the end of the tree and returns it
    if newnode is given adds that node, too."""

    assert type(tagpath) == str
    tags = []
    if tagpath != "":
        tags = tagpath.split('/')  # tag chain
    else:
        addTagPath(node, [], newnode)
        return node  # FIXME: is this correct!?!?

    assert len(tags) > 0  # we want a chain

    # iterative code to search for the path

    if branch:
        rem = 1
    else:
        rem = 0

    while len(tags) > rem:
        tag = tags.pop(0)
        nodeList = getTagByName(node, tag)
        if len(nodeList) == 0:  # couldn't find
            tags.insert(0, tag)  # put it back in
            return addTagPath(node, tags, newnode)
        else:
            node = nodeList[len(nodeList) - 1]  # discard other matches
    else:
        # had only one tag..
        return addTagPath(node, tags, newnode)

def addText(node, tagpath, text):
    node = addNode(node, tagpath)
    node.insertData(text)

def removeChild(node, doc):
    node.hide()

def removeChildText(node):
    node.firstChild().hide()

def removeAttribute(node):
    pass

def newNode(node, tag):
    return iks.newDocument(tag)

def toString(node):
    return node.toString()

def toPretty(node):
    return node.toPrettyString()


code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

"""
 xmlext is a helper module for accessing XML files using
 xml.dom.minidom . It is a convenient wrapper for some
 DOM functions, and provides path based get/add functions
 as in KDE API.

 function names are mixedCase for compatibility with minidom,
 an 'old library'

 this implementation uses minidom
"""

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

import inary

import xml.dom.minidom as minidom
from xml.parsers.expat import ExpatError

class XMLError(inary.errors.Error):
    pass

def newDocument(tag):
    impl = minidom.getDOMImplementation()
    dom = impl.createDocument(None, tag, None)
    return dom.documentElement

def parse(fileName):
    try:
        dom = minidom.parse(fileName)
        return dom.documentElement
    except ExpatError as inst:
        raise XMLError(_("File \"{}\" has invalid XML: {}\n").format(fileName,
                                                                   str(inst)))

def parseString(fileString):
    try:
        dom = minidom.parseString(fileString)
        return dom.documentElement
    except ExpatError as inst:
        raise XMLError(_("FileString is an invalid XML: {}\n\nFileString: {}\n").format(
            str(inst), fileString))

def getAllNodes(node, tagPath):
    """retrieve all nodes that match a given tag path."""
    tags = tagPath.split('/')
    if len(tags) == 0:
        return []
    nodeList = [node]  # basis case
    for tag in tags:
        results = [getTagByName(x, tag) for x in nodeList]
        nodeList = []
        for x in results:
            nodeList.extend(x)
            pass  # emacs indentation error, keep it here
        if len(nodeList) == 0:
            return []
    return nodeList

def getNodeAttribute(node, attrname):
    """get named attribute from DOM node"""
    if not node.hasAttribute(attrname):
        return None
    return node.getAttribute(attrname)

def getAttributeList(node):
    return node.attributes

def setNodeAttribute(node, attrname, value):
    """get named attribute from DOM node"""
    return node.setAttribute(attrname, value)

def getChildElts(parent):
    """get only child elements"""
    return [x for x in parent.childNodes if x.nodeType == x.ELEMENT_NODE]

def getTagByName(parent, childName):
    return [x for x in parent.childNodes
            if x.nodeType == x.ELEMENT_NODE and x.tagName == childName]

def getNodeText(node, tagpath=""):
    """get the first child and expect it to be text!"""
    if tagpath != "":
        node = getNode(node, tagpath)
    try:
        child = node.childNodes[0]
    except IndexError:
        return None
    except AttributeError:  # no node by that name
        return None
    if child.nodeType == child.TEXT_NODE:
        # print('child_data=', child.data.strip())
        return child.data.strip()  # in any case, strip whitespaces...
    else:
        raise XMLError(_("getNodeText: Expected text node, got something else!"))

def getChildText(node_s, tagpath):
    """get the text of a child at the end of a tag path"""
    node = getNode(node_s, tagpath)
    if not node:
        return None
    return getNodeText(node)

def getNode(node, tagpath):
    """returns the *first* matching node for given tag path."""

    if tagpath == "":
        return node

    assert type(tagpath) == str
    tags = tagpath.split('/')
    assert len(tags) > 0

    # iterative code to search for the path
    for tag in tags:
        currentNode = None
        for child in node.childNodes:
            if child.nodeType == node.ELEMENT_NODE and child.tagName == tag:
                currentNode = child
                break
        if not currentNode:
            return None
        else:
            node = currentNode
    return currentNode

def createTagPath(node, tags):
    """create new child at the end of a tag chain starting from node
    no matter what"""
    if len(tags) == 0:
        return node
    dom = node.ownerDocument
    for tag in tags:
        node = node.appendChild(dom.createElement(tag))
    return node

def addTagPath(node, tags, newnode=None):
    """add newnode at the end of a tag chain, smart one"""
    node = createTagPath(node, tags)
    if newnode:  # node to add specified
        node.appendChild(newnode)
    return node

def addNode(node, tagpath, newnode=None, branch=True):
    """add a new node at the end of the tree and returns it
    if newnode is given adds that node, too."""

    assert type(tagpath) == str
    tags = []
    if tagpath != "":
        tags = tagpath.split('/')  # tag chain
    else:
        addTagPath(node, [], newnode)
        return node

    assert len(tags) > 0  # we want a chain

    # iterative code to search for the path

    if branch:
        rem = 1
    else:
        rem = 0

    while len(tags) > rem:
        tag = tags.pop(0)
        nodeList = getTagByName(node, tag)
        if len(nodeList) == 0:  # couldn't find
            tags.insert(0, tag)  # put it back in
            return addTagPath(node, tags, newnode)
        else:
            node = nodeList[len(nodeList) - 1]  # discard other matches
    else:
        # had only one tag..
        return addTagPath(node, tags, newnode)

def addText(node, tagPath, text, branch=True):
    newnode = node.ownerDocument.createTextNode(text)
    return addNode(node, tagPath, newnode, branch=branch)

def removeChild(node, doc):
    doc.removeChild(node)

def removeChildText(node):
    node.childNodes[0].nodeValue = ""

def removeAttribute(node):
    pass

def newNode(node, tag):
    return node.ownerDocument.createElement(tag)

def toString(node):
    return node.toxml()

def toPretty(node):
    return node.toprettyxml()


code:
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018, Suleyman POYRAZ (Zaryob)
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

try:
    from .xmlfile_iks import *

except:
    from .xmlfile_minidom import *


code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

"""
 XmlFile class further abstracts a dom object using the
 high-level dom functions provided in xmlext module (and sorely lacking
 in xml.dom :( )

 function names are mixedCase for compatibility with iksemel,
 an 'old library'

 this implementation uses iksemel
"""

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

import io
import ciksemel as iks

import inary.errors
import inary.config
from inary.file import File

class Error(inary.errors.Error):
    pass

class XmlFile(object):
    """A class to help reading and writing an XML file"""

    def __init__(self, tag):
        self.rootTag = tag

    def newDocument(self):
        """clear DOM"""
        self.doc = iks.newDocument(self.rootTag)

    def unlink(self):
        """deallocate DOM structure"""
        del self.doc

    def rootNode(self):
        """returns root document element"""
        return self.doc

    def parsexml(self, file):
        try:
            if type(file) == bytes:
                file.decode('utf-8')
            self.doc = iks.parseString(str(file))
            return self.doc
        except Exception as e:
            raise Error(_("File \"{}\" has invalid XML: {}\n").format(file, e))

    def readxml(self, uri, tmpDir='/tmp', sha1sum=False,
                compress=None, sign=None, copylocal=False):
        uri = File.make_uri(uri)
        try:
            localpath = File.download(uri, tmpDir, sha1sum=sha1sum,
                                      compress=compress, sign=sign, copylocal=copylocal)
        except IOError as e:
            raise Error(_("Cannot read URI {0}: {1}").format(uri, str(e)))

        st = io.StringIO()

        try:
            from inary.libraries.preprocess import preprocess
            preprocess.preprocess(infile=localpath, outfile=st, defines=inary.config.Config().values.directives)
            st.seek(0)
        except:
            st = open(localpath)

        try:
            self.doc = iks.parse(localpath)
            return self.doc
        except Exception as e:
            raise Error(_("File \"{}\" has invalid XML.").format(localpath))

    def writexml(self, uri, tmpDir='/tmp', sha1sum=False, compress=None, sign=None):
        f = File(uri, File.write, sha1sum=sha1sum, compress=compress, sign=sign)
        f.write(self.doc.toPrettyString())
        f.close()

    def writexmlfile(self, f):
        f.write(self.doc.toPrettyString())


code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

"""
 XmlFile class further abstracts a dom object using the
 high-level dom functions provided in xmlext module (and sorely lacking
 in xml.dom :( )

 function names are mixedCase for compatibility with minidom,
 an 'old library'

"""

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

import io
import xml.dom.minidom as minidom
from xml.parsers.expat import ExpatError

import inary.errors
import inary.config
from inary.file import File

class Error(inary.errors.Error):
    pass

class XmlFile(object):
    """A class to help reading and writing an XML file"""

    def __init__(self, tag):
        self.rootTag = tag

    def newDocument(self):
        """clear DOM"""
        impl = minidom.getDOMImplementation()
        self.doc = impl.createDocument(None, self.rootTag, None)

    def unlink(self):
        """deallocate DOM structure"""
        self.doc.unlink()
        del self.doc

    def rootNode(self):
        """returns root document element"""
        return self.doc.documentElement

    def parsexml(self, file):
        try:
            self.doc = minidom.parseString(file)
            return self.doc.documentElement
        except Exception as e:
            raise Error(_("File \"{}\" has invalid XML: {}\n").format(file, e))

    def readxml(self, uri, tmpDir='/tmp', sha1sum=False,
                compress=None, sign=None, copylocal=False):
        uri = File.make_uri(uri)
        try:
            localpath = File.download(uri, tmpDir, sha1sum=sha1sum,
                                      compress=compress, sign=sign, copylocal=copylocal)
        except IOError as e:
            raise Error(_("Cannot read URI {0}: {1}").format(uri, str(e)))

        st = io.StringIO()

        try:
            from inary.libraries import preprocess
            preprocess.preprocess(infile=localpath, outfile=st, defines=inary.config.Config().values.directives)
            st.seek(0)
        except:
            st = open(localpath)

        try:
            self.doc = minidom.parse(localpath)
            return self.doc.documentElement
        except ExpatError as err:
            raise Error(_("File \"{}\" has invalid XML: {}\n").format(localpath,
                                                                    str(err)))

    def writexml(self, uri, tmpDir='/tmp', sha1sum=False, compress=None, sign=None):
        f = File(uri, File.write, sha1sum=sha1sum, compress=compress, sign=sign)
        f.write(self.doc.toprettyxml())
        f.close()

    def writexmlfile(self, f):
        f.write(self.doc.toprettyxml())


code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#
# generic user interface
#

(installed, upgraded, removed, installing, removing, configuring, configured, extracting,
 downloading, packagestogo, updatingrepo, cached, desktopfile) = list(range(13))

class UI(object):
    """Abstract class for UI operations, derive from this."""

    class Progress:
        def __init__(self, totalsize, existsize=0):
            self.totalsize = totalsize
            try:
                self.percent = (existsize * 100) / totalsize
            except ArithmeticError:
                self.percent = 0

        def update(self, size):
            if not self.totalsize:
                return 100
            try:
                self.percent = (size * 100) / self.totalsize
            except ArithmeticError:
                self.percent = 0
            return self.percent

    def __init__(self, debuggy=False, verbose=False):
        self.show_debug = debuggy
        self.show_verbose = verbose
        self.errors = 0
        self.warnings = 0

    def close(self):
        """cleanup stuff here"""
        pass

    def set_verbose(self, flag):
        self.show_verbose = flag

    def set_debug(self, flag):
        self.show_debug = flag

    def info(self, msg, verbose=False, noln=False, color='default'):
        """give an informative message"""
        pass

    def ack(self, msg):
        """inform the user of an important event and wait for acknowledgement"""
        pass

    def debug(self, msg, color='normal'):
        """show debugging info"""
        if self.show_debug:
            self.info(str('DEBUG: ' + msg),color)

    def warning(self, msg):
        """warn the user"""
        pass

    def error(self, msg):
        """inform a (possibly fatal) error"""
        pass

    # FIXME: merge this with info, this just means "important message"
    def action(self, msg):
        """uh?"""
        pass

    def choose(self, msg, list):
        """ask the user to choose from a list of alternatives"""
        pass

    def confirm(self, msg,invert=False):
        msg = str(msg)
        if ctx.config.options and ctx.config.options.yes_all:
            return True

        locale.setlocale(locale.LC_ALL, "")
        yes_expr = re.compile(locale.nl_langinfo(locale.YESEXPR))
        no_expr = re.compile(locale.nl_langinfo(locale.NOEXPR))
        locale.setlocale(locale.LC_ALL, "C")

        tty.tcflush(sys.stdin.fileno(), 0)
        if invert:
            prompt = msg + util.colorize(" "+_('(yes'), 'red') + '/' + util.colorize(_('no)'), 'green') + ":  "
        else:
            prompt = msg + util.colorize(" "+_('(yes'), 'green') + '/' + util.colorize(_('no)'), 'red') + ":  "
        util.noecho(False)
        s = input(prompt)
        util.noecho(True)

        if yes_expr.search(s):
            return True
        else:
            return False

    def display_progress(self, **ka):
        """display progress"""
        pass

    def status(self, msg=None, push_screen=True):
        """set status, if not given clear it"""
        pass

    def notify(self, event, logging=True, **keywords):
        """notify UI of a significant event"""
        pass


code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

"""Simplifies working with URLs, purl module provides common URL
parsing and processing"""

import os.path
import urllib.parse

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

class URI(object):
    """URI class provides a URL parser and simplifies working with
    URLs."""

    def __init__(self, uri=None):
        if uri:
            self.set_uri(str(uri))
        else:
            self.__scheme = None
            self.__location = None
            self.__path = None
            self.__filename = None
            self.__params = None
            self.__query = None
            self.__fragment = None
            self.__uri = None

        self.__authinfo = None

    def get_uri(self):
        if self.__uri:
            return self.__uri
        return None

    def set_uri(self, uri):
        # (scheme, location, path, params, query, fragment)
        uri = str(uri)
        u = urllib.parse.urlparse(uri, "file")
        self.__scheme = u[0]
        self.__location = u[1]
        self.__path = u[2]
        self.__filename = os.path.basename(self.__path)
        self.__params = u[3]
        self.__query = u[4]
        self.__fragment = u[5]

        self.__uri = uri

    def is_local_file(self):
        if self.scheme() == "file":
            return True
        else:
            return False

    def is_remote_file(self):
        return not self.is_local_file()

    def is_absolute_path(self):
        return os.path.isabs(self.__path)

    def is_relative_path(self):
        return not self.is_absolute_path()

    def set_auth_info(self, authTuple):
        if not isinstance(authTuple, tuple):
            raise Exception(_("setAuthInfo needs a tuple (user, pass)"))
        self.__authinfo = authTuple

    def auth_info(self):
        return self.__authinfo

    def scheme(self):
        return self.__scheme

    def location(self):
        return self.__location

    def path(self):
        return self.__path

    def filename(self):
        return self.__filename

    def params(self):
        return self.__params

    def query(self):
        return self.__query

    def fragment(self):
        return self.__fragment

    def __str__(self):
        return self.get_uri()

    uri = property(get_uri, set_uri)


code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

"""misc. utility functions, including process and file utils"""

# standard python modules

import fcntl
import fnmatch
import hashlib
import operator
import os
import platform
import re
import shutil
import struct

import sys
import termios
import unicodedata

from functools import reduce

import gettext
__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

try:
    import subprocess
except ImportError:
    raise Exception(_("Module: \'subprocess\' can not imported."))

class Singleton(type):
    def __init__(cls, name, bases, dict):
        super(Singleton, cls).__init__(name, bases, dict)
        cls.instance = None

    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(Singleton, cls).__call__(*args, **kw)

        return cls.instance

# inary modules
import inary
import inary.errors
import inary.context as ctx

class Error(inary.errors.Error):
    pass

class FileError(Error):
    pass

class FilePermissionDeniedError(Error):
    pass

def locked(func):
    """
    Decorator for synchronizing privileged functions
    """

    def wrapper(*__args, **__kw):
        try:
            lock = open(join_path(ctx.config.lock_dir(), 'inary'), 'w')
        except IOError:
            raise inary.errors.PrivilegeError(_("You have to be root for this operation."))

        try:
            fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
            ctx.locked = True
        except IOError:
            if not ctx.locked:
                raise inary.errors.AnotherInstanceError(
                    _("Another instance of Inary is running. Only one instance is allowed."))

        try:
            inary.db.invalidate_caches()
            ctx.ui.info(_('Invalidating database caches...'), verbose=True)
            ret = func(*__args, **__kw)
            ctx.ui.info(_('Updating database caches...'), verbose=True)
            inary.db.update_caches()
            return ret
        finally:
            ctx.locked = False
            lock.close()

    return wrapper

#########################
# string/list/functional#
#########################

whitespace = ' \t\n\r\v\f'
ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ascii_letters = ascii_lowercase + ascii_uppercase
digits = '0123456789'
hexdigits = digits + 'abcdef' + 'ABCDEF'
octdigits = '01234567'
punctuation = """!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
printable = digits + ascii_letters + punctuation + whitespace

def every(pred, seq):
    return reduce(operator.and_, list(map(pred, seq)), True)

def any(pred, seq):
    return reduce(operator.or_, list(map(pred, seq)), False)

def unzip(seq):
    return list(zip(*seq))

def concat(l):
    """Concatenate a list of lists."""
    return reduce(operator.concat, l)

def multisplit(str, chars):
    """Split str with any of the chars."""
    l = [str]
    for c in chars:
        l = concat([x.split(c) for x in l])
    return l

def same(l):
    """Check if all elements of a sequence are equal."""
    if len(l) == 0:
        return True
    else:
        last = l.pop()
        for x in l:
            if x != last:
                return False
        return True

def any(pred, seq):
    return reduce(operator.or_, list(map(pred, seq)), False)

def flatten_list(l):
    """Flatten a list of lists."""
    # Fastest solution is list comprehension
    # See: http://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python
    return [item for sublist in l for item in sublist]

def unique_list(l):
    """Creates a unique list by deleting duplicate items"""
    list_set = set(l)
    unique_list = (list(list_set))
    return [x for x in unique_list]

def strlist(l):
    """Concatenate string reps of l's elements."""
    return "".join([str(x) + ' ' for x in l])

def prefix(a, b):
    """Check if sequence a is a prefix of sequence b."""
    if len(a) > len(b):
        return False
    for i in range(0, len(a)):
        if a[i] != b[i]:
            return False
    return True

def remove_prefix(a, b):
    """Remove prefix a from sequence b."""
    assert prefix(a, b)
    return b[len(a):]

def suffix(a, b):
    """Check if sequence a is a suffix of sequence b."""
    if len(a) > len(b):
        return False
    for i in range(1, len(a) + 1):
        if a[-i] != b[-i]:
            return False
    return True

def remove_suffix(a, b):
    """Remove suffix a from sequence b."""
    assert suffix(a, b)
    return b[:-len(a)]

def human_readable_size(size=0):
    symbols, depth = [' B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], 0

    while size > 1000 and depth < 8:
        size = float(size / 1024)
        depth += 1

    return size, symbols[depth]

def human_readable_rate(size=0):
    x = human_readable_size(size)
    return x[0], x[1] + '/s'

def format_by_columns(strings, sep_width=2):
    longest_str_len = len(max(strings, key=len))
    term_rows, term_columns = get_terminal_size()

    def get_columns(max_count):
        if longest_str_len > term_columns:
            return [longest_str_len]

        columns = []
        for name in strings:
            table_width = sum(columns) + len(name) + len(columns) * sep_width
            if table_width > term_columns:
                break

            columns.append(len(name))
            if len(columns) == max_count:
                break

        return columns

    def check_size(columns):
        total_sep_width = (len(columns) - 1) * sep_width

        for n, name in enumerate(strings):
            col = n % len(columns)
            if len(name) > columns[col]:
                columns[col] = len(name)

            if len(columns) > 1:
                width = sum(columns) + total_sep_width
                if width > term_columns:
                    return False

        return True

    columns = get_columns(term_columns)

    while not check_size(columns):
        columns = get_columns(len(columns) - 1)

    sep = " " * sep_width
    lines = []
    current_line = []
    for n, name in enumerate(strings):
        col = n % len(columns)
        current_line.append(name.ljust(columns[col]))

        if col == len(columns) - 1:
            lines.append(sep.join(current_line))
            current_line = []

    if current_line:
        lines.append(sep.join(current_line))

    return "\n".join(lines)

##############################
# Process Releated Functions #
##############################

def search_executable(executable):
    """Search for the executable in user's paths and return it."""
    for _path in os.environ["PATH"].split(":"):
        full_path = os.path.join(_path, executable)
        if os.path.exists(full_path) and os.access(full_path, os.X_OK):
            return full_path
    return None

def run_batch(cmd, ui_debug=True):
    """Run command and report return value and output."""
    ctx.ui.info(_('Running ') + cmd, verbose=True)
    p = subprocess.Popen(cmd, shell=True,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = p.communicate()
    if ui_debug: ctx.ui.debug(_('return value for "{0}" is {1}').format(cmd, p.returncode))
    return p.returncode, out.decode('utf-8'), err

# TODO: it might be worthwhile to try to remove the
# use of ctx.stdout, and use run_batch()'s return
# values instead. but this is good enough :)
def run_logged(cmd):
    """Run command and get the return value."""
    ctx.ui.info(_('Running ') + cmd, verbose=True)
    if ctx.stdout:
        stdout = ctx.stdout
    else:
        if ctx.get_option('debug'):
            stdout = None
        else:
            stdout = subprocess.PIPE
    if ctx.stderr:
        stderr = ctx.stderr
    else:
        if ctx.get_option('debug'):
            stderr = None
        else:
            stderr = subprocess.STDOUT

    p = subprocess.Popen(cmd, shell=True, stdout=stdout, stderr=stderr)
    out, err = p.communicate()
    ctx.ui.debug(_('return value for "{0}" is {1}').format(cmd, p.returncode))

    return p.returncode

######################
# Terminal functions #
######################

def get_terminal_size():
    try:
        ret = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, "1234")
    except IOError:
        rows = int(os.environ.get("LINES", 25))
        cols = int(os.environ.get("COLUMNS", 80))
        return rows, cols

    return struct.unpack("hh", ret)

def xterm_title(message):
    """Set message as console window title."""
    if "TERM" in os.environ and sys.stderr.isatty():
        terminalType = os.environ["TERM"]
        for term in ["xterm", "Eterm", "aterm", "rxvt", "screen", "kterm", "rxvt-unicode"]:
            if terminalType.startswith(term):
                sys.stderr.write("\x1b]2;" + str(message) + "\x07")
                sys.stderr.flush()
                break

def xterm_title_reset():
    """Reset console window title."""
    if "TERM" in os.environ:
        xterm_title("")

#############################
#   ncurses like functions  #
#############################
def initscr():
    """Clear and create a window"""
    printw("\x1b[s\x1bc")

def endsrc():
    """Clear and restore screen"""
    printw("\x1bc\x1b[u")

def move(x,y):
    """Move"""
    printw("\x1b[{};{}f".format(y,x))

def printw(msg=''):
    """Print clone"""
    sys.stdout.write(msg)
    sys.stdout.flush()

def mvprintw(x,y,msg=''):
    """Move and print"""
    move(x,y)
    printw(msg)

def noecho(enabled=True):
    if(ctx.get_option('no_color')==False):
        if(enabled):
            printw("\x1b[?25l")
        else:
            printw("\x1b[?25h")

def attron(attribute):
    """Attribute enable"""
    if(attribute=="A_NORMAL"):
        sys.stdout.write("\x1b[;0m")
    elif(attribute=="A_UNDERLINE"):
        sys.stdout.write("\x1b[4m")
    elif(attribute=="A_REVERSE"):
        sys.stdout.write("\x1b[7m")
    elif(attribute=="A_BLINK"):
        sys.stdout.write("\x1b[5m")
    elif(attribute=="A_DIM"):
        sys.stdout.write("\x1b[2m")
    elif(attribute=="A_BOLD"):
        sys.stdout.write("\x1b[1m")
    elif(attribute=="A_INVIS"):
        sys.stdout.write("\x1b[8m")
    elif(attribute=="C_BLACK"):
        sys.stdout.write("\x1b[30m")
    elif(attribute=="C_RED"):
        sys.stdout.write("\x1b[31m")
    elif(attribute=="C_GREEN"):
        sys.stdout.write("\x1b[32m")
    elif(attribute=="C_YELLOW"):
        sys.stdout.write("\x1b[33m")
    elif(attribute=="C_BLUE"):
        sys.stdout.write("\x1b[34m")
    elif(attribute=="C_MAGENTA"):
        sys.stdout.write("\x1b[35m")
    elif(attribute=="C_CYAN"):
        sys.stdout.write("\x1b[36m")
    elif(attribute=="C_WHITE"):
        sys.stdout.write("\x1b374m")
    elif(attribute=="B_BLACK"):
        sys.stdout.write("\x1b[40m")
    elif(attribute=="B_RED"):
        sys.stdout.write("\x1b[41m")
    elif(attribute=="B_GREEN"):
        sys.stdout.write("\x1b[42m")
    elif(attribute=="B_YELLOW"):
        sys.stdout.write("\x1b[43m")
    elif(attribute=="B_BLUE"):
        sys.stdout.write("\x1b[44m")
    elif(attribute=="B_MAGENTA"):
        sys.stdout.write("\x1b[45m")
    elif(attribute=="B_CYAN"):
        sys.stdout.write("\x1b[46m")
    elif(attribute=="B_WHITE"):
        sys.stdout.write("\x1b[47m")
    sys.stdout.flush()

def drawbox(x1,y1,x2,y2):
    """Draw box"""
    mvprintw(x1,y1,"╔")
    mvprintw(x1,y2,"╚")
    mvprintw(x2,y1,"╗")
    mvprintw(x2,y2,"╝")
    for i in range((x1+1),(x2-1)):
        mvprintw(i,y1,"═")
        mvprintw(i,y2,"═")
    for i in range((y1+1),(y2-1)):
        mvprintw(x1,i,"║")
        mvprintw(x2,i,"║")

#############################
# Path Processing Functions #
#############################

def splitpath(a):
    """split path into components and return as a list
    os.path.split doesn't do what I want like removing trailing /"""
    comps = a.split(os.path.sep)
    if comps[len(comps) - 1] == '':
        comps.pop()
    return comps

def makepath(comps, relative=False, sep=os.path.sep):
    """Reconstruct a path from components."""
    path = reduce(lambda x, y: x + sep + y, comps, '')
    if relative:
        return path[len(sep):]
    else:
        return path

def parentpath(a, sep=os.path.sep):
    # remove trailing '/'
    a = a.rstrip(sep)
    return a[:a.rfind(sep)]

def parenturi(a):
    return parentpath(a, '/')

def subpath(a, b):
    """Find if path a is before b in the directory tree."""
    return prefix(splitpath(a), splitpath(b))

def removepathprefix(prefix, path):
    """Remove path prefix a from b, finding the pathname rooted at a."""
    comps = remove_prefix(splitpath(prefix), splitpath(path))
    if len(comps) > 0:
        return join_path(*tuple(comps))
    else:
        return ""

def join_path(a, *p):
    """Join two or more pathname components.
    Python os.path.join cannot handle '/' at the start of latter components.
    """
    path = a
    for b in p:
        b = b.lstrip('/')
        if path == '' or path.endswith('/'):
            path += b
        else:
            path += '/' + b
    return path

####################################
# File/Directory Related Functions #
####################################

def check_file(_file, mode=os.F_OK):
    """Shorthand to check if a file exists."""
    if not os.access(_file, mode):
        raise FileError(_("File {} not found.").format(_file))
    return True

def ensure_dirs(path):
    """Make sure the given directory path exists."""
    if not os.path.exists(path):
        os.makedirs(path)

def clean_dir(path):
    """Remove all content of a directory."""
    if os.path.exists(path):
        shutil.rmtree(path)

def delete_file(path):
    if os.path.isfile(path):
        if os.path.exists(path):
            os.remove(path)

def creation_time(_file):
    """Return the creation time of the given file."""
    if check_file(_file):
        import time
        st = os.stat(_file)
        return time.localtime(st.st_ctime)

def dir_size(_dir):
    """Calculate the size of files under a directory."""
    # It's really hard to give an approximate value for package's
    # installed size. Gettin a sum of all files' sizes if far from
    # being true. Using 'du' command (like Debian does) can be a
    # better solution :(.
    # Not really, du calculates size on disk, this is much better

    if os.path.exists(_dir) and (not os.path.isdir(_dir) and not os.path.islink(_dir)):
        # so, this is not a directory but file..
        return os.path.getsize(_dir)

    if os.path.islink(_dir):
        return int(len(read_link(_dir)))

    def sizes():
        for root, dirs, files in os.walk(_dir):
            yield sum(
                [os.path.getsize(join_path(root, name)) for name in files if not os.path.islink(join_path(root, name))])

    return sum(sizes())

def copy_file(src, dest):
    """Copy source file to the destination file."""
    check_file(src)
    ensure_dirs(os.path.dirname(dest))
    shutil.copyfile(src, dest)

def copy_file_stat(src, dest):
    """Copy source file to the destination file with all stat info."""
    check_file(src)
    ensure_dirs(os.path.dirname(dest))
    shutil.copy2(src, dest)

def free_space(directory=None):
    """Returns the free space (x Byte) in the device. """
    if not directory:
        # Defaults to /
        directory = ctx.config.values.general.destinationdirectory
    _stat = os.statvfs(directory)
    free_space = _stat.f_bfree * _stat.f_bsize

    return free_space

def read_link(link):
    """Return the normalized path which is pointed by the symbolic link."""
    # tarfile module normalizes the paths pointed by symbolic links. This
    # causes problems as the file hashes and sizes are calculated before
    # this normalization.
    return os.path.normpath(os.readlink(link))

def is_ar_file(file_path):
    return open(file_path, 'rb').read(8) == '!<arch>\n'

def clean_ar_timestamps(ar_file):
    """Zero all timestamps in the ar files."""
    if not is_ar_file(ar_file):
        return
    content = open(ar_file).readlines()
    fp = open(ar_file, 'w')
    for line in content:
        pos = line.rfind(chr(32) + chr(96))
        if pos > -1 and line[pos - 57:pos + 2].find(chr(47)) > -1:
            line = line[:pos - 41] + '0000000000' + line[pos - 31:]
        fp.write(line)
    fp.close()

def calculate_hash(path):
    """Return a (path, hash) tuple for given path."""
    if os.path.islink(path):
        # For symlinks, path string is hashed instead of the content
        value = sha1_data(read_link(path))
        if not os.path.exists(path):
            ctx.ui.info(_("Including external link \"{}\"").format(path))
    elif os.path.isdir(path):
        ctx.ui.info(_("Including directory \"{}\"").format(path))
        value = None
    else:
        if path.endswith('.a'):
            # .a file content changes with each compile due to timestamps
            # We pad them with zeroes, thus hash will be stable
            clean_ar_timestamps(path)
        value = sha1_file(path)

    return path, value

def get_file_hashes(top, excludePrefix=None, removePrefix=None):
    """Yield (path, hash) tuples for given directory tree.

    Generator function iterates over a toplevel path and returns the
    (filePath, sha1Hash) tuples for all files. If excludePrefixes list
    is given as a parameter, function will exclude the filePaths
    matching those prefixes. The removePrefix string parameter will be
    used to remove prefix from filePath while matching excludes, if
    given.
    """

    def is_included(path):
        if excludePrefix:
            temp = remove_prefix(removePrefix, path)
            while temp != "/":
                if len([x for x in excludePrefix if fnmatch.fnmatch(temp, x)]) > 0:
                    return False
                temp = os.path.dirname(temp)
        return True

    # single file/symlink case
    if not os.path.isdir(top) or os.path.islink(top):
        if is_included(top):
            yield calculate_hash(top)
        return

    for root, dirs, files in os.walk(top):
        # Hash files and file symlinks
        for name in files:
            path = os.path.join(root, name)
            if is_included(path):
                yield calculate_hash(path)

        # Hash symlink dirs
        # os.walk doesn't enter them, we don't want to follow them either
        # but their name and hashes must be reported
        # Discussed in bug #339
        for name in dirs:
            path = os.path.join(root, name)
            if os.path.islink(path):
                if is_included(path):
                    yield calculate_hash(path)

        # Hash empty dir
        # Discussed in bug #340
        if len(files) == 0 and len(dirs) == 0:
            if is_included(root):
                yield calculate_hash(root)

def check_file_hash(filename, hash):
    """Check the file's integrity with a given hash."""
    return sha1_file(filename) == hash

def sha1_file(filename):
    """Calculate sha1 hash of file."""
    # Broken links can cause problem!
    try:
        m = hashlib.sha1()
        f = open(filename, 'rb')
        while True:
            # 256 KB seems ideal for speed/memory tradeoff
            # It wont get much faster with bigger blocks, but
            # heap peak grows
            block = f.read(256 * 1024)
            if len(block) == 0:
                # end of file
                break
            m.update(block)
            # Simple trick to keep total heap even lower
            # Delete the previous block, so while next one is read
            # we wont have two allocated blocks with same size
            del block
        return m.hexdigest()
    except IOError as e:
        if e.errno == 13:
            # Permission denied, the file doesn't have read permissions, skip
            raise FilePermissionDeniedError(_("You don't have necessary read permissions"))
        else:
            raise FileError(_("Cannot calculate SHA1 hash of \"{}\"").format(filename))

def sha1_data(data):
    """Calculate sha1 hash of given data."""
    m = hashlib.sha1()
    m.update(data.encode('utf-8'))
    return m.hexdigest()

def uncompress(patchFile, compressType="gz", targetDir=""):
    """Uncompress the file and return the new path."""
    formats = ("gz", "gzip", "bz2", "bzip2", "lzma", "xz")
    if compressType not in formats:
        raise Error(_("Compression type is not valid: '{}'").format(compressType))

    archive = inary.archive.Archive(patchFile, compressType)
    try:
        archive.unpack(targetDir)
    except Exception as msg:
        raise Error(_("Error while decompressing \"{0}\": {1}").format(patchFile, msg))

    # FIXME: Get file path from Archive instance
    filePath = join_path(targetDir, os.path.basename(patchFile))

    # remove suffix from file cause its uncompressed now
    extensions = {"gzip": "gz", "bzip2": "bz2"}
    extension = extensions.get(compressType, compressType)
    return filePath.split(".{}".format(extension))[0]

def check_patch_level(workdir, path):
    level = 0
    while path:
        if os.path.isfile("{0}/{1}".format(workdir, path)): return level
        if path.find("/") == -1: return None
        level += 1
        path = path[path.find("/") + 1:]

def do_patch(sourceDir, patchFile, level=0, name=None, reverse=False):
    """Apply given patch to the sourceDir."""
    cwd = os.getcwd()
    if os.path.exists(sourceDir):
        os.chdir(sourceDir)
    else:
        raise Error(_("ERROR: WorkDir ({}) does not exist\n").format(sourceDir))

    check_file(patchFile)

    if level is None:
        with open(patchFile) as patchfile:
            lines = patchfile.readlines()
            try:
                paths_m = [l.strip().split()[1] for l in lines if l.startswith("---") and "/" in l]
                try:
                    paths_p = [l.strip().split()[1] for l in lines if l.startswith("+++")]
                except IndexError:
                    paths_p = []
            except IndexError:
                pass
            else:
                if not paths_p:
                    paths_p = paths_m[:]
                    try:
                        paths_m = [l.strip().split()[1] for l in lines if l.startswith("***") and "/" in l]
                    except IndexError:
                        pass

                for path_p, path_m in zip(paths_p, paths_m):
                    if "/dev/null" in path_m and not len(paths_p) - 1 == paths_p.index(path_p): continue
                    level = check_patch_level(sourceDir, path_p)
                    if level is None and len(paths_m) - 1 == paths_m.index(path_m):
                        level = check_patch_level(sourceDir, path_m)
                    if not level is None:
                        ctx.ui.info(_("Detected patch level={0} for {1}").format(level, os.path.basename(patchFile)), verbose=True)
                        break

    if level is None:
        level = 0

    if name is None:
        name = os.path.basename(patchFile)

    if ctx.get_option('use_quilt'):
        patchesDir = join_path(sourceDir, ctx.const.quilt_dir_suffix)
        # Make sure sourceDir/patches directory exists and if not create one!
        if not os.path.exists(patchesDir):
            os.makedirs(patchesDir)
        # Import original patch into quilt tree
        (ret, out, err) = run_batch(
            'quilt import {0} -p {1} -P {2} \"{3}\"'.format(("-R" if reverse else ""), level, name, patchFile))
        # run quilt push to apply original patch into tree
        (ret, out, err) = run_batch('quilt push')
    else:
        # run GNU patch to apply original patch into tree
        (ret, out, err) = run_batch(
            "patch --remove-empty-files --no-backup-if-mismatch {0} -p{1} -i \"{2}\"".format(("-R" if reverse else ""),
                                                                                level, patchFile))

    if ret:
        if out is None and err is None:
            # Which means stderr and stdout directed so they are None
            raise Error(_("ERROR: patch (\"{}\") failed.").format(patchFile))
        else:
            raise Error(_("ERROR: patch (\"{0}\") failed: {1}").format(patchFile, out))

    os.chdir(cwd)

def strip_file(filepath, fileinfo, outpath):
    """Strip an elf file from debug symbols."""

    def run_strip(f, flags=""):
        p = os.popen("strip {0} {1}".format(flags, f))
        ret = p.close()
        if ret:
            ctx.ui.warning(_("\'strip\' command failed for file \"{}\"!").format(f))

    def run_chrpath(f):
        """ remove rpath info from binary """
        p = os.popen("chrpath -d {}".format(f))
        ret = p.close()
        if ret:
            ctx.ui.warning(_("\'chrpath\' command failed for file \"{}\"!").format(f))

    def save_elf_debug(f, o):
        """copy debug info into file.debug file"""
        p = os.popen("objcopy --only-keep-debug {0} {1}{2}".format(f, o, ctx.const.debug_file_suffix))
        ret = p.close()
        if ret:
            ctx.ui.warning(_("\'objcopy\' (keep-debug) command failed for file \"{}\"!").format(f))

        """mark binary/shared objects to use file.debug"""
        p = os.popen("objcopy --add-gnu-debuglink={0}{1} {2}".format(o, ctx.const.debug_file_suffix, f))
        ret = p.close()
        if ret:
            ctx.ui.warning(_("\'objcopy\' (add-debuglink) command failed for file \"{}\"!").format(f))

    if "current ar archive" in fileinfo:
        run_strip(filepath, "--strip-debug")
        return True

    elif re.search("SB\s+executable", fileinfo):
        if ctx.config.values.build.generatedebug:
            ensure_dirs(os.path.dirname(outpath))
            save_elf_debug(filepath, outpath)
        run_strip(filepath)
        # FIXME: removing RPATH also causes problems, for details see gelistirici mailing list - caglar10ur
        # run_chrpath(filepath)
        return True

    elif re.search("SB\s+shared object", fileinfo):
        if ctx.config.values.build.generatedebug:
            ensure_dirs(os.path.dirname(outpath))
            save_elf_debug(filepath, outpath)
        run_strip(filepath, "--strip-unneeded")
        # run_chrpath(filepath)
        # FIXME: warn for TEXTREL
        return True

    return False

def partition_freespace(directory):
    """Return free space of given directory's partition."""
    st = os.statvfs(directory)
    return st.f_frsize * st.f_bfree

########################################
# Package/Repository Related Functions #
########################################

def package_filename(name, version, release, distro_id=None, arch=None):
    """Return a filename for a package with the given information. """

    if distro_id is None:
        distro_id = ctx.config.values.general.distribution_id

    if arch is None:
        arch = ctx.config.values.general.architecture

    fn = "-".join((name, version, release, distro_id, arch))
    fn += ctx.const.package_suffix

    return fn

def parse_package_name_legacy(package_name):
    """Separate package name and version string for package formats <= 1.1.

    example: tasma-1.0.3-5-2 -> (tasma, 1.0.3-5-2)
    """
    # We should handle package names like 855resolution
    name = []
    for part in package_name.split("-"):
        if name != [] and part[0] in digits:
            break
        else:
            name.append(part)
    name = "-".join(name)
    version = package_name[len(name) + 1:]

    return name, version

def parse_package_name(package_name):
    """Separate package name and version string.

    example: tasma-1.0.3-5-p11-x86_64 -> (tasma, 1.0.3-5)
    """

    # Strip extension if exists
    if package_name.endswith(ctx.const.package_suffix):
        package_name = remove_suffix(ctx.const.package_suffix, package_name)

    try:
        name, version, release, distro_id, arch = package_name.rsplit("-", 4)

        # Arch field cannot start with a digit. If a digit is found,
        # the package might have an old format. Raise here to call
        # the legacy function.
        if not arch or arch[0] in digits:
            raise ValueError

    except ValueError:
        try:
            return parse_package_name_legacy(package_name)
        except:
            raise Error(_("Invalid package name: \"{}\"").format(package_name))

    return name, "{0}-{1}".format(version, release)

def parse_package_dir_path(package_name):
    name = parse_package_name(package_name)[0]
    if name.split("-").pop() in ["devel", "32bit", "doc", "docs", "pages", "static", "dbginfo", "32bit-dbginfo",
                                 "userspace"]: name = name[:-1 - len(name.split("-").pop())]
    return "{0}/{1}".format(name[0:4].lower() if name.startswith("lib") and len(name) > 3 else name.lower()[0],
                            name.lower())

def parse_delta_package_name_legacy(package_name):
    """Separate delta package name and release infos for package formats <= 1.1.

    example: tasma-5-7.delta.inary -> (tasma, 5, 7)
    """
    name, build = parse_package_name(package_name)
    build = build[:-len(ctx.const.delta_package_suffix)]
    buildFrom, buildTo = build.split("-")

    return name, buildFrom, buildTo

def parse_delta_package_name(package_name):
    """Separate delta package name and release infos

    example: tasma-5-7-p11-x86_64.delta.inary -> (tasma, 5, 7)
    """

    # Strip extension if exists
    if package_name.endswith(ctx.const.delta_package_suffix):
        package_name = remove_suffix(ctx.const.delta_package_suffix,
                                     package_name)

    try:
        name, source_release, target_release, distro_id, arch = \
            package_name.rsplit("-", 4)

        # Arch field cannot start with a digit. If a digit is found,
        # the package might have an old format. Raise here to call
        # the legacy function.
        if not arch or arch[0] in digits:
            raise ValueError

    except ValueError:
        try:
            return parse_delta_package_name_legacy(package_name)
        except:
            raise Error(_("Invalid delta package name: \"{}\"").format(package_name))

    return name, source_release, target_release

def split_package_filename(filename):
    """Split fields in package filename.

    example: tasma-1.0.3-5-p11-x86_64.inary -> (tasma, 1.0.3, 5, p11, x86_64)
    """

    # Strip extension if exists
    if filename.endswith(ctx.const.package_suffix):
        filename = remove_suffix(ctx.const.package_suffix, filename)

    try:
        name, version, release, distro_id, arch = filename.rsplit("-", 4)

        # Arch field cannot start with a digit. If a digit is found,
        # the package might have an old format.
        if not arch or arch[0] in digits:
            raise ValueError

    except ValueError:
        name, version = parse_package_name_legacy(filename)
        version, release, build = split_version(version)
        distro_id = arch = None

    return name, version, release, distro_id, arch

def split_delta_package_filename(filename):
    """Split fields in delta package filename.

    example: tasma-5-7-p11-x86_64.delta.inary -> (tasma, 5, 7, p11, x86-64)
    """

    # Strip extension if exists
    if filename.endswith(ctx.const.delta_package_suffix):
        filename = remove_suffix(ctx.const.delta_package_suffix, filename)

    try:
        name, source_release, target_release, distro_id, arch = \
            filename.rsplit("-", 4)

        # Arch field cannot start with a digit. If a digit is found,
        # the package might have an old format.
        if not arch or arch[0] in digits:
            raise ValueError

    except ValueError:
        # Old formats not supported
        name = parse_delta_package_name_legacy(filename)[0]
        source_release = target_release = None

    return name, source_release, target_release, distro_id, arch

def split_version(package_version):
    """Split version, release and build parts of a package version

    example: 1.0.3-5-2 -> (1.0.3, 5, 2)
    """
    version, sep, release_and_build = package_version.partition("-")
    release, sep, build = release_and_build.partition("-")
    return version, release, build

def filter_latest_packages(package_paths):
    """ For a given inary package paths list where there may also be multiple versions
        of the same package, filters only the latest versioned ones """

    import inary.version

    latest = {}
    for path in package_paths:

        name, version = parse_package_name(os.path.basename(path[:-len(ctx.const.package_suffix)]))

        if name in latest:
            l_version, l_release, l_build = split_version(latest[name][1])
            r_version, r_release, r_build = split_version(version)

            try:
                l_release = int(l_release)
                r_release = int(r_release)

                l_build = int(l_build) if l_build else None
                r_build = int(r_build) if r_build else None

            except ValueError:
                continue

            if l_build and r_build:
                if l_build > r_build:
                    continue

            elif l_release > r_release:
                continue

            elif l_release == r_release:
                l_version = inary.version.make_version(l_version)
                r_version = inary.version.make_version(r_version)

                if l_version > r_version:
                    continue

        if version:
            latest[name] = (path, version)

    return [x[0] for x in list(latest.values())]

def colorize(msg, color):
    """Colorize the given message for console output"""
    if color in ctx.const.colors and not ctx.get_option('no_color'):
        return str(ctx.const.colors[color] + msg + ctx.const.colors['default'])
    else:
        return str(msg)

def config_changed(config_file):
    fpath = join_path(ctx.config.dest_dir(), config_file.path)
    if os.path.exists(fpath) and not os.path.isdir(fpath):
        if os.path.islink(fpath):
            f = os.readlink(fpath)
            if os.path.exists(f) and sha1_data(f) != config_file.hash:
                return True
        else:
            if sha1_file(fpath) != config_file.hash:
                return True
    return False

# recursively remove empty dirs starting from dirpath
def rmdirs(dirpath):
    if os.path.isdir(dirpath) and not os.listdir(dirpath):
        ctx.ui.info(_("Removing empty dir: \"{}\"").format(dirpath),verbose=True)
        os.rmdir(dirpath)
        rmdirs(os.path.dirname(dirpath))

# Python regex sucks
# http://mail.python.org/pipermail/python-list/2009-January/523704.html
def letters():
    start = end = None
    result = []
    for index in range(sys.maxunicode + 1):
        c = chr(index)
        if unicodedata.category(c)[0] == 'L':
            if start is None:
                start = end = c
            else:
                end = c
        elif start:
            if start == end:
                result.append(start)
            else:
                result.append(start + "-" + end)
            start = None
    return ''.join(result)

def get_kernel_option(option):
    """Get a dictionary of args for the given kernel command line option"""

    args = {}

    try:
        cmdline = open("/proc/cmdline").read().split()
    except IOError:
        return args

    for cmd in cmdline:
        if "=" in cmd:
            optName, optArgs = cmd.split("=", 1)
        else:
            optName = cmd
            optArgs = ""

        if optName == option:
            for arg in optArgs.split(","):
                if ":" in arg:
                    k, v = arg.split(":", 1)
                    args[k] = v
                else:
                    args[arg] = ""

    return args

def get_cpu_count():
    """
    This function part of portage
    Copyright 2015 Gentoo Foundation
    Distributed under the terms of the GNU General Public License v2

    Using:
    Try to obtain the number of CPUs available.
    @return: Number of CPUs or None if unable to obtain.
    """
    try:
        import multiprocessing
        return multiprocessing.cpu_count()
    except (ImportError, NotImplementedError):
        return None

def get_vm_info():
    vm_info = {}

    if platform.system() == 'Linux':
        try:
            proc = subprocess.Popen(["free"],
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT)
        except OSError:
            pass
        output = proc.communicate()[0].decode('utf-8')
        if proc.wait() == os.EX_OK:
            for line in output.splitlines():
                line = line.split()
                if len(line) < 2:
                    continue
                if line[0] == "Mem:":
                    try:
                        vm_info["ram.total"] = int(line[1]) * 1024
                    except ValueError:
                        pass
                    if len(line) > 3:
                        try:
                            vm_info["ram.free"] = int(line[3]) * 1024
                        except ValueError:
                            pass
                elif line[0] == "Swap:":
                    try:
                        vm_info["swap.total"] = int(line[1]) * 1024
                    except ValueError:
                        pass
                    if len(line) > 3:
                        try:
                            vm_info["swap.free"] = int(line[3]) * 1024
                        except ValueError:
                            pass
    else:
        try:
            proc = subprocess.Popen(["sysctl", "-a"],
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT)
        except OSError:
            pass
        else:
            output = proc.communicate()[0].decode('utf-8')
            if proc.wait() == os.EX_OK:
                for line in output.splitlines():
                    line = line.split(":", 1)
                    if len(line) != 2:
                        continue


code:
# -*- coding: utf-8 -*-
#
# Main fork Pisi: Copyright (C) 2005 - 2011, Tubitak/UEKAE
#
# Copyright (C) 2016 - 2018, Suleyman POYRAZ (Zaryob)
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

"""version structure"""

import gettext

__trans = gettext.translation('inary', fallback=True)
_ = __trans.gettext

import inary.errors

# Basic rule is:
# p > (no suffix) > m > rc > pre > beta > alpha
# m: milestone. this was added for OO.o
# p: patch-level
__keywords = (
    ("alpha", -5),
    ("beta", -4),
    ("pre", -3),
    ("rc", -2),
    ("m", -1),
    ("p", 1),
)

class InvalidVersionError(inary.errors.Error):
    pass

def __make_version_item(v):
    try:
        return int(v), None
    except ValueError:
        return int(v[:-1]), v[-1]

def make_version(version):
    ver, sep, suffix = version.partition("_")
    try:
        if sep:
            # "s" is a string greater than the greatest keyword "rc"
            if "a" <= suffix <= "s":
                for keyword, value in __keywords:
                    if suffix.startswith(keyword):
                        return list(map(__make_version_item, ver.split("."))), value, \
                               list(map(__make_version_item, suffix[len(keyword):].split(".")))
                else:
                    # Probably an invalid version string. Reset ver string
                    # to raise an exception in __make_version_item function.
                    ver = ""
            else:
                return list(map(__make_version_item, ver.split("."))), 0, \
                       list(map(__make_version_item, suffix.split(".")))

        return list(map(__make_version_item, ver.split("."))), 0, [(0, None)]

    except ValueError:
        raise InvalidVersionError(_("Invalid version string: '{}'").format(version))

class Version(object):
    __slots__ = ("__version", "__version_string")

    @staticmethod
    def valid(version):
        try:
            make_version(version)
        except InvalidVersionError:
            return False
        return True

    def __init__(self, verstring):
        self.__version_string = verstring
        self.__version = make_version(verstring)

    def string(self):
        return self.__version_string

    def compare(self, ver):
        # In old code it was written with cmp().
        if isinstance(ver, str):
            return (self.__version > make_version(ver)) - (self.__version < make_version(ver))

        return (self.__version > ver.__version) - (self.__version < ver.__version)

    def __lt__(self, rhs):
        if isinstance(rhs, str):
            return self.__version < make_version(rhs)

        return self.__version < rhs.__version

    def __le__(self, rhs):
        if isinstance(rhs, str):
            return self.__version <= make_version(rhs)

        return self.__version <= rhs.__version

    def __gt__(self, rhs):
        if isinstance(rhs, str):
            return self.__version > make_version(rhs)

        return self.__version > rhs.__version

    def __ge__(self, rhs):
        if isinstance(rhs, str):
            return self.__version >= make_version(rhs)

        return self.__version >= rhs.__version

    def __eq__(self, rhs):
        if isinstance(rhs, str):
            return self.__version_string == rhs

        return self.__version_string == rhs.__version_string

    def __str__(self):
        return self.__version_string