Skip to content

Instantly share code, notes, and snippets.

@geblanco
Last active December 27, 2022 08:17
Show Gist options
  • Save geblanco/acc8b118cb3f2faf38cea44dee3fa3fd to your computer and use it in GitHub Desktop.
Save geblanco/acc8b118cb3f2faf38cea44dee3fa3fd to your computer and use it in GitHub Desktop.
Script to get permissions diff between multiple directories or files. You can get a files permission list by passing -p and a single directory
#!/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