1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
import md5
import os
import re
import warnings
from xml.etree import ElementTree as ET
#========================================================================
# Regex helpers for parsing file names and validating checksums.
#========================================================================
_version_in_name = re.compile("(\S*)[-](\d+\.\d+\.*\d*)\S*")
def _get_filename(filename):
match = _version_in_name.search(filename)
if match is None:
raise ValueError, "Could not find name in filename: " + filename
return match.group(1)
def _get_version(filename):
match = _version_in_name.search(filename)
if match is None:
raise ValueError, "Could not find version in filename: " + filename
return match.group(2)
def _get_checksum(filename):
base, ext = os.path.splitext(filename)
data = open(base).read()
return md5.new(data).hexdigest()
filename_re = re.compile('filename: \s*(.*)\n')
version_re = re.compile('version: \s*(.*)\n')
checksum_re = re.compile('checksum: \s*(.*)\n')
desc_re = re.compile('\ndescription:\n')
codedict = {'filename':{'re':filename_re,
'get':_get_filename},
'version': {'re':version_re,
'get':_get_version},
'checksum': {'re':checksum_re,
'get':_get_checksum}
}
class InfoFile:
"""Representation of an .info file, which provides metadata of another
file (its "target").
Important methods:
@classmethod
from_info_file(filename)
construct an InfoFile object from a filename --- simple parser
name: %filename% (if not present extracted from .info filename)
version: %filename% (if not present it is extracted from name of file)
checksum: md5hash (if not present it is computed from the basefile)
html: (everything else in the file from the next line to the end)
get_xml()
return a list of xml elements for this file
"""
# The filename of the update_file. This is not the full path -
# see **location_root** below.
filename = ""
# The version of the target file
version = None
# Checksum of the target file
checksum = None
# A multi-line HTML document describing the changes between
# this version and the previous version
description = ""
# The reported location of where self.filename can be found. This gets
# prepended to self.filename to form the full path. Typically this will be
# an HTTP URL, but this can be a URI for a local or LAN directory.
# This field usually gets set by an external tool, and is not present
# in the .info format.
location = "./"
# A function that takes a string (self.version) and returns something
# that can be used to compare against the version-parsed version of
# another VersionInfo object.
version_parser = None
#========================================================================
# Constructors
#========================================================================
@classmethod
def from_info_file(cls, filename):
""" Construct an InfoFile instance from a .info file on disk.
"""
str = open(filename).read()
obj = cls()
for attr in ['filename', 'version', 'checksum']:
funcdict = codedict[attr]
match = funcdict['re'].search(str)
if match is None:
value = funcdict['get'](filename)
else:
value = match.group(1)
setattr(obj, attr, value)
match = desc_re.search(str)
if match is None:
warnings.warn("Info file " + filename + " lacks a description: field")
else:
beg, end = match.span()
start = str.find('\n', end)
obj.description = str[start:]
return obj
@classmethod
def from_target_file(cls, filename):
""" Construct an InfoFile given the filename of the target file.
"""
obj = cls(filename=filename)
# Try to glean a version number from the file name
try:
version = _get_version(filename)
obj.version = version
except ValueError:
pass
return obj
@classmethod
def from_xml(cls, bytes):
""" Returns a new InfoFile instance from a multi-line string of
XML data
"""
raise NotImplementedError
def __init__(self, **kwargs):
# Do a strict Traits-like construction
for attr in ("filename", "version", "checksum", "description",
"location", "version_parser"):
if attr in kwargs:
setattr(self, attr, kwargs[attr])
return
#========================================================================
# Public methods
#========================================================================
def to_xml(self):
root = ET.Element("file")
for attrname in ("version", "filename", "location", "checksum", "description"):
node = ET.SubElement(root, attrname)
node.text = getattr(self, attrname)
return root
def to_xml_str(self):
""" Returns a multi-line string of XML representing the information in
this object.
"""
return ET.tostring(self.to_xml())
def to_info_str(self):
""" Returns a multi-line string in the .info file format
"""
lines = []
for attr in ["filename", "version", "checksum"]:
lines.append(attr + ": " + getattr(self, attr))
return "\n".join(lines) + "\ndescription:\n" + self.description + "\n"
def __cmp__(self, other):
""" Allows for comparing two VersionInfo objects so they can
be presented in version-sorted order. This is where we parse
and interpretation of the **version** string attribute.
"""
# TODO: Do something more intelligent here, if version parsers are missing
if self.version_parser is not None:
self_ver = self.version_parser(self.version)
else:
self_ver = self.version
if other.version_parser is not None:
other_ver = other.version_parser(other.version)
else:
other_ver = other.version
if self_ver < other_ver:
return -1
elif self.ver == other_ver:
return 0
else:
return 1
|