Package linda :: Module dpkgver
[hide private]

Source Code for Module linda.dpkgver

  1  # -*- coding: UTF-8 -*- 
  2  # Small changes by Steve Kowalik, GPL (C) 2005, 2006 
  3  # Scott James Remnant told me the license is MIT 
  4  """Parse and compare Debian version strings. 
  5   
  6  This module contains a class designed to sit in your Python code pretty 
  7  naturally and represent a Debian version string.  It implements various 
  8  special methods to make dealing with them sweet. 
  9  """ 
 10   
 11  __author__    = "Scott James Remnant <scott@netsplit.com>" 
 12   
 13   
 14  import re 
 15  from linda.debug import dprint 
 16   
 17  # Regular expressions make validating things easy 
 18  valid_epoch = re.compile(r'^[0-9]+$') 
 19  valid_upstream = re.compile(r'^[0-9][A-Za-z0-9+:.~-]*$') 
 20  valid_revision = re.compile(r'^[A-Za-z0-9+.~]+$') 
 21   
 22  # Character comparison table for upstream and revision components 
 23  cmp_table = "~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-.:" 
 24   
 25   
26 -class VersionError(Exception): pass
27 -class BadInputError(VersionError): pass
28 -class BadEpochError(BadInputError): pass
29 -class BadUpstreamError(BadInputError): pass
30 -class BadRevisionError(BadInputError): pass
31
32 -class DpkgVersion(object):
33 """ 34 Debian version number. 35 36 This class is designed to be reasonably transparent and allow you to write code like: 37 38 >>> s.version >= '1.100-1' 39 40 The comparison will be done according to Debian rules, so '1.2' will compare lower. 41 42 Properties: 43 - epoch: Epoch 44 - upstream: Upstream version 45 - revision: Debian/local revision 46 """ 47
48 - def __init__(self, ver):
49 """Parse a string or number into the three components.""" 50 self.epoch = 0 51 self.upstream = None 52 self.revision = None 53 54 ver = str(ver) 55 if not len(ver): 56 raise BadInputError, "Input cannot be empty" 57 58 # Epoch is component before first colon 59 idx = ver.find(":") 60 if idx != -1: 61 self.epoch = ver[:idx] 62 if not len(self.epoch): 63 raise BadEpochError, "Epoch cannot be empty" 64 if not valid_epoch.search(self.epoch): 65 raise BadEpochError, "Bad epoch format" 66 ver = ver[idx+1:] 67 68 # Revision is component after last hyphen 69 idx = ver.rfind("-") 70 if idx != -1: 71 self.revision = ver[idx+1:] 72 if not len(self.revision): 73 raise BadRevisionError, "Revision cannot be empty" 74 if not valid_revision.search(self.revision): 75 raise BadRevisionError, "Bad revision format" 76 ver = ver[:idx] 77 78 # Remaining component is upstream 79 self.upstream = ver 80 if not len(self.upstream): 81 raise BadUpstreamError, "Upstream version cannot be empty" 82 83 dprint(_("Verifying upstream version: %s") % self.upstream) 84 if not valid_upstream.search(self.upstream): 85 raise BadUpstreamError, "Bad upstream version format: %s" % \ 86 self.upstream 87 88 self.epoch = int(self.epoch)
89
90 - def getWithoutEpoch(self):
91 """Return the version without the epoch.""" 92 str = self.upstream 93 if self.revision is not None: 94 str += "-%s" % (self.revision,) 95 return str
96 97 without_epoch = property(getWithoutEpoch) 98
99 - def __str__(self):
100 """Return the class as a string for printing.""" 101 str = "" 102 if self.epoch > 0: 103 str += "%d:" % (self.epoch,) 104 str += self.upstream 105 if self.revision is not None: 106 str += "-%s" % (self.revision,) 107 return str
108
109 - def __repr__(self):
110 """Return a debugging representation of the object.""" 111 return "<%s epoch: %d, upstream: %r, revision: %r>" \ 112 % (self.__class__.__name__, self.epoch, 113 self.upstream, self.revision)
114
115 - def __cmp__(self, other):
116 """Compare two Version classes.""" 117 other = DpkgVersion(other) 118 119 result = cmp(self.epoch, other.epoch) 120 if result != 0: return result 121 122 result = deb_cmp(self.upstream, other.upstream) 123 if result != 0: return result 124 125 result = deb_cmp(self.revision or "", other.revision or "") 126 if result != 0: return result 127 128 return 0
129
130 - def is_native(self):
131 native = False 132 if not self.revision: 133 native = True 134 return native
135
136 -def strcut(str, idx, accept):
137 """Cut characters from str that are entirely in accept.""" 138 ret = "" 139 while idx < len(str) and str[idx] in accept: 140 ret += str[idx] 141 idx += 1 142 143 return (ret, idx)
144
145 -def deb_order(str, idx):
146 """Return the comparison order of two characters.""" 147 if idx >= len(str): 148 return 0 149 elif str[idx] == "~": 150 return -1 151 else: 152 return cmp_table.index(str[idx])
153
154 -def deb_cmp_str(x, y):
155 """Compare two strings in a deb version.""" 156 idx = 0 157 while (idx < len(x)) or (idx < len(y)): 158 result = deb_order(x, idx) - deb_order(y, idx) 159 if result < 0: 160 return -1 161 elif result > 0: 162 return 1 163 164 idx += 1 165 166 return 0
167
168 -def deb_cmp(x, y):
169 """Implement the string comparison outlined by Debian policy.""" 170 x_idx = y_idx = 0 171 while x_idx < len(x) or y_idx < len(y): 172 # Compare strings 173 (x_str, x_idx) = strcut(x, x_idx, cmp_table) 174 (y_str, y_idx) = strcut(y, y_idx, cmp_table) 175 result = deb_cmp_str(x_str, y_str) 176 if result != 0: return result 177 178 # Compare numbers 179 (x_str, x_idx) = strcut(x, x_idx, "0123456789") 180 (y_str, y_idx) = strcut(y, y_idx, "0123456789") 181 result = cmp(int(x_str or "0"), int(y_str or "0")) 182 if result != 0: return result 183 184 return 0
185