"""Utility functions for website build/upload."""
from __future__ import annotations
import hashlib
import logging
import os
from pathlib import Path
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Union, cast
import igittigitt
from ....utils import FileHash, change_dir
if TYPE_CHECKING:
from _typeshed import StrPath
LOGGER = logging.getLogger(__name__)
[docs]def calculate_hash_of_files(files: Iterable[StrPath], root: Path) -> str:
"""Return a hash of all of the given files at the given root.
Args:
files: file names to include in the hash calculation, relative to ``root``.
root: base directory to analyze files in.
Returns:
A hash of the hashes of the given files.
"""
file_hash = FileHash(hashlib.md5())
file_hash.add_files(sorted(str(f) for f in files), relative_to=root)
return file_hash.hexdigest
[docs]def get_hash_of_files(
root_path: Path,
directories: Optional[List[Dict[str, Union[List[str], str]]]] = None,
) -> str:
"""Generate md5 hash of files.
Args:
root_path: Base directory where all paths will be relative to.
This should already be resolve to an absolute path.
directories: List of mappings that describe the paths to hash and files
to exclude.
"""
directories = directories or [{"path": "./"}]
files_to_hash: List[StrPath] = []
for i in directories:
gitignore = get_ignorer(
root_path / cast(str, i["path"]),
cast(Optional[List[str]], i.get("exclusions")),
)
with change_dir(root_path):
for root, dirs, files in os.walk(cast(str, i["path"]), topdown=True):
sub_root = Path(root).resolve()
if root != "./" and gitignore.match(sub_root):
dirs[:] = [] # type: ignore
files[:] = [] # type: ignore
else:
for filename in files:
filepath = sub_root / filename
if not gitignore.match(filepath):
files_to_hash.append(filepath)
return calculate_hash_of_files(files_to_hash, root_path)
[docs]def get_ignorer(
path: Path, additional_exclusions: Optional[List[str]] = None
) -> igittigitt.IgnoreParser:
"""Create gitignore filter from directory ``.gitignore`` file.
Args:
path: Top-level directory that the gitignore filter will be created for.
This directory and it's subdirectories will be searched for
``.gitignore`` files to use.
additional_exclusions: Additional gitignore patterns to add.
"""
additional_exclusions = additional_exclusions or []
gitignore = igittigitt.IgnoreParser()
gitignore.parse_rule_files(path)
for rule in additional_exclusions:
gitignore.add_rule(rule, path)
return gitignore