From 48cd7869b262cc99277ea503786380c339ff63e7 Mon Sep 17 00:00:00 2001 From: zy Date: Fri, 6 Jun 2025 13:32:43 +0200 Subject: [PATCH] s --- dirf/__main__.py | 1 + dirf/diff.py | 244 +++++++++++++++++++++++++++--------------- dirf/mytypes.py | 92 ++++++++++++++-- dirf/whatsthispath.py | 7 +- 4 files changed, 248 insertions(+), 96 deletions(-) diff --git a/dirf/__main__.py b/dirf/__main__.py index cc7d323..d278fd9 100644 --- a/dirf/__main__.py +++ b/dirf/__main__.py @@ -1,5 +1,6 @@ import argparse + def main(): parser = argparse.ArgumentParser() parser.add_argument("") diff --git a/dirf/diff.py b/dirf/diff.py index 195294b..295db62 100644 --- a/dirf/diff.py +++ b/dirf/diff.py @@ -3,8 +3,7 @@ __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 typing import Callable, Final, assert_never from .mytypes import * from .Config import * @@ -14,62 +13,125 @@ 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] = {} + """ + (see 'diff'.) + """ - 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" + type RelPath = Path + """ + represents a path relative to the root of the two compared directories. + """ + + + def __init__(self, first:AbsPath, second:AbsPath, subpath:RelPath) -> None: + """ + (please create me using 'diff'.) + """ + + self.first:Final[AbsPath] = first + """ + path of the first directory compared. + """ + + self.second:Final[AbsPath] = second + """ + path of the second directory compared. + """ + + self.subpath:Final[DiffResult.RelPath] = subpath + """ + self contains info from this directory. + """ + + self.differs:set[NameAndType] = set() + """ + list the children of self.subpath that have a different content + (or link target for a symbolic link) in both directories. + """ + + self.same:set[NameAndType] = set() + """ + list the children of self.subpath that have the same content + (or link target for a symbolic link) in both directories. + """ + + self.differentTypes:set[tuple[Filename,PathType,PathType]] = set() + """ + list the children of self.subpath that have a different PathType + in both directories. + + self.differentTypes[1] is the PathType of the one + in the firstdirectory, self.differentTypes[2] is the PathType + of the one in the second directory. + """ + + self.presentOnlyInFirst:set[NameAndType] = set() + """ + list the children of self.subpath that are present + only in the first directory. + """ + + self.presentOnlyInSecond:set[NameAndType] = set() + """ + list the children of self.subpath that are present + only in the second directory. + """ + + self.weird:set[tuple[Filename,PathTypeW|None,PathTypeW|None]] = set() + """ + list the children that have an unknown type in at least one + of the two directories. They are ignored whe comparing two directories. + + self.weird[1] is the type of the one + in the firstdirectory, self.weird[2] is the type + of the one in the second directory. 'None' means the file doesn't exist + in the directory. + """ + + self.subresults:dict[Filename,DiffResult] = {} + """ + for any directory in self.differs there is a corresponding DiffResult. + """ + + + def print(self) -> None: + """ + print a complete representation of self. + """ - 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) + title(f"comparaison of the two directories") + title(f"- {self.first}") + title(f"- {self.second}") + if self.subpath != "/": + title(f"in the subdirectory {repr(self.subpath)}") + print() - printWith("different", self.differs, reprPathAndType) + printWith("same", self.same, reprFileAndType) + + printWith("different", self.differs, reprFileAndType) 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"exists only in {self.first}", + self.presentOnlyInFirst, reprFileAndType ) - printWith(f"exist only in {reprTarget}", - self.presentOnlyInSecond, reprPathAndType + printWith(f"exists only in {self.second}", + self.presentOnlyInSecond, reprFileAndType ) - printWith("dunno what that is lol", self.weird, - lambda v: v + printWith("unknown types", self.weird, + lambda v: f"{reprPathType(v[1])} / {reprPathType(v[2])}: {v[0]}" ) @@ -79,84 +141,92 @@ def diff(pathA:str, pathB:str, *, ignored:Set[PathAndType]) -> DiffResult: def _diffDirs( - absPathA:str, absPathB:str, absIgnoredPaths:Set[PathAndType], - relCurrentPath:str, *, trust:bool + pathA:AbsPath, pathB:AbsPath, ignored:Set[PathAndType], + currentPath:DiffResult.RelPath, *, trust:bool ) -> DiffResult: - absCurrentPathA = absPathA + "/" + relCurrentPath - absCurrentPathB = absPathB + "/" + relCurrentPath - absCurrentPathA_what = whatsthispath(absCurrentPathA) - absCurrentPathB_what = whatsthispath(absCurrentPathB) + currentPathA = pathA + "/" + currentPath + currentPathB = pathB + "/" + currentPath + currentPathA_type = whatsthispath(currentPathA) + currentPathB_type = whatsthispath(currentPathB) 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 pathA[0] == "/", "'pathA' shall be an absolute path." + assert pathB[0] == "/", "'pathB' shall be an absolute path." + assert currentPath[0] == "/", ( + "'currentPath' shall start with '/'." ) - assert all([ v[1][0] == "/" for v in absIgnoredPaths ]), ( - "'ignoredPaths' must contain only absolute paths." + assert all([ v[1][0] == "/" for v in ignored ]), ( + "'ignoredPaths' shall contain only absolute paths." ) - assert absCurrentPathA_what == 'd', "'pathA' should be a directory." - assert absCurrentPathB_what == 'd', "'pathB' should be a directory." + assert currentPathA_type == 'd', "'pathA' shall be a directory." + assert currentPathB_type == 'd', "'pathB' shall be a directory." - r = DiffResult(relCurrentPath) + r = DiffResult(pathA, pathB, currentPath) - 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: + for name in set(listdir(currentPathA)) | set(listdir(currentPathB)): + newPath:DiffResult.RelPath = currentPath + "/" + name + newPathA:AbsPath = currentPathA + "/" + name + newPathB:AbsPath = currentPathB + "/" + name + newPathA_type = whatsthispath(newPathA) + newPathB_type = whatsthispath(newPathB) + if newPathA_type == "w" or newPathB_type == "w": + # file is weird + r.weird.add( + (name, newPathA_type, newPathB_type) + ) + elif newPathA_type is None: + # file doesnt exist in first + assert newPathB_type is not None + r.presentOnlyInSecond.add((newPathB_type, name)) + elif newPathB_type is None: + # file doesnt exist in second + r.presentOnlyInFirst.add((newPathA_type, name)) + elif newPathA_type != newPathB_type: + # file types are different r.differentTypes.add( - (relNewPath, absNewPathA_what, absNewPathB_what) + (name, newPathA_type, newPathB_type) ) else: + # file type is the same _diffOne(r, - absPathA, absPathB, absIgnoredPaths, relNewPath, name, - absNewPathA_what + pathA, pathB, ignored, newPath, name, + newPathA_type ) - r.simplify(deep=False) return r -def _diffOne(r:DiffResult, absPathA:str, absPathB:str, absIgnoredPaths:Set[PathAndType], - relCurrentPath:str, filename:str, relCurrentPath_what:PathType +def _diffOne(r:DiffResult, pathA:AbsPath, pathB:AbsPath, + ignored:Set[PathAndType], path:DiffResult.RelPath, filename:str, + what:PathType ) -> None: - match relCurrentPath_what: + match what: case "d": - rr = _diffDirs(absPathA, absPathB, absIgnoredPaths, relCurrentPath, trust=False) # TODO(debug) add trust=True + # files are directories + rr = _diffDirs(pathA, pathB, ignored, path, 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 ) + r.differs.add( (what, filename) ) else: - r.same.add( ( relCurrentPath_what, filename ) ) + r.same.add( ( 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)) - ) + def getContent(path:str) -> str|bytes: + nonlocal what + if what == "l": + return readlink(path) + return open(path, "rb").read() ( r.same if ( - getData(absPathA + "/" + relCurrentPath) - == getData(absPathB + "/" + relCurrentPath) + getContent(pathA + "/" + path) + == getContent(pathB + "/" + path) ) else r.differs - ).add( (relCurrentPath_what, filename) ) + ).add( (what, filename) ) - case "w": - r.weird.add( filename ) + case _: + assert_never(what) diff --git a/dirf/mytypes.py b/dirf/mytypes.py index 75e4a12..0678a60 100644 --- a/dirf/mytypes.py +++ b/dirf/mytypes.py @@ -1,16 +1,90 @@ __all__ = [ "PathType", - "PathAndType", + "PathTypeW", + "Filename", + "Path", + "AbsPath", + "NameAndType", + "NameAndTypeW", "reprPathType", - "lineForPathAndType", + "reprFileAndType", + "PathAndType", ] from typing import Literal -type PathType = Literal["d","f","l","w"] +type PathType = Literal["d","f","l"] +""" +All the types that a known element can have: +- 'd': directory +- 'f': file +- 'l': symbolic link +""" -def reprPathType(t:PathType) -> str: +type PathTypeW = PathType|Literal["w"] +""" +All the types that an element can have. +To use when the type of an element can be not in PathType. + +Superset of PathType. + +'w' (weird) means the type is unknown. +""" + + +## +## These types are just aliases to str, but they allow to define +## what a variable can contain. +## + +type Filename = str +""" +represents the name of a file, a directory or whatever else. + +can't contain any '/' obviously. +""" + +type Path = str +""" +represents any path. + +must start with '/'. +""" + +type AbsPath = Path +""" +represents a Path relative to the linux root. +""" + + +type NameAndType = tuple[PathType,Filename] +""" +a Filename and its associated PathType. +""" + +type NameAndTypeW = tuple[PathTypeW,Filename] +""" +a Filename and its associated PathTypeW. +""" + +type PathAndType = tuple[PathType,Filename] +""" +a Path and its associated PathType. +""" + +type PathAndTypeW = tuple[PathTypeW,Filename] +""" +a Path and its associated PathType. +""" + + +def reprPathType(t:PathTypeW|None) -> str: + """ + return a common name in natural language of the given type. + + t == None means the file doesn't exist. + """ match t: case "d": return "directory" @@ -20,9 +94,11 @@ def reprPathType(t:PathType) -> str: return "symbolic link" case "w": return "unknown type" + case None: + return "(doesn't exist)" - -type PathAndType = tuple["PathType",str] - -def lineForPathAndType(v:PathAndType) -> str: +def reprFileAndType(v:NameAndTypeW|PathAndTypeW) -> str: + """ + equivalent to repr() for a NameAndTypeW. + """ return f"{reprPathType(v[0])}: {v[1]}" diff --git a/dirf/whatsthispath.py b/dirf/whatsthispath.py index 4d8e197..0da9e3a 100644 --- a/dirf/whatsthispath.py +++ b/dirf/whatsthispath.py @@ -5,7 +5,12 @@ import os.path from .mytypes import * -def whatsthispath(path:str) -> PathType|None: +def whatsthispath(path:AbsPath) -> PathTypeW|None: + """ + get the PathTypeW of a file. + + if the file doesn't exist, returns None. + """ if os.path.islink(path): return "l" if os.path.isdir(path):