aboutsummaryrefslogtreecommitdiff
path: root/Snippets/checksum.py
blob: 53a53183db96ea52cfe620374cb6be2d7bee53b3 (plain)
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import hashlib
import os
import sys

from os.path import basename

from fontTools.ttLib import TTFont


def write_checksum(filepaths, stdout_write=False, use_ttx=False, include_tables=None, exclude_tables=None, do_not_cleanup=False):
    checksum_dict = {}
    for path in filepaths:
        if not os.path.exists(path):
            sys.stderr.write("[checksum.py] ERROR: " + path + " is not a valid file path" + os.linesep)
            sys.exit(1)

        if use_ttx:
            # append a .ttx extension to existing extension to maintain data about the binary that
            # was used to generate the .ttx XML dump.  This creates unique checksum path values for
            # paths that would otherwise not be unique with a file extension replacement with .ttx
            # An example is woff and woff2 web font files that share the same base file name:
            #
            #  coolfont-regular.woff  ==> coolfont-regular.ttx
            #  coolfont-regular.woff2 ==> coolfont-regular.ttx  (KAPOW! checksum data lost as this would overwrite dict value)
            temp_ttx_path = path + ".ttx"

            tt = TTFont(path)
            # important to keep the newlinestr value defined here as hash values will change across platforms
            # if platform specific newline values are assumed
            tt.saveXML(temp_ttx_path, newlinestr="\n", skipTables=exclude_tables, tables=include_tables)
            checksum_path = temp_ttx_path
        else:
            if include_tables is not None:
                sys.stderr.write("[checksum.py] -i and --include are not supported for font binary filepaths. \
                    Use these flags for checksums with the --ttx flag.")
                sys.exit(1)
            if exclude_tables is not None:
                sys.stderr.write("[checksum.py] -e and --exclude are not supported for font binary filepaths. \
                    Use these flags for checksums with the --ttx flag.")
                sys.exit(1)
            checksum_path = path

        file_contents = _read_binary(checksum_path)

        # store SHA1 hash data and associated file path basename in the checksum_dict dictionary
        checksum_dict[basename(checksum_path)] = hashlib.sha1(file_contents).hexdigest()

        # remove temp ttx files when present
        if use_ttx and do_not_cleanup is False:
            os.remove(temp_ttx_path)

    # generate the checksum list string for writes
    checksum_out_data = ""
    for key in checksum_dict.keys():
        checksum_out_data += checksum_dict[key] + "  " + key + "\n"

    # write to stdout stream or file based upon user request (default = file write)
    if stdout_write:
        sys.stdout.write(checksum_out_data)
    else:
        checksum_report_filepath = "checksum.txt"
        with open(checksum_report_filepath, "w") as file:
            file.write(checksum_out_data)


def check_checksum(filepaths):
    check_failed = False
    for path in filepaths:
        if not os.path.exists(path):
            sys.stderr.write("[checksum.py] ERROR: " + path + " is not a valid filepath" + os.linesep)
            sys.exit(1)

        with open(path, mode='r') as file:
            for line in file.readlines():
                cleaned_line = line.rstrip()
                line_list = cleaned_line.split(" ")
                # eliminate empty strings parsed from > 1 space characters
                line_list = list(filter(None, line_list))
                if len(line_list) == 2:
                    expected_sha1 = line_list[0]
                    test_path = line_list[1]
                else:
                    sys.stderr.write("[checksum.py] ERROR: failed to parse checksum file values" + os.linesep)
                    sys.exit(1)

                if not os.path.exists(test_path):
                    print(test_path + ": Filepath is not valid, ignored")
                else:
                    file_contents = _read_binary(test_path)
                    observed_sha1 = hashlib.sha1(file_contents).hexdigest()
                    if observed_sha1 == expected_sha1:
                        print(test_path + ": OK")
                    else:
                        print("-" * 80)
                        print(test_path + ": === FAIL ===")
                        print("Expected vs. Observed:")
                        print(expected_sha1)
                        print(observed_sha1)
                        print("-" * 80)
                        check_failed = True

    # exit with status code 1 if any fails detected across all tests in the check
    if check_failed is True:
        sys.exit(1)


def _read_binary(filepath):
    with open(filepath, mode='rb') as file:
        return file.read()


if __name__ == '__main__':
    parser = argparse.ArgumentParser(prog="checksum.py", description="A SHA1 hash checksum list generator and checksum testing script")
    parser.add_argument("-t", "--ttx", help="Calculate from ttx file", action="store_true")
    parser.add_argument("-s", "--stdout", help="Write output to stdout stream", action="store_true")
    parser.add_argument("-n", "--noclean", help="Do not discard *.ttx files used to calculate SHA1 hashes", action="store_true")
    parser.add_argument("-c", "--check", help="Verify checksum values vs. files", action="store_true")
    parser.add_argument("filepaths", nargs="+", help="One or more file paths.  Use checksum file path for -c/--check.  Use paths\
        to font files for all other commands.")

    parser.add_argument("-i", "--include", action="append", help="Included OpenType tables for ttx data dump")
    parser.add_argument("-e", "--exclude", action="append", help="Excluded OpenType tables for ttx data dump")

    args = parser.parse_args(sys.argv[1:])

    if args.check is True:
        check_checksum(args.filepaths)
    else:
        write_checksum(args.filepaths, stdout_write=args.stdout, use_ttx=args.ttx, do_not_cleanup=args.noclean, include_tables=args.include, exclude_tables=args.exclude)