tparse.py - amprolla - devuan's apt repo merger
 (HTM) 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