comparison roundup/install_util.py @ 5427:88cf5614e0f4

Python 3 preparation: use byte strings and binary I/O in roundup/install_util.py. This code is hashing files it copies and inserting comments with the hash value. The hash interfaces require bytes objects in Python 3 and it seems reasonable to use such objects throughout this file.
author Joseph Myers <jsm@polyomino.org.uk>
date Wed, 25 Jul 2018 09:54:08 +0000
parents 9ba03348f923
children 7ada5d44b21d
comparison
equal deleted inserted replaced
5426:5dc27422f3ec 5427:88cf5614e0f4
21 __docformat__ = 'restructuredtext' 21 __docformat__ = 'restructuredtext'
22 22
23 import os, shutil 23 import os, shutil
24 from hashlib import sha1 24 from hashlib import sha1
25 25
26 from roundup.anypy.strings import s2b
27
26 sgml_file_types = [".xml", ".ent", ".html"] 28 sgml_file_types = [".xml", ".ent", ".html"]
27 hash_file_types = [".py", ".sh", ".conf", ".cgi"] 29 hash_file_types = [".py", ".sh", ".conf", ".cgi"]
28 slast_file_types = [".css"] 30 slast_file_types = [".css"]
29 31
30 digested_file_types = sgml_file_types + hash_file_types + slast_file_types 32 digested_file_types = sgml_file_types + hash_file_types + slast_file_types
31 33
32 def extractFingerprint(lines): 34 def extractFingerprint(lines):
33 # get fingerprint from last line 35 # get fingerprint from last line
34 if lines[-1].startswith("#SHA: "): 36 if lines[-1].startswith(b"#SHA: "):
35 # handle .py/.sh comment 37 # handle .py/.sh comment
36 return lines[-1][6:].strip() 38 return lines[-1][6:].strip()
37 elif lines[-1].startswith("<!-- SHA: "): 39 elif lines[-1].startswith(b"<!-- SHA: "):
38 # handle xml/html files 40 # handle xml/html files
39 fingerprint = lines[-1][10:] 41 fingerprint = lines[-1][10:]
40 fingerprint = fingerprint.replace('-->', '') 42 fingerprint = fingerprint.replace(b'-->', b'')
41 return fingerprint.strip() 43 return fingerprint.strip()
42 elif lines[-1].startswith("/* SHA: "): 44 elif lines[-1].startswith(b"/* SHA: "):
43 # handle css files 45 # handle css files
44 fingerprint = lines[-1][8:] 46 fingerprint = lines[-1][8:]
45 fingerprint = fingerprint.replace('*/', '') 47 fingerprint = fingerprint.replace(b'*/', b'')
46 return fingerprint.strip() 48 return fingerprint.strip()
47 return None 49 return None
48 50
49 def checkDigest(filename): 51 def checkDigest(filename):
50 """Read file, check for valid fingerprint, return TRUE if ok""" 52 """Read file, check for valid fingerprint, return TRUE if ok"""
51 # open and read file 53 # open and read file
52 inp = open(filename, "r") 54 inp = open(filename, "rb")
53 lines = inp.readlines() 55 lines = inp.readlines()
54 inp.close() 56 inp.close()
55 57
56 fingerprint = extractFingerprint(lines) 58 fingerprint = extractFingerprint(lines)
57 if fingerprint is None: 59 if fingerprint is None:
62 digest = sha1() 64 digest = sha1()
63 for line in lines: 65 for line in lines:
64 digest.update(line) 66 digest.update(line)
65 67
66 # compare current to stored digest 68 # compare current to stored digest
67 return fingerprint == digest.hexdigest() 69 return fingerprint == s2b(digest.hexdigest())
68 70
69 71
70 class DigestFile: 72 class DigestFile:
71 """ A class that you can use like open() and that calculates 73 """ A class that you can use like open() and that calculates
72 and writes a SHA digest to the target file. 74 and writes a SHA digest to the target file.
73 """ 75 """
74 76
75 def __init__(self, filename): 77 def __init__(self, filename):
76 self.filename = filename 78 self.filename = filename
77 self.digest = sha1() 79 self.digest = sha1()
78 self.file = open(self.filename, "w") 80 self.file = open(self.filename, "wb")
79 81
80 def write(self, data): 82 def write(self, data):
81 lines = data.splitlines() 83 lines = data.splitlines()
82 # if the file is coming from an installed tracker being used as a 84 # if the file is coming from an installed tracker being used as a
83 # template, then we will want to re-calculate the SHA 85 # template, then we will want to re-calculate the SHA
84 fingerprint = extractFingerprint(lines) 86 fingerprint = extractFingerprint(lines)
85 if fingerprint is not None: 87 if fingerprint is not None:
86 data = '\n'.join(lines[:-1]) + '\n' 88 data = b'\n'.join(lines[:-1]) + b'\n'
87 self.file.write(data) 89 self.file.write(data)
88 self.digest.update(data) 90 self.digest.update(data)
89 91
90 def close(self): 92 def close(self):
91 file, ext = os.path.splitext(self.filename) 93 file, ext = os.path.splitext(self.filename)
92 94
93 if ext in sgml_file_types: 95 if ext in sgml_file_types:
94 self.file.write("<!-- SHA: %s -->\n" % (self.digest.hexdigest(),)) 96 self.file.write(s2b("<!-- SHA: %s -->\n" % (self.digest.hexdigest(),)))
95 elif ext in hash_file_types: 97 elif ext in hash_file_types:
96 self.file.write("#SHA: %s\n" % (self.digest.hexdigest(),)) 98 self.file.write(s2b("#SHA: %s\n" % (self.digest.hexdigest(),)))
97 elif ext in slast_file_types: 99 elif ext in slast_file_types:
98 self.file.write("/* SHA: %s */\n" % (self.digest.hexdigest(),)) 100 self.file.write(s2b("/* SHA: %s */\n" % (self.digest.hexdigest(),)))
99 101
100 self.file.close() 102 self.file.close()
101 103
102 104
103 def copyDigestedFile(src, dst, copystat=1): 105 def copyDigestedFile(src, dst, copystat=1):
116 return shutil.copyfile(src, dst) 118 return shutil.copyfile(src, dst)
117 119
118 fsrc = None 120 fsrc = None
119 fdst = None 121 fdst = None
120 try: 122 try:
121 fsrc = open(src, 'r') 123 fsrc = open(src, 'rb')
122 fdst = DigestFile(dst) 124 fdst = DigestFile(dst)
123 shutil.copyfileobj(fsrc, fdst) 125 shutil.copyfileobj(fsrc, fdst)
124 finally: 126 finally:
125 if fdst: fdst.close() 127 if fdst: fdst.close()
126 if fsrc: fsrc.close() 128 if fsrc: fsrc.close()
129 131
130 132
131 def test(): 133 def test():
132 import sys 134 import sys
133 135
134 testdata = open(sys.argv[0], 'r').read() 136 testdata = open(sys.argv[0], 'rb').read()
135 137
136 for ext in digested_file_types: 138 for ext in digested_file_types:
137 testfile = "__digest_test" + ext 139 testfile = "__digest_test" + ext
138 140
139 out = DigestFile(testfile) 141 out = DigestFile(testfile)
140 out.write(testdata) 142 out.write(testdata)
141 out.close() 143 out.close()
142 144
143 assert checkDigest(testfile), "digest ok w/o modification" 145 assert checkDigest(testfile), "digest ok w/o modification"
144 146
145 mod = open(testfile, 'r+') 147 mod = open(testfile, 'r+b')
146 mod.seek(0) 148 mod.seek(0)
147 mod.write('# changed!') 149 mod.write('# changed!')
148 mod.close() 150 mod.close()
149 151
150 assert not checkDigest(testfile), "digest fails after modification" 152 assert not checkDigest(testfile), "digest fails after modification"

Roundup Issue Tracker: http://roundup-tracker.org/