dirf/dirf/diff.py
zy 0db6c387bb s: making diff *
ignore the given paths, including the other path (you get it)
2025-05-04 00:07:20 +02:00

162 lines
4.9 KiB
Python

__add__ = [ "diff" ]
from collections.abc import Set
from os import listdir, readlink
from os.path import abspath
from typing import Callable, Final
from collections.abc import Iterable
from .mytypes import *
from .Config import *
from .utils import *
from .whatsthispath import *
from .utils import *
class DiffResult:
def __init__(self, path:str) -> None:
self.path:Final[str] = path
self.differs:set[PathAndType] = set()
self.same:set[PathAndType] = set()
self.differentTypes:set[tuple[str,PathType,PathType]] = set()
self.presentOnlyInFirst:set[PathAndType] = set()
self.presentOnlyInSecond:set[PathAndType] = set()
self.weird:set[str] = set()
self.subresults:dict[str,DiffResult] = {}
def simplify(self, *, deep:bool) -> None:
for l in [
self.differs, self.same, self.presentOnlyInFirst,
self.presentOnlyInSecond
]:
mapSet(l, ( lambda v: ( v[0], simplifyPath(v[1]) ) ))
mapSet(self.differentTypes, (
lambda v: ( simplifyPath(v[0]), *v[1:] )
))
mapSet(self.weird, simplifyPath)
if deep:
for v in self.subresults.values():
v.simplify(deep=True)
def print(self, source:str|None = None, target:str|None = None) -> None:
reprSource = repr(source) if source is not None else "the source directory"
reprTarget = repr(target) if target is not None else "the target directory"
def reprPathAndType(v:PathAndType) -> str:
return f"{lineForPathAndType(v)}"
def printWith[T](label:str, what:set[T], f:Callable[[T],str]):
if len(what) == 0:
return
print(label + ":")
for v in what:
print(f"-\t{f(v)}")
title(f"for path {repr(self.path)}")
printWith("same", self.same, reprPathAndType)
printWith("different", self.differs, reprPathAndType)
printWith("different types", self.differentTypes,
lambda v: f"{reprPathType(v[1])} / {reprPathType(v[2])}: {v[0]}"
)
printWith(f"exist only in {reprSource}",
self.presentOnlyInFirst, reprPathAndType
)
printWith(f"exist only in {reprTarget}",
self.presentOnlyInSecond, reprPathAndType
)
printWith("dunno what that is lol", self.weird,
lambda v: v
)
def diff(pathA:str, pathB:str, *, ignored:Set[PathAndType]) -> DiffResult:
r = _diffDirs(abspath(pathA), abspath(pathB), ignored, "/", trust=False)
return r
def _diffDirs(
absPathA:str, absPathB:str, absIgnoredPaths:Set[PathAndType],
relCurrentPath:str, *, trust:bool
) -> DiffResult:
absCurrentPathA = absPathA + "/" + relCurrentPath
absCurrentPathB = absPathB + "/" + relCurrentPath
absCurrentPathA_what = whatsthispath(absCurrentPathA)
absCurrentPathB_what = whatsthispath(absCurrentPathB)
if not trust:
assert absPathA[0] == "/", "'pathA' should be an absolute path."
assert absPathB[0] == "/", "'pathB' should be an absolute path."
assert relCurrentPath[0] == "/", (
"'relCurrentPath' should start with '/'."
)
assert all([ v[1][0] == "/" for v in absIgnoredPaths ]), (
"'ignoredPaths' must contain only absolute paths."
)
assert absCurrentPathA_what == 'd', "'pathA' should be a directory."
assert absCurrentPathB_what == 'd', "'pathB' should be a directory."
r = DiffResult(relCurrentPath)
for name in set(listdir(absCurrentPathA)) | set(listdir(absCurrentPathB)):
relNewPath = relCurrentPath + "/" + name
absNewPathA = absCurrentPathA + "/" + name
absNewPathB = absCurrentPathB + "/" + name
absNewPathA_what = whatsthispath(absNewPathA)
absNewPathB_what = whatsthispath(absNewPathB)
if absNewPathA_what is None:
assert absNewPathB_what is not None
r.presentOnlyInSecond.add((absNewPathB_what, relNewPath))
elif absNewPathB_what is None:
r.presentOnlyInFirst.add((absNewPathA_what, relNewPath))
elif absNewPathA_what != absNewPathB_what:
r.differentTypes.add(
(relNewPath, absNewPathA_what, absNewPathB_what)
)
else:
_diffOne(r,
absPathA, absPathB, absIgnoredPaths, relNewPath, name,
absNewPathA_what
)
r.simplify(deep=False)
return r
def _diffOne(r:DiffResult, absPathA:str, absPathB:str, absIgnoredPaths:Set[PathAndType],
relCurrentPath:str, filename:str, relCurrentPath_what:PathType
) -> None:
match relCurrentPath_what:
case "d":
rr = _diffDirs(absPathA, absPathB, absIgnoredPaths, relCurrentPath, trust=False) # TODO(debug) add trust=True
r.subresults[filename] = rr
if (len(rr.differentTypes) > 0
or len(rr.presentOnlyInFirst) > 0
or len(rr.presentOnlyInSecond) > 0
or len(rr.differs) > 0
):
r.differs.add( (relCurrentPath_what, filename) )
elif len(rr.weird) > 0:
r.weird.add( filename )
else:
r.same.add( ( relCurrentPath_what, filename ) )
case "f"|"l":
getData:Callable[[str],str|bytes] = (
(lambda path: open(path, "rb").read())
if relCurrentPath_what == "f" else
(lambda path: readlink(path))
)
(
r.same if (
getData(absPathA + "/" + relCurrentPath)
== getData(absPathB + "/" + relCurrentPath)
) else r.differs
).add( (relCurrentPath_what, filename) )
case "w":
r.weird.add( filename )