parse.py (7712B)
1 # See LICENSE file for copyright and license details. 2 3 """ 4 Parsing functions/helpers 5 """ 6 7 from time import mktime, strptime 8 9 10 def get_time(date): 11 """ 12 Gets epoch time 13 """ 14 if not date: 15 # hardcode if something's amiss 16 date = 'Sun, 29 Oct 2017 10:00:00 UTC' 17 return mktime(strptime(date, '%a, %d %b %Y %H:%M:%S %Z')) 18 19 20 def get_date(relfile): 21 """ 22 Gets the date from the contents of a Release file 23 """ 24 date = None 25 contents = relfile.split('\n') 26 for line in contents: 27 if line.startswith('Date: '): 28 date = line.split(': ')[1] 29 break 30 return date 31 32 33 def parse_release(reltext): 34 """ 35 Parses a Release file and returns a dict of the files we need 36 key = filename, value = tuple of sha256sum and file size 37 """ 38 hashes = {} 39 40 contents = reltext.split('\n') 41 42 sha256 = False 43 for line in contents: 44 if sha256 is True and line != '': 45 filename = line.split()[2] 46 filesize = line.split()[1] 47 checksum = line.split()[0] 48 hashes[filename] = (checksum, filesize) 49 elif line.startswith('SHA256:'): 50 sha256 = True 51 52 return hashes 53 54 55 def parse_release_head(reltext): 56 """ 57 Parses the header of the release file to grab needed metadata 58 """ 59 metadata = {} 60 61 contents = reltext.split('\n') 62 63 splitter = 'MD5Sum:' 64 65 md5sum = False 66 for line in contents: 67 if md5sum is True: 68 break 69 elif line.startswith(splitter): 70 md5sum = True 71 else: 72 key = line.split(': ')[0] 73 val = line.split(': ')[1] 74 metadata[key] = val 75 76 return metadata 77 78 79 def parse_package(entry): 80 """ 81 Parses a single Packages entry 82 """ 83 pkgs = {} 84 85 contents = entry.split('\n') 86 87 key = '' 88 value = '' 89 for line in contents: 90 if line.startswith(' '): 91 value += '\n' + line 92 else: 93 pkgs[key] = value 94 95 val = line.split(':', 1) 96 key = val[0] 97 value = val[1][1:] 98 99 if key: 100 pkgs[key] = value 101 102 return pkgs 103 104 105 def parse_packages(pkgtext): 106 """ 107 Parses our package file contents into a hashmap 108 key: package name, value: entire package paragraph as a hashmap 109 """ 110 _map = {} 111 112 pkgs = pkgtext.split('\n\n') 113 for pkg in pkgs: 114 single = pkg.split('\n') 115 for line in single: 116 if line.startswith('Package: '): 117 key = line.split(': ')[1] 118 _map[key] = parse_package(pkg) 119 break 120 121 return _map 122 123 124 def parse_dependencies(dependencies): 125 """ 126 Parses a dependency line from a debian Packages file. 127 128 Example line:: 129 130 'lib6 (>= 2.4), libdbus-1-3 (>= 1.0.2), foo | bar (>= 4.5.6)' 131 132 Output:: 133 134 A list of dep alternatives, whose elements are dicts, in the form: 135 136 [{'lib6': '(>= 2.4)'}, {'libdbus-1-3': '(>= 1.0.2)'}, 137 {'foo': None, 'bar': '(>= 4.5.6)'}] 138 """ 139 ret = [] 140 141 for alternatives in dependencies.split(', '): 142 depset = {} 143 for pkg_plus_version in alternatives.split('|'): 144 ver = pkg_plus_version.strip(' ').split(' ', 1) 145 name = ver[0] 146 147 # If we get passed an empty string, the name is '', and we just 148 # outright stop. 149 if not name: 150 return [] 151 if len(ver) == 2: 152 version = ver[1] 153 depset[name] = version 154 else: 155 depset[name] = None 156 ret.append(depset) 157 158 return ret 159 160 161 def compare_epochs(epo1, epo2): 162 """ 163 Compares two given epochs and returns their difference. 164 """ 165 return int(epo1) - int(epo2) 166 167 168 def get_epoch(ver): 169 """ 170 Parses and returns the epoch, and the rest, split of a version string. 171 """ 172 if ':' in ver: 173 return ver.split(':', 1) 174 return "0", ver 175 176 177 def get_upstream(rest): 178 """ 179 Separate upstream_version from debian-version. The latter is whatever is 180 found after the last "-" (hyphen) 181 """ 182 split_s = rest.rsplit('-', 1) 183 if len(split_s) < 2: 184 return split_s[0], "" 185 return split_s 186 187 188 def get_non_digit(s): 189 """ 190 Get a string and return the longest leading substring consisting 191 exclusively of non-digits (or an empty string), and the remaining 192 substring. 193 """ 194 if not s: 195 return "", "" 196 head = "" 197 tail = s 198 N = len(s) 199 i = 0 200 while i < N and not s[i].isdigit(): 201 head += s[i] 202 tail = tail[1:] 203 i += 1 204 return head, tail 205 206 207 def get_digit(s): 208 """ 209 Get a string and return the integer value of the longest leading substring 210 consisting exclusively of digit characters (or zero otherwise), and the 211 remaining substring. 212 """ 213 if not s: 214 return 0, "" 215 head = "" 216 tail = s 217 N = len(s) 218 i = 0 219 while i < N and s[i].isdigit(): 220 head += s[i] 221 tail = tail[1:] 222 i += 1 223 return int(head), tail 224 225 226 def char_val(c): 227 """ 228 Returns an integer value of a given unicode character. Returns 0 on ~ 229 (since this is in Debian's policy) 230 """ 231 if c == '~': 232 return 0 233 elif not c.isalpha(): 234 return 256 + ord(c) 235 return ord(c) 236 237 238 def compare_deb_str(a1, a2): 239 while len(a1) > 0 and len(a2) > 0: 240 char_diff = char_val(a1[0]) - char_val(a2[0]) 241 if char_diff != 0: 242 return char_diff 243 a1 = a1[1:] 244 a2 = a2[1:] 245 if len(a1) == 0: 246 if len(a2) == 0: 247 return 0 248 else: 249 if a2[0] == '~': 250 return 512 251 else: 252 return -ord(a2[0]) 253 else: 254 if a1[0] == '~': 255 return -512 256 else: 257 return ord(a1[0]) 258 259 260 def compare_non_epoch(s1, s2): 261 cont = True 262 while cont: 263 alpha1, tail1 = get_non_digit(s1) 264 alpha2, tail2 = get_non_digit(s2) 265 if alpha1 == alpha2: 266 if not tail1 and not tail2: 267 diff = 0 268 break 269 num1, s1 = get_digit(tail1) 270 num2, s2 = get_digit(tail2) 271 if num1 == num2: 272 cont = True 273 else: 274 diff = num1 - num2 275 cont = False 276 else: 277 cont = False 278 diff = compare_deb_str(alpha1, alpha2) 279 280 return diff 281 282 283 def cmppkgver(ver1, ver2): 284 """ 285 Main function to compare two package versions. Wraps around other 286 functions to provide a result. It returns an integer < 0 if ver1 is 287 earlier than ver2, 0 if ver1 is the same as ver2, and an integer > 0 288 if ver2 is earlier than ver2. 289 290 WARNING: The function does not induce a total order (i.e., return values 291 MUST NOT be added or subtracted) 292 293 https://www.debian.org/doc/debian-policy/#version 294 """ 295 epoch1, rest1 = get_epoch(ver1) 296 epoch2, rest2 = get_epoch(ver2) 297 298 ec = compare_epochs(epoch1, epoch2) 299 if ec != 0: 300 # The two versions differ on epoch 301 return ec 302 303 upst1, rev1 = get_upstream(rest1) 304 upst2, rev2 = get_upstream(rest2) 305 306 up_diff = compare_non_epoch(upst1, upst2) 307 if up_diff == 0: 308 return compare_non_epoch(rev1, rev2) 309 return up_diff 310 311 312 def compare_dict(dic1, dic2): 313 """ 314 Compares two dicts 315 Takes two dicts and returns a dict of tuples with the differences. 316 317 Example input: 318 319 dic1={'foo': 'bar'}, dic2={'foo': 'baz'} 320 321 Example output: 322 323 {'foo': ('bar', 'baz')} 324 """ 325 d1_keys = set(dic1.keys()) 326 d2_keys = set(dic2.keys()) 327 intersect_keys = d1_keys.intersection(d2_keys) 328 mod = {o: (dic1[o], dic2[o]) for o in intersect_keys if dic1[o] != dic2[o]} 329 return mod