Mercurial > p > roundup > code
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" |
