nex_docus/backend/venv312/lib/python3.12/site-packages/fontTools/diff/diff.py

295 lines
8.8 KiB
Python

import os
import subprocess
import tempfile
from contextlib import contextmanager
from difflib import unified_diff
from multiprocessing import Pool, cpu_count
from typing import Any, Callable, Iterable, Iterator, List, Optional, Text, Tuple
from fontTools.ttLib import TTFont # type: ignore
from .utils import get_file_modtime
#
#
# Private functions
#
#
def _get_fonts_and_save_xml(
filepath_a: Text,
filepath_b: Text,
tmpdirpath: Text,
include_tables: Optional[List[Text]],
exclude_tables: Optional[List[Text]],
font_number_a: int,
font_number_b: int,
use_multiprocess: bool,
) -> Tuple[Text, Text, Text, Text, Text, Text]:
post_pathname, postpath, pre_pathname, prepath = _get_pre_post_paths(
filepath_a, filepath_b
)
# instantiate left and right fontTools.ttLib.TTFont objects
tt_left = TTFont(prepath, fontNumber=font_number_a)
tt_right = TTFont(postpath, fontNumber=font_number_b)
left_ttxpath = os.path.join(tmpdirpath, "left.ttx")
right_ttxpath = os.path.join(tmpdirpath, "right.ttx")
_mp_save_ttx_xml(
tt_left,
tt_right,
left_ttxpath,
right_ttxpath,
exclude_tables,
include_tables,
use_multiprocess,
)
return left_ttxpath, right_ttxpath, pre_pathname, prepath, post_pathname, postpath
def _get_pre_post_paths(
filepath_a: Text,
filepath_b: Text,
) -> Tuple[Text, Text, Text, Text]:
prepath = filepath_a
postpath = filepath_b
pre_pathname = filepath_a
post_pathname = filepath_b
return post_pathname, postpath, pre_pathname, prepath
def _mp_save_ttx_xml(
tt_left: Any,
tt_right: Any,
left_ttxpath: Text,
right_ttxpath: Text,
exclude_tables: Optional[List[Text]],
include_tables: Optional[List[Text]],
use_multiprocess: bool,
) -> None:
if use_multiprocess and cpu_count() > 1:
# Use parallel fontTools.ttLib.TTFont.saveXML dump
# by default on multi CPU systems. This is a performance
# optimization. Profiling demonstrates that this can reduce
# execution time by up to 30% for some fonts
mp_args_list = [
(tt_left, left_ttxpath, include_tables, exclude_tables),
(tt_right, right_ttxpath, include_tables, exclude_tables),
]
with Pool(processes=2) as pool:
pool.starmap(_ttfont_save_xml, mp_args_list)
else:
# use sequential fontTools.ttLib.TTFont.saveXML dumps
# when use_multiprocess is False or single CPU system
# detected
_ttfont_save_xml(tt_left, left_ttxpath, include_tables, exclude_tables)
_ttfont_save_xml(tt_right, right_ttxpath, include_tables, exclude_tables)
def _ttfont_save_xml(
ttf: Any,
filepath: Text,
include_tables: Optional[List[Text]],
exclude_tables: Optional[List[Text]],
) -> bool:
"""Writes TTX specification formatted XML to disk on filepath."""
ttf.saveXML(filepath, tables=include_tables, skipTables=exclude_tables)
return True
@contextmanager
def _saved_ttx_files(
filepath_a: Text,
filepath_b: Text,
include_tables: Optional[List[Text]],
exclude_tables: Optional[List[Text]],
font_number_a: int,
font_number_b: int,
use_multiprocess: bool,
) -> Iterator[Tuple[Text, Text, Text, Text, Text, Text]]:
with tempfile.TemporaryDirectory() as tmpdirpath:
yield _get_fonts_and_save_xml(
filepath_a,
filepath_b,
tmpdirpath,
include_tables,
exclude_tables,
font_number_a,
font_number_b,
use_multiprocess,
)
def _diff_with_saved_ttx_files(
filepath_a: Text,
filepath_b: Text,
include_tables: Optional[List[Text]],
exclude_tables: Optional[List[Text]],
font_number_a: int,
font_number_b: int,
use_multiprocess: bool,
create_differ: Callable[[Text, Text, Text, Text, Text, Text], Iterable[Text]],
) -> Iterator[Text]:
with _saved_ttx_files(
filepath_a,
filepath_b,
include_tables,
exclude_tables,
font_number_a,
font_number_b,
use_multiprocess,
) as (
left_ttxpath,
right_ttxpath,
pre_pathname,
prepath,
post_pathname,
postpath,
):
yield from create_differ(
left_ttxpath,
right_ttxpath,
pre_pathname,
prepath,
post_pathname,
postpath,
)
#
#
# Public functions
#
#
def u_diff(
filepath_a: Text,
filepath_b: Text,
context_lines: int = 3,
include_tables: Optional[List[Text]] = None,
exclude_tables: Optional[List[Text]] = None,
font_number_a: int = -1,
font_number_b: int = -1,
use_multiprocess: bool = True,
) -> Iterator[Text]:
"""Performs a unified diff on a TTX serialized data format dump of font binary data using
a modified version of the Python standard libary difflib module.
filepath_a: (string) pre-file local file path
filepath_b: (string) post-file local file path
context_lines: (int) number of context lines to include in the diff (default=3)
include_tables: (list of str) Python list of OpenType tables to include in the diff
exclude_tables: (list of str) Python list of OpentType tables to exclude from the diff
use_multiprocess: (bool) use multi-processor optimizations (default=True)
include_tables and exclude_tables are mutually exclusive arguments. Only one should
be defined
:returns: Generator of ordered diff line strings that include newline line endings
:raises: KeyError if include_tables or exclude_tables includes a mis-specified table
that is not included in filepath_a OR filepath_b
"""
def _create_unified_diff(
left_ttxpath: Text,
right_ttxpath: Text,
pre_pathname: Text,
prepath: Text,
post_pathname: Text,
postpath: Text,
) -> Iterable[Text]:
with open(left_ttxpath) as ff:
fromlines = ff.readlines()
with open(right_ttxpath) as tf:
tolines = tf.readlines()
fromdate = get_file_modtime(prepath)
todate = get_file_modtime(postpath)
yield from unified_diff(
fromlines,
tolines,
pre_pathname,
post_pathname,
fromdate,
todate,
n=context_lines,
)
yield from _diff_with_saved_ttx_files(
filepath_a,
filepath_b,
include_tables,
exclude_tables,
font_number_a,
font_number_b,
use_multiprocess,
_create_unified_diff,
)
def run_external_diff(
diff_tool: Text,
diff_args: List[Text],
filepath_a: Text,
filepath_b: Text,
include_tables: Optional[List[Text]] = None,
exclude_tables: Optional[List[Text]] = None,
font_number_a: int = -1,
font_number_b: int = -1,
use_multiprocess: bool = True,
) -> Iterator[Text]:
"""Performs a unified diff on a TTX serialized data format dump of font binary data using
an external diff executable that is requested by the caller via `command`
diff_tool: (string) command line executable string
diff_args: (list of strings) arguments for the diff tool
filepath_a: (string) pre-file local file path
filepath_b: (string) post-file local file path
include_tables: (list of str) Python list of OpenType tables to include in the diff
exclude_tables: (list of str) Python list of OpentType tables to exclude from the diff
use_multiprocess: (bool) use multi-processor optimizations (default=True)
include_tables and exclude_tables are mutually exclusive arguments. Only one should
be defined
:returns: Generator of ordered diff line strings that include newline line endings
:raises: KeyError if include_tables or exclude_tables includes a mis-specified table
that is not included in filepath_a OR filepath_b
:raises: IOError if exception raised during execution of `command` on TTX files
"""
def _create_external_diff(
left_ttxpath: Text,
right_ttxpath: Text,
_pre_pathname: Text,
_prepath: Text,
_post_pathname: Text,
_postpath: Text,
) -> Iterable[Text]:
command = [diff_tool] + diff_args + [left_ttxpath, right_ttxpath]
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf8",
)
for line in process.stdout:
yield line
err = process.stderr.read()
if err:
raise IOError(err)
yield from _diff_with_saved_ttx_files(
filepath_a,
filepath_b,
include_tables,
exclude_tables,
font_number_a,
font_number_b,
use_multiprocess,
_create_external_diff,
)