class TemporaryDirectory(object):
    def __init__(self):
    def __del__(self): class Package(object):
    """
    Class to store full package name: category/package-version
    """
    __slots__ = ('category', 'name', 'version')

    def __init__(self, category, package, version):
        self.category = category
        self.name = package
        self.version = version

    def __str__(self):
        return self.category + '/' + self.name + '-' + self.version

    def __eq__(self, other):
        return self.category == other.category and \
            self.name == other.name and \
            self.version == other.version

    def __hash__(self):
        return hash(self.category + self.name + self.version)

    def serialize(self):
        return [self.category, self.name, self.version]

    @classmethod
    def deserialize(cls, value):
        return Package(*value) class elist(list):
    '''Custom list type which adds a customized __str__()
    and takes an optional separator argument

    elist() -> new empty elist
    elist(iterable) -> new elist initialized from iterable's items
    elist(separator='\\n\\t') -> new empty elist with
        newline & tab indented str(x) output
    elist(iterable, ' ') -> new elist initialized from iterable's items
        with space separated str(x) output
    '''

    __slots__ = ('_sep_')

    def __init__(self, iterable=None, separator=' '):
        '''
        iterable: initialize from iterable's items
        separator: string used to join list members with for __str__()
        '''
        list.__init__(self, iterable or [])
        self._sep_ = separator

    def __str__(self):
        '''Custom output function
        'x.__str__() <==> str(separator.join(x))'
        '''
        return self._sep_.join(map(str, self)) def fast_manifest(directory):
    """
    Digest package directory.
    This function is intended to be used in place of repoman manifest,
    as it is to slow.

    Args:
        directory: Directory.
    """
    manifest = []
    metadata = os.path.join(directory, "metadata.xml")

    for aux in glob.glob(os.path.join(directory, "files/*")):
        manifest.append(ManifestEntry(os.path.dirname(aux),
                            os.path.basename(aux), "AUX"))
    for ebuild in glob.glob(os.path.join(directory, "*.ebuild")):
        manifest.append(ManifestEntry(directory,
                            os.path.basename(ebuild), "EBUILD"))
    if (os.path.isfile(metadata)):
        manifest.append(ManifestEntry(directory, "metadata.xml", "MISC"))

    manifest = [" ".join([m.ftype, m.name, m.size,
                          "SHA256", m.sha256, "SHA512", m.sha512,
                          "WHIRLPOOL", m.whirlpool])
                for m in manifest]

    with open(os.path.join(directory, "Manifest"), 'w') as f:
        f.write('\n'.join(manifest) + '\n') class FileJSON(FileJSONData):
    """
    Class for JSON files. Supports custom JSON serialization
    provided by g_sorcery.serialization.
    """

    def read_content(self):
        """
        Read JSON file.
        """
        content = {}
        with open(self.path, 'r') as f:
            content = json.load(f, object_hook=deserializeHook)
        return content

    def write_content(self, content):
        """
        Write JSON file.
        """
        with open(self.path, 'w') as f:
            json.dump(content, f, indent=2, sort_keys=True, cls=JSONSerializer) class DependencyError(GSorceryError):
    pass class DigestError(GSorceryError):
    pass class InvalidKeyError(DBError):
    pass class Logger(object):
    """
    A simple logger object. Uses portage out facilities.
    """
    def __init__(self):
        self.out = portage.output.EOutput()

    def error(self, message):
        self.out.eerror(message)

    def info(self, message):
        self.out.einfo(message)

    def warn(self, message):
        self.out.ewarn(message) class PackageManager(object):
