blob: 097ddd53cb50800a5f84a8e3e70f2d9d1580676b [file] [log] [blame]
#!/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)
tt.saveXML(temp_ttx_path, 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,
)