tparse.py - amprolla - devuan's apt repo merger
HTML git clone https://git.parazyd.org/amprolla
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
tparse.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