class Portage(PackageManager):
    def __init__(self):
    def run_command(self, *args):
    def install(self, pkgname, *args):
    def __init__(self):
    def install(self, pkgname, *args): class PackageDB(object):
    """
    Package database.
    It uses DBLayout class to manipulate files that
    contain DB stored on disk.

    There are two versions of DB layout now:
        0 -- legacy version
        1 -- new version that supports DB structure versioning

    DB structure itself has two versions:
        0 -- legacy version, categories contain dictionary package name: versions dict
        1 -- actual version, corresponds to the DB in memory:
            DB is a dictionary with categories as keys.
            Each category contains a dictionary with two entries:
                common_data -- fields common to all the packages
                packages -- dictionary with packages (content of category dictionary in v. 0)

    For DB layout v. 0 only DB structure v. 0 is possible.
    """

    class Iterator(object):
        """
        Iterator class over the package database.
        """
        def __init__(self, package_db):
            self.cats_iter = iter(package_db.database.items())
            try:
                self.cat_name, self.cat_data = next(self.cats_iter)
            except StopIteration:
                self.set_to_end()
                return

            if not self.cat_data:
                self.set_to_end()
                return

            self.pkgs_iter = iter(self.cat_data['packages'].items())
            try:
                self.pkg_name, self.pkg_data = next(self.pkgs_iter)
            except StopIteration:
                self.set_to_end()
                return

            if not self.pkg_data:
                self.set_to_end()
                return

            self.vers_iter = iter(self.pkg_data.items())

        def set_to_end(self):
            self.cat_name, self.cat_data = None, None
            self.pkgs_iter = None
            self.pkg_name, self.pkg_data = None, None
            self.vers_iter = None

        def __iter__(self):
            return self

        if py2k:
            def next(self):
                if not self.vers_iter or not self.pkgs_iter:
                    raise StopIteration

                ver, ebuild_data = None, None
                while not ver:
                    try:
                        ver, ebuild_data = next(self.vers_iter)
                    except StopIteration:
                        ver, ebuild_data = None, None
                    if not ver:
                        try:
                            self.pkg_name, self.pkg_data = next(self.pkgs_iter)
                            self.vers_iter = iter(self.pkg_data.items())
                        except StopIteration:
                            self.cat_name, self.cat_data = next(self.cats_iter)
                            self.pkgs_iter = iter(self.cat_data['packages'].items())
                            self.pkg_name, self.pkg_data = next(self.pkgs_iter)
                            self.vers_iter = iter(self.pkg_data.items())

                ebuild_data = dict(ebuild_data)
                ebuild_data.update(self.cat_data['common_data'])
                return (Package(self.cat_name, self.pkg_name, ver), ebuild_data)

        else:
            def __next__(self):
                if not self.vers_iter or not self.pkgs_iter:
                    raise StopIteration

                ver, ebuild_data = None, None
                while not ver:
                    try:
                        ver, ebuild_data = next(self.vers_iter)
                    except StopIteration:
                        ver, ebuild_data = None, None
                    if not ver:
                        try:
                            self.pkg_name, self.pkg_data = next(self.pkgs_iter)
                            self.vers_iter = iter(self.pkg_data.items())
                        except StopIteration:
                            self.cat_name, self.cat_data = next(self.cats_iter)
                            self.pkgs_iter = iter(self.cat_data['packages'].items())
                            self.pkg_name, self.pkg_data = next(self.pkgs_iter)
                            self.vers_iter = iter(self.pkg_data.items())

                ebuild_data = dict(ebuild_data)
                ebuild_data.update(self.cat_data['common_data'])
                return (Package(self.cat_name, self.pkg_name, ver), ebuild_data)


    def __init__(self, directory,
                 persistent_datadir = None,
                 preferred_layout_version=1,
                 preferred_db_version=1,
                 preferred_category_format=JSON_FILE_SUFFIX):

        if preferred_layout_version == 0 \
           and preferred_db_version != 0:
            raise DBStructureError("Wrong DB version: " + str(preferred_db_version) + \
                                   ", with DB layout version 0 it can be only 0")

        if not preferred_db_version in SUPPORTED_DB_STRUCTURES:
            raise DBStructureError("Unsupported DB version: " + str(preferred_db_version))

        if not preferred_layout_version in SUPPORTED_DB_LAYOUTS:
            raise DBLayoutError("unsupported DB layout version: " + str(preferred_layout_version))

        if not preferred_category_format in SUPPORTED_FILE_FORMATS:
            raise DBLayoutError("unsupported packages file format: " + preferred_category_format)

        self.logger = Logger()
        self.directory = os.path.abspath(directory)

        self.persistent_datadir = persistent_datadir
        if self.persistent_datadir is not None:
            self.persistent_datadir = os.path.abspath(self.persistent_datadir)

        self.preferred_layout_version = preferred_layout_version
        self.preferred_db_version = preferred_db_version
        self.preferred_category_format = preferred_category_format
        self.db_layout = DBLayout(self.directory)
        self.reset_db()


    def __iter__(self):
        return PackageDB.Iterator(self)


    def reset_db(self):
        """
        Reset database.
        """
        self.database = {}
        self.categories = {}


    def sync(self, db_uri, repository_config = None, sync_method="tgz"):
        """
        Synchronize local database with remote database.

        Args:
            db_uri: URI for synchronization with remote database.
            repository_config: repository config.
            sync_method: sync method (tgz or git).
        """
        if repository_config is None:
            repository_config = {}

        try:
            syncer_cls = SUPPORTED_SYNCERS[sync_method]
        except KeyError:
            raise SyncError('unsupported sync method: ' + sync_method)
        if self.persistent_datadir is not None:
            remotedb_dir = os.path.join(self.persistent_datadir, 'remote')
        else:
            remotedb_dir = None
        syncer = syncer_cls(remotedb_dir)
        synced_data = syncer.sync(db_uri, repository_config)

        tempdb_dir = synced_data.get_path()
        tempdb = PackageDB(tempdb_dir)

        tempdb.db_layout.check_manifest()

        self.logger.info("copy files to an actual database")
        self.clean()
        copy_all(tempdb_dir, self.directory)

        self.db_layout.check_manifest()

        del synced_data


    def clean(self):
        """
        Clean database.
        """
        self.db_layout.clean()
        self.reset_db()


    def write(self):
        """
        Write and digest database.
        """
        if self.database:
            self.logger.info("writing database...")

        metadata = {'db_version': self.preferred_db_version,
                    'layout_version': self.preferred_layout_version,
                    'category_format': self.preferred_category_format}

        if self.preferred_db_version == 0:
            packages = dict(self.database)
            for category, cat_data in packages.items():
                for _, versions in cat_data['packages'].items():
                    for version, ebuild_data in versions.items():
                        ebuild_data.update(cat_data['common_data'])
                packages[category] = cat_data['packages']
        else:
            packages = dict(self.database)

        self.db_layout.write(metadata, self.categories, packages)

        if self.database:
            self.logger.info("database written")


    def read(self):
        """
        Read database.
        """
        metadata, self.categories, packages = self.db_layout.read()

        db_version = metadata['db_version']
        self.database = packages
        if db_version == 0:
            for category, cat_data in self.database.items():
                self.database[category] = {'common_data': {}, 'packages': cat_data}
        elif db_version == 1:
            pass
        else:
            raise DBStructureError("Unsupported DB version: " + str(db_version))


    def add_category(self, category, description=None):
        """
        Add a category.

        Args:
            category: Category name.
            description: Category description.
        """
        if not description:
            description = {}
        self.categories[category] = description


    def set_common_data(self, category, common_data):
        """
        Set common data for a category.

        Args:
            category: Category name.
            common_data: Category common data.
        """
        if not category in self.categories:
            raise InvalidKeyError('Non-existent category: ' + category)

        if not category in self.database:
            self.database[category] = {'common_data': common_data, 'packages': {}}
        else:
            self.database[category]['common_data'] = common_data


    def get_common_data(self, category):
        """
        Get common data for a category.

        Args:
            category: Category name.

        Returns:
            Dictionary with category common data.
        """
        if not category in self.categories:
            raise InvalidKeyError('Non-existent category: ' + category)

        if not category in self.database:
            return {}
        else:
            return self.database[category]['common_data']


    def add_package(self, package, ebuild_data=None):
        """
        Add a package.

        Args:
            package: g_collections.Package instance.
            ebuild_data: Dictionary with package description.
        """
        if not ebuild_data:
            ebuild_data = {}

        category = package.category
        name = package.name
        version = package.version

        if not category or not name or not version:
            raise DBError("wrong package: " + str(package))

        if not category in self.categories:
            raise InvalidKeyError('Non-existent category: ' + category)

        if not category in self.database:
            self.database[category] = {'common_data': {}, 'packages': {}}

        if not name in self.database[category]['packages']:
            self.database[category]['packages'][name] = {}

        self.database[category]['packages'][name][version] = ebuild_data


    def list_categories(self):
        """
        List all categories.

        Returns:
            List with category names.
        """
        return list(self.categories)


    def in_category(self, category, name):
        """
        Tests whether a package is in a given category.

        Args:
            category: Category name.
            name: Package name.

        Returns:
            Boolean value.
        """
        if not category or (not category in self.categories):
            raise InvalidKeyError('No such category: ' + category)

        if not category in self.database:
            return False

        return name in self.database[category]['packages']


    def list_package_names(self, category):
        """
        List package names in a category.

        Args:
            category: Category name.

        Returns:
            List of package names.
        """
        if not category or (not category in self.categories):
            raise InvalidKeyError('No such category: ' + category)

        if not category in self.database:
            return []

        return list(self.database[category]['packages'])


    def list_catpkg_names(self):
        """
        List category/package entries.

        Returns:
            List with category/package entries.
        """
        result = []
        for category, cat_data in self.database.items():
            for name in cat_data['packages']:
                result.append(category + '/' + name)
        return result


    def list_package_versions(self, category, name):
        """
        List package versions.

        Args:
            category: Category name.
            name: package name.

        Returns:
            List of package versions.
        """
        if not category or (not category in self.categories):
            raise InvalidKeyError('No such category: ' + category)

        if not category in self.database \
           or not name in self.database[category]['packages']:
            raise InvalidKeyError('No such package: ' + category + '/' + name)

        return list(self.database[category]['packages'][name])


    def list_all_packages(self):
        """
        List all packages in a database.

        Returns:
            List of g_collections.Package instances.
        """
        result = []
        for category, cat_data in self.database.items():
            for name, versions in cat_data['packages'].items():
                for version in versions:
                    result.append(Package(category, name, version))
        return result


    def get_package_description(self, package):
        """
        Get package ebuild data.

        Args:
            package: g_collections.Package instance.

        Returns:
            Dictionary with package ebuild data.
        """
        #a possible exception should be catched in the caller
        desc = dict(self.database[package.category]['packages']\
                    [package.name][package.version])
        desc.update(self.database[package.category]['common_data'])
        return desc


    def get_max_version(self, category, name):
        """
        Get the recent available version of a package.

        Args:
            category: Category name.
            name: package name.

        Returns:
            The recent version of a package.
        """
        if not category or (not category in self.categories):
            raise InvalidKeyError('No such category: ' + category)

        if not category in self.database \
           or not name in self.database[category]['packages']:
            raise InvalidKeyError('No such package: ' + category + '/' + name)

        pkgname = category + '/' + name
        versions = list(self.database[category]['packages'][name])
        max_ver = versions[0]
        for version in versions[1:]:
            if portage.pkgcmp(portage.pkgsplit(pkgname + '-' + version),
                              portage.pkgsplit(pkgname + '-' + max_ver)) > 0:
                max_ver = version
        return max_ver
