Last active
December 27, 2022 08:17
Script to get permissions diff between multiple directories or files. You can get a files permission list by passing -p and a single directory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import argparse | |
from glob import glob | |
from typing import List, Tuple, Union, Iterable, Optional | |
from pathlib import Path | |
from itertools import combinations | |
from dataclasses import dataclass | |
def parse_flags() -> argparse.Namespace: | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
"-r", | |
"--recursive", | |
action="store_true", | |
help="recursively compare any subdirectories found", | |
) | |
parser.add_argument( | |
"-i", | |
"--report_identical_files", | |
action="store_true", | |
help="report when two files are the same", | |
) | |
parser.add_argument( | |
"-s", "--summarize", action="store_true", help="report only when files differ" | |
) | |
parser.add_argument( | |
"-p", | |
"--permissions", | |
action="store_true", | |
help="Also print permissions on files only present in one side", | |
) | |
parser.add_argument("input_files", type=str, nargs="+", help="FILES") | |
return parser.parse_args() | |
@dataclass | |
class Result: | |
diff: Optional[int] = None | |
src_file: Path = None | |
src_perms: Optional[str] = None | |
dst_file: Optional[Path] = None | |
dst_perms: Optional[str] = None | |
report_identical: Optional[bool] = False | |
summarize: Optional[bool] = False | |
enoent_permissions: Optional[bool] = False | |
def __post_init__(self): | |
if self.src_file is None: | |
raise ValueError("`src_file` must be a valid Path") | |
if self.diff is None: | |
if not self.src_file.exists(): | |
self.diff = -2 | |
elif self.dst_file is None or not self.dst_file.exists(): | |
self.diff = 2 | |
elif self.src_file.name != self.dst_file.name: | |
raise ValueError( | |
f"Tried to compare different files! `src={self.src_file}`, `dst=" | |
f"{self.dst_file}`" | |
) | |
else: | |
self.src_perms = self._permissions(self.src_file) | |
self.dst_perms = self._permissions(self.dst_file) | |
self.diff = int(self.src_perms != self.dst_perms) | |
def __repr__(self) -> str: | |
ret = "" | |
head_str = f"File {self.src_file} and {self.dst_file}" | |
if self.diff == 0: | |
if self.report_identical: | |
ret = f"{head_str} have equal permissions" | |
elif abs(self.diff) == 2: | |
target = self.dst_file if self.diff < 0 else self.src_file | |
if not self.enoent_permissions: | |
ret = f"File {target.name} only present in {target.parent}" | |
else: | |
ret = f"{target} {self._permissions(target)}" | |
elif self.summarize: | |
ret = f"{self.src_file} {self.src_perms}" | |
else: | |
ret = f"{head_str} differ: {self.src_perms} - {self.dst_perms}" | |
return ret | |
def _permissions(self, target: Path) -> str: | |
return str(oct(target.stat().st_mode)[-3:]) | |
def should_report(self) -> bool: | |
return bool(str(self)) | |
def expand_file( | |
input_file: Path, recursive: bool | |
) -> Union[Path, Tuple[str, List[Path]]]: | |
input_file = input_file.expanduser() | |
if input_file.is_dir(): | |
# there is no way to tell Path.glob not to recurse | |
input_file = [ | |
Path(file).absolute() | |
for file in glob(str(input_file.joinpath("**/")), recursive=recursive) | |
if Path(file).is_file() | |
] | |
else: | |
input_file = [input_file] | |
return input_file | |
def comparisons_from_dirs( | |
src: List[Path], dst: Optional[List[Path]] = None | |
) -> Iterable[Tuple[Path, Optional[Path]]]: | |
compared = [] | |
if dst is None: | |
dst = [] | |
dst_names = [file.name for file in dst] | |
for src_file in src: | |
dst_file = ( | |
dst[dst_names.index(src_file.name)] if src_file.name in dst_names else None | |
) | |
compared.append(src_file.name) | |
yield (src_file, dst_file) | |
for dst_file in dst: | |
if dst_file.name not in compared: | |
yield (dst_file, None) | |
def iter_comparisons(files: List[Union[List[Path], Path]]) -> Iterable[Result]: | |
# allow comparison to empty dir to get a file permission list | |
if len(files) == 1: | |
files.append([]) | |
for src, dst in combinations(files, r=2): | |
yield from comparisons_from_dirs(src, dst) | |
def main( | |
recursive: bool, | |
report_identical_files: bool, | |
summarize: bool, | |
permissions: bool, | |
input_files: List[str], | |
): | |
results = [] | |
files = [expand_file(Path(file), recursive=recursive) for file in input_files] | |
for src, dst in iter_comparisons(files): | |
res = Result( | |
src_file=src, | |
dst_file=dst, | |
summarize=summarize, | |
report_identical=report_identical_files, | |
enoent_permissions=permissions, | |
) | |
results.append(res) | |
if res.should_report(): | |
print(res) | |
if __name__ == "__main__": | |
main(**vars(parse_flags())) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment