From 999420dcd9b5f8b6866f2073f812a9f7ae8d79e6 Mon Sep 17 00:00:00 2001 From: Mike Hiretsky Date: Thu, 19 Oct 2017 12:39:49 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=20Eix=20=D0=B4=D0=BB=D1=8F=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=D0=B0=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=D0=BB=D0=B8=D1=87=D0=BD=D1=8B=D0=BC=D0=B8=20=D0=BF=D0=B0?= =?UTF-8?q?=D1=80=D1=81=D0=B5=D1=80=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pym/calculate/lib/utils/portage.py | 235 ++++++++++++++++++++++++++--- 1 file changed, 212 insertions(+), 23 deletions(-) diff --git a/pym/calculate/lib/utils/portage.py b/pym/calculate/lib/utils/portage.py index 2e8d1cf..ed94162 100644 --- a/pym/calculate/lib/utils/portage.py +++ b/pym/calculate/lib/utils/portage.py @@ -38,7 +38,7 @@ from collections import Mapping, defaultdict from .common import getTupleVersion from contextlib import closing from functools import total_ordering -from itertools import ifilter, imap, chain +from itertools import ifilter, imap, chain, groupby Colors = TextState.Colors import glob @@ -762,6 +762,7 @@ class Eix(object): Xml = '--xml' Upgrade = '--upgrade' TestObsolete = '--test-obsolete' + Exact = "--exact" default_options = [Option.Xml] @@ -770,20 +771,22 @@ class Eix(object): self.package = list(package) else: self.package = [package] - self.options = list(options) + self.package + self.default_options + self._options = options + self.no_default = kwargs.get('no_default', False) if not kwargs.get('all_versions', False): - self.__get_versions = self._get_versions - self._get_versions = self._get_best_version + self.parser = EixFullnameParserBestVersion() + else: + self.parser = EixFullnameParser() - def _get_best_version(self, et): - ret = None - for ver in ifilter(lambda x: x.find('mask') is None, - et.iterfind('version')): - ret = ver.attrib['id'] - yield ret + @property + def options(self): + if self.no_default: + return list(self._options) + self.package + else: + return list(self._options) + self.package + self.default_options def _process(self): - return process(self.cmd, *self.options) + return process(self.cmd, *self.options, env={"LANG": "C"}) def get_output(self): """ @@ -796,31 +799,153 @@ class Eix(object): """ Получить список пакетов """ - return list(self._parseXml(self.get_output())) + return list(self.parser.parseXml(self.get_output())) - def _get_versions(self, et): + +class EixParser(object): + """ + Парсер XML вывода от eix + """ + def parseXml(self, buffer): + try: + eix_xml = ET.fromstring(buffer) + return self.get_categories(eix_xml) + except ET.ParseError: + return iter(()) + + def get_categories(self, et): + raise StopIteration() + + +class EixFullnameParser(EixParser): + """ + Получить все версии пакета + """ + def get_versions(self, et): for ver in et.iterfind('version'): yield ver.attrib['id'] - def _get_packages(self, et): + def get_packages(self, et): for pkg in et: - for version in self._get_versions(pkg): + for version in self.get_versions(pkg): if version: yield "%s-%s" % (pkg.attrib['name'], version) else: yield pkg.attrib['name'] - def _get_categories(self, et): + def get_categories(self, et): for category in et: - for pkg in self._get_packages(category): + for pkg in self.get_packages(category): yield EmergePackage("%s/%s" % (category.attrib['name'], pkg)) - def _parseXml(self, buffer): - try: - eix_xml = ET.fromstring(buffer) - return self._get_categories(eix_xml) - except ET.ParseError: - return iter(()) + + +class EixFullnameParserBestVersion(EixFullnameParser): + """ + Получить только одну максимальную версию пакета + """ + def get_versions(self, et): + ret = None + for ver in ifilter(lambda x: x.find('mask') is None, + et.iterfind('version')): + ret = ver.attrib['id'] + yield ret + + +class PackageVersionInfo(object): + """ + Информация о версии пакета + """ + class Mask(object): + Keyword = 1 + MissingKeyword = 2 + Hardmask = 3 + + def __init__(self, pn, version): + self.pn = pn + self.slot = "" + self.version = version + self.mask = [] + self.installed = False + + def clone(self): + obj = PackageVersionInfo(self.pn, self.version) + obj.mask = list(self.mask) + obj.installed = self.installed + obj.slot = self.slot + return obj + + @property + def stable(self): + return not self.mask + + @property + def hardmask(self): + return PackageVersionInfo.Mask.Hardmask in self.mask + + @property + def unstable(self): + return any(x in (PackageVersionInfo.Mask.Keyword, + PackageVersionInfo.Mask.MissingKeyword) + for x in self.mask) + + @property + def unstable_keyword(self): + return PackageVersionInfo.Mask.Keyword in self.mask + + @property + def missing_keyword(self): + return PackageVersionInfo.Mask.MissingKeyword in self.mask + + def __repr__(self): + return "<{pn}-{pv}:{slot} {attrs}>".format( + pn=self.pn, + pv=self.version, + slot=self.slot, + attrs=" ".join(x for x in ("stable", "hardmask", + "unstable", "unstable_keyword", + "installed", + "missing_keyword") if getattr(self, x))) + + +class EixVersionParser(EixParser): + """ + Возвращает информацию о версии + """ + mask_map = { + 'hard': PackageVersionInfo.Mask.Hardmask, + 'keyword': PackageVersionInfo.Mask.Keyword, + 'missing_keyword': PackageVersionInfo.Mask.MissingKeyword, + } + + def get_categories(self, et): + for category in et: + for version in self.get_packages(category): + yield version + + def get_packages(self, et): + for pkg in et: + pvi = PackageVersionInfo("%s/%s"% + (et.attrib['name'], + pkg.attrib['name']), None) + for version in self.get_versions(pkg, pvi): + yield version + + def get_versions(self, et, pvi): + for ver in et.iterfind('version'): + retver = pvi.clone() + retver.version = ver.attrib['id'] + if "slot" in ver.attrib: + retver.slot = ver.attrib['slot'] + else: + retver.slot = "0" + if "installed" in ver.attrib: + retver.installed = True + for verattr in ver.iterfind('mask'): + _type = verattr.get('type') + if _type in self.mask_map: + retver.mask.append(self.mask_map[_type]) + yield retver class ChrootEix(Eix): @@ -1456,6 +1581,70 @@ class BinaryPackage(object): def clear(self): removeDir(self.work_dn) +class WorldPackage(object): + def __init__(self, package): + self.pkg = EmergePackage(package) + + def __hash__(self): + return hash("%s:%s" % (self.pkg["CATEGORY/PN"], self.pkg["SLOT!"])) + + def __eq__(self, obj): + return (self.pkg["CATEGORY/PN"] == obj["CATEGORY/PN"] and + self.pkg["SLOT!"] == obj["SLOT!"]) + + def __getitem__(self, item): + return self.pkg[item] + + def __repr__(self): + return str("%s:%s"%(self.pkg["CATEGORY/PN"], self.pkg["SLOT"]) + if self.pkg["SLOT!"] else self.pkg["CATEGORY/PN"]) + +class WorldFile(object): + class DiffType(object): + Added = 0 + Removed = 1 + Omitted = 2 + + def __init__(self, data): + self.data = data + self.packages = { + WorldPackage(x) + for x in (x.strip() for x in self.data.split('\n') + if x.strip() and not x.startswith("#"))} + + def diff_new(self, worldfile): + return worldfile.packages - self.packages + + def diff_removed(self, worldfile): + return self.packages - worldfile.packages + + def diff_omitted(self, worldfile): + return worldfile.packages & self.packages + + def category_diff(self, worldfile): + for group, data in groupby(sorted(chain( + ((WorldFile.DiffType.Added, x) + for x in self.diff_new(worldfile)), + ((WorldFile.DiffType.Removed, x) + for x in self.diff_removed(worldfile)), + ((WorldFile.DiffType.Omitted, x) + for x in self.diff_omitted(worldfile))), + key=lambda x: x[1]["CATEGORY/PN"]), + lambda x: x[1]["CATEGORY/PN"]): + data = list(data) + added = sorted((pkg for _type, pkg in data + if _type == WorldFile.DiffType.Added), + key=lambda x: str(x)) + removed = sorted((pkg for _type, pkg in data + if _type == WorldFile.DiffType.Removed), + key=lambda x: str(x)) + omitted = sorted((pkg for _type, pkg in data + if _type == WorldFile.DiffType.Omitted), + key=lambda x: str(x)) + if added or removed: + yield (group, added, removed, omitted) + + def get_binary_file(pkg, pkgdir): """ Получить имя файла бинарного пакета