1 2from __future__ import print_function 3 4import os.path 5import re 6import tarfile 7from . import packagequery 8import subprocess 9 10class ArchError(packagequery.PackageError): 11 pass 12 13class ArchQuery(packagequery.PackageQuery, packagequery.PackageQueryResult): 14 def __init__(self, fh): 15 self.__file = fh 16 self.__path = os.path.abspath(fh.name) 17 self.fields = {} 18 #self.magic = None 19 #self.pkgsuffix = 'pkg.tar.gz' 20 self.pkgsuffix = b'arch' 21 22 def read(self, all_tags=True, self_provides=True, *extra_tags): 23 # all_tags and *extra_tags are currently ignored 24 f = open(self.__path, 'rb') 25 #self.magic = f.read(5) 26 #if self.magic == '\375\067zXZ': 27 # self.pkgsuffix = 'pkg.tar.xz' 28 fn = open('/dev/null', 'wb') 29 pipe = subprocess.Popen(['tar', '-O', '-xf', self.__path, '.PKGINFO'], stdout=subprocess.PIPE, stderr=fn).stdout 30 for line in pipe.readlines(): 31 line = line.rstrip().split(b' = ', 2) 32 if len(line) == 2: 33 field, value = line[0].decode('ascii'), line[1] 34 self.fields.setdefault(field, []).append(value) 35 if self_provides: 36 prv = b'%s = %s' % (self.name(), self.fields['pkgver'][0]) 37 self.fields.setdefault('provides', []).append(prv) 38 return self 39 40 def vercmp(self, archq): 41 res = packagequery.cmp(int(self.epoch()), int(archq.epoch())) 42 if res != 0: 43 return res 44 res = ArchQuery.rpmvercmp(self.version(), archq.version()) 45 if res != 0: 46 return res 47 res = ArchQuery.rpmvercmp(self.release(), archq.release()) 48 return res 49 50 def name(self): 51 return self.fields['pkgname'][0] if 'pkgname' in self.fields else None 52 53 def version(self): 54 pkgver = self.fields['pkgver'][0] if 'pkgver' in self.fields else None 55 if pkgver != None: 56 pkgver = re.sub(br'[0-9]+:', b'', pkgver, 1) 57 pkgver = re.sub(br'-[^-]*$', b'', pkgver) 58 return pkgver 59 60 def release(self): 61 pkgver = self.fields['pkgver'][0] if 'pkgver' in self.fields else None 62 if pkgver != None: 63 m = re.search(br'-([^-])*$', pkgver) 64 if m: 65 return m.group(1) 66 return None 67 68 def _epoch(self): 69 pkgver = self.fields.get('pkgver', [b''])[0] 70 if pkgver: 71 m = re.match(br'([0-9])+:', pkgver) 72 if m: 73 return m.group(1) 74 return b'' 75 76 def epoch(self): 77 epoch = self._epoch() 78 if epoch: 79 return epoch 80 return b'0' 81 82 def arch(self): 83 return self.fields['arch'][0] if 'arch' in self.fields else None 84 85 def description(self): 86 return self.fields['pkgdesc'][0] if 'pkgdesc' in self.fields else None 87 88 def path(self): 89 return self.__path 90 91 def provides(self): 92 return self.fields['provides'] if 'provides' in self.fields else [] 93 94 def requires(self): 95 return self.fields['depend'] if 'depend' in self.fields else [] 96 97 def conflicts(self): 98 return self.fields['conflict'] if 'conflict' in self.fields else [] 99 100 def obsoletes(self): 101 return self.fields['replaces'] if 'replaces' in self.fields else [] 102 103 def recommends(self): 104 # a .PKGINFO has no notion of "recommends" 105 return [] 106 107 def suggests(self): 108 # libsolv treats an optdepend as a "suggests", hence we do the same 109 if 'optdepend' not in self.fields: 110 return [] 111 return [re.sub(b':.*', b'', entry) for entry in self.fields['optdepend']] 112 113 def supplements(self): 114 # a .PKGINFO has no notion of "recommends" 115 return [] 116 117 def enhances(self): 118 # a .PKGINFO has no notion of "enhances" 119 return [] 120 121 def canonname(self): 122 name = self.name() 123 if name is None: 124 raise ArchError(self.path(), 'package has no name') 125 version = self.version() 126 if version is None: 127 raise ArchError(self.path(), 'package has no version') 128 arch = self.arch() 129 if arch is None: 130 raise ArchError(self.path(), 'package has no arch') 131 return ArchQuery.filename(name, self._epoch(), version, self.release(), 132 arch) 133 134 def gettag(self, tag): 135 # implement me, if needed 136 return None 137 138 @staticmethod 139 def query(filename, all_tags = False, *extra_tags): 140 f = open(filename, 'rb') 141 archq = ArchQuery(f) 142 archq.read(all_tags, *extra_tags) 143 f.close() 144 return archq 145 146 @staticmethod 147 def rpmvercmp(ver1, ver2): 148 """ 149 implementation of RPM's version comparison algorithm 150 (as described in lib/rpmvercmp.c) 151 """ 152 if ver1 == ver2: 153 return 0 154 elif ver1 is None: 155 return -1 156 elif ver2 is None: 157 return 1 158 res = 0 159 while res == 0: 160 # remove all leading non alphanumeric chars 161 ver1 = re.sub(b'^[^a-zA-Z0-9]*', b'', ver1) 162 ver2 = re.sub(b'^[^a-zA-Z0-9]*', b'', ver2) 163 if not (len(ver1) and len(ver2)): 164 break 165 # check if we have a digits segment 166 mo1 = re.match(b'(\d+)', ver1) 167 mo2 = re.match(b'(\d+)', ver2) 168 numeric = True 169 if mo1 is None: 170 mo1 = re.match(b'([a-zA-Z]+)', ver1) 171 mo2 = re.match(b'([a-zA-Z]+)', ver2) 172 numeric = False 173 # check for different types: alpha and numeric 174 if mo2 is None: 175 if numeric: 176 return 1 177 return -1 178 seg1 = mo1.group(0) 179 ver1 = ver1[mo1.end(0):] 180 seg2 = mo2.group(1) 181 ver2 = ver2[mo2.end(1):] 182 if numeric: 183 # remove leading zeros 184 seg1 = re.sub(b'^0+', b'', seg1) 185 seg2 = re.sub(b'^0+', b'', seg2) 186 # longer digit segment wins - if both have the same length 187 # a simple ascii compare decides 188 res = len(seg1) - len(seg2) or packagequery.cmp(seg1, seg2) 189 else: 190 res = packagequery.cmp(seg1, seg2) 191 if res > 0: 192 return 1 193 elif res < 0: 194 return -1 195 return packagequery.cmp(ver1, ver2) 196 197 @staticmethod 198 def filename(name, epoch, version, release, arch): 199 if epoch: 200 if release: 201 return b'%s-%s:%s-%s-%s.arch' % (name, epoch, version, release, arch) 202 else: 203 return b'%s-%s:%s-%s.arch' % (name, epoch, version, arch) 204 if release: 205 return b'%s-%s-%s-%s.arch' % (name, version, release, arch) 206 else: 207 return b'%s-%s-%s.arch' % (name, version, arch) 208 209 210if __name__ == '__main__': 211 import sys 212 archq = ArchQuery.query(sys.argv[1]) 213 print(archq.name(), archq.version(), archq.release(), archq.arch()) 214 try: 215 print(archq.canonname()) 216 except ArchError as e: 217 print(e.msg) 218 print(archq.description()) 219 print('##########') 220 print(b'\n'.join(archq.provides())) 221 print('##########') 222 print(b'\n'.join(archq.requires())) 223