import argparse
import os
import portage
from .compatibility import configparser
from .g_collections import Package, elist
from .fileutils import fast_manifest, FileJSON
from .exceptions import DependencyError, DigestError, InvalidKeyError
from .logger import Logger
from .mangler import package_managers
from .package_db import PackageDB

        Args:
            overlay: Overlay directory.
            eclasses: List of eclasses.
        """
        self.logger.info("eclasses generation")
        eclass_g = self.eclass_g_class()
        path = os.path.join(overlay, 'eclass')
        if not os.path.exists(path):
            os.makedirs(path)
        for eclass in eclasses:
            self.logger.info("    generating " + eclass + " eclass")
            source = eclass_g.generate(eclass)
            with open(os.path.join(path, eclass + '.eclass'), 'w') as f:
                f.write('\n'.join(source))


    def get_dependencies(self, package_db, pkgname):
        """
        Get dependencies for a given package.

        Args:
            package_db: Database.
            pkgname: package name (string).

        Returns:
            A set containing dependencies (instances of Package).
        Package version is ignored currently and a returned set contains all
        the versions of packages pkgname depends on.
        """
        parts = pkgname.split('/')
        category = None
        if len(parts) == 1:
            name = parts[0]
        elif len(parts) == 2:
            category = parts[0]
            name = parts[1]
        else:
            error = 'bad package name: ' + pkgname
            self.logger.error(error + '\n')
            raise DependencyError(error)

        if not category:
            all_categories = package_db.list_categories()
            categories = []
            for cat in all_categories:
                if package_db.in_category(cat, name):
                    categories.append(cat)

            if not len(categories):
                error = 'no package with name ' \
                                  + pkgname + ' found'
                self.logger.error(error + '\n')
                raise DependencyError(error)

            if len(categories) > 1:
                self.logger.error('ambiguous packagename: ' + pkgname + '\n')
                self.logger.error('please select one of' \
                                  + 'the following packages:\n')
                for cat in categories:
                    self.logger.error('    ' + cat + '/' + pkgname + '\n')
                raise DependencyError("ambiguous packagename")

            category = categories[0]
        versions = package_db.list_package_versions(category, name)
        dependencies = set()
        for version in versions:
            dependencies |= self.solve_dependencies(package_db,
                                    Package(category, name, version))[0]
        return dependencies

    def solve_dependencies(self, package_db, package,
                           solved_deps=None, unsolved_deps=None):
        """
        Solve dependencies.

        Args:
            package_db: Package database.
            package: A package we want to solve dependencies for.
            solved_deps: List of solved dependencies.
            unsolved_deps: List of dependencies to be solved.

        Returns:
            A pair (solved_deps, unsolved_deps).

        Note:
            Each dependency is an object of class g_collections.Dependency.
        """
        if not solved_deps:
            solved_deps = set()
        if not unsolved_deps:
            unsolved_deps = set()
        if package in solved_deps:
            return solved_deps
        if package in unsolved_deps:
            error = 'circular dependency for ' + package.category + '/' + \
              package.name + '-' + package.version
            raise DependencyError(error)
        unsolved_deps.add(package)
        found = True
        try:
            desc = package_db.get_package_description(package)
        except KeyError:
            found = False
        if not found:
            error = "package " + package.category + '/' + \
                package.name + '-' + package.version + " not found"
            self.logger.error(error)
            # at the moment ignore unsolved dependencies, as those deps can be in other repo
            # or can be external: portage will catch it
            unsolved_deps.remove(package)
            return (solved_deps, unsolved_deps)

        dependencies = desc["dependencies"]
        for pkg in dependencies:
            try:
                versions = package_db.list_package_versions(pkg.category,
                                                        pkg.package)
                for version in versions:
                    solved_deps, unsolved_deps = self.solve_dependencies(package_db,

                                    Package(pkg.category, pkg.package, version),