s
This commit is contained in:
parent
f85db5191e
commit
48cd7869b2
4 changed files with 248 additions and 96 deletions
|
@ -1,5 +1,6 @@
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("")
|
parser.add_argument("")
|
||||||
|
|
244
dirf/diff.py
244
dirf/diff.py
|
@ -3,8 +3,7 @@ __add__ = [ "diff" ]
|
||||||
from collections.abc import Set
|
from collections.abc import Set
|
||||||
from os import listdir, readlink
|
from os import listdir, readlink
|
||||||
from os.path import abspath
|
from os.path import abspath
|
||||||
from typing import Callable, Final
|
from typing import Callable, Final, assert_never
|
||||||
from collections.abc import Iterable
|
|
||||||
|
|
||||||
from .mytypes import *
|
from .mytypes import *
|
||||||
from .Config import *
|
from .Config import *
|
||||||
|
@ -14,62 +13,125 @@ from .utils import *
|
||||||
|
|
||||||
|
|
||||||
class DiffResult:
|
class DiffResult:
|
||||||
def __init__(self, path:str) -> None:
|
"""
|
||||||
self.path:Final[str] = path
|
(see 'diff'.)
|
||||||
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:
|
type RelPath = Path
|
||||||
reprSource = repr(source) if source is not None else "the source directory"
|
"""
|
||||||
reprTarget = repr(target) if target is not None else "the target directory"
|
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]):
|
def printWith[T](label:str, what:set[T], f:Callable[[T],str]):
|
||||||
if len(what) == 0:
|
if len(what) == 0:
|
||||||
return
|
return
|
||||||
print(label + ":")
|
print(label + ":")
|
||||||
for v in what:
|
for v in what:
|
||||||
print(f"-\t{f(v)}")
|
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,
|
printWith("different types", self.differentTypes,
|
||||||
lambda v: f"{reprPathType(v[1])} / {reprPathType(v[2])}: {v[0]}"
|
lambda v: f"{reprPathType(v[1])} / {reprPathType(v[2])}: {v[0]}"
|
||||||
)
|
)
|
||||||
|
|
||||||
printWith(f"exist only in {reprSource}",
|
printWith(f"exists only in {self.first}",
|
||||||
self.presentOnlyInFirst, reprPathAndType
|
self.presentOnlyInFirst, reprFileAndType
|
||||||
)
|
)
|
||||||
|
|
||||||
printWith(f"exist only in {reprTarget}",
|
printWith(f"exists only in {self.second}",
|
||||||
self.presentOnlyInSecond, reprPathAndType
|
self.presentOnlyInSecond, reprFileAndType
|
||||||
)
|
)
|
||||||
|
|
||||||
printWith("dunno what that is lol", self.weird,
|
printWith("unknown types", self.weird,
|
||||||
lambda v: v
|
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(
|
def _diffDirs(
|
||||||
absPathA:str, absPathB:str, absIgnoredPaths:Set[PathAndType],
|
pathA:AbsPath, pathB:AbsPath, ignored:Set[PathAndType],
|
||||||
relCurrentPath:str, *, trust:bool
|
currentPath:DiffResult.RelPath, *, trust:bool
|
||||||
) -> DiffResult:
|
) -> DiffResult:
|
||||||
|
|
||||||
absCurrentPathA = absPathA + "/" + relCurrentPath
|
currentPathA = pathA + "/" + currentPath
|
||||||
absCurrentPathB = absPathB + "/" + relCurrentPath
|
currentPathB = pathB + "/" + currentPath
|
||||||
absCurrentPathA_what = whatsthispath(absCurrentPathA)
|
currentPathA_type = whatsthispath(currentPathA)
|
||||||
absCurrentPathB_what = whatsthispath(absCurrentPathB)
|
currentPathB_type = whatsthispath(currentPathB)
|
||||||
|
|
||||||
if not trust:
|
if not trust:
|
||||||
assert absPathA[0] == "/", "'pathA' should be an absolute path."
|
assert pathA[0] == "/", "'pathA' shall be an absolute path."
|
||||||
assert absPathB[0] == "/", "'pathB' should be an absolute path."
|
assert pathB[0] == "/", "'pathB' shall be an absolute path."
|
||||||
assert relCurrentPath[0] == "/", (
|
assert currentPath[0] == "/", (
|
||||||
"'relCurrentPath' should start with '/'."
|
"'currentPath' shall start with '/'."
|
||||||
)
|
)
|
||||||
assert all([ v[1][0] == "/" for v in absIgnoredPaths ]), (
|
assert all([ v[1][0] == "/" for v in ignored ]), (
|
||||||
"'ignoredPaths' must contain only absolute paths."
|
"'ignoredPaths' shall contain only absolute paths."
|
||||||
)
|
)
|
||||||
assert absCurrentPathA_what == 'd', "'pathA' should be a directory."
|
assert currentPathA_type == 'd', "'pathA' shall be a directory."
|
||||||
assert absCurrentPathB_what == 'd', "'pathB' should 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)):
|
for name in set(listdir(currentPathA)) | set(listdir(currentPathB)):
|
||||||
relNewPath = relCurrentPath + "/" + name
|
newPath:DiffResult.RelPath = currentPath + "/" + name
|
||||||
absNewPathA = absCurrentPathA + "/" + name
|
newPathA:AbsPath = currentPathA + "/" + name
|
||||||
absNewPathB = absCurrentPathB + "/" + name
|
newPathB:AbsPath = currentPathB + "/" + name
|
||||||
absNewPathA_what = whatsthispath(absNewPathA)
|
newPathA_type = whatsthispath(newPathA)
|
||||||
absNewPathB_what = whatsthispath(absNewPathB)
|
newPathB_type = whatsthispath(newPathB)
|
||||||
if absNewPathA_what is None:
|
if newPathA_type == "w" or newPathB_type == "w":
|
||||||
assert absNewPathB_what is not None
|
# file is weird
|
||||||
r.presentOnlyInSecond.add((absNewPathB_what, relNewPath))
|
r.weird.add(
|
||||||
elif absNewPathB_what is None:
|
(name, newPathA_type, newPathB_type)
|
||||||
r.presentOnlyInFirst.add((absNewPathA_what, relNewPath))
|
)
|
||||||
elif absNewPathA_what != absNewPathB_what:
|
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(
|
r.differentTypes.add(
|
||||||
(relNewPath, absNewPathA_what, absNewPathB_what)
|
(name, newPathA_type, newPathB_type)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
# file type is the same
|
||||||
_diffOne(r,
|
_diffOne(r,
|
||||||
absPathA, absPathB, absIgnoredPaths, relNewPath, name,
|
pathA, pathB, ignored, newPath, name,
|
||||||
absNewPathA_what
|
newPathA_type
|
||||||
)
|
)
|
||||||
|
|
||||||
r.simplify(deep=False)
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def _diffOne(r:DiffResult, absPathA:str, absPathB:str, absIgnoredPaths:Set[PathAndType],
|
def _diffOne(r:DiffResult, pathA:AbsPath, pathB:AbsPath,
|
||||||
relCurrentPath:str, filename:str, relCurrentPath_what:PathType
|
ignored:Set[PathAndType], path:DiffResult.RelPath, filename:str,
|
||||||
|
what:PathType
|
||||||
) -> None:
|
) -> None:
|
||||||
match relCurrentPath_what:
|
match what:
|
||||||
case "d":
|
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
|
r.subresults[filename] = rr
|
||||||
if (len(rr.differentTypes) > 0
|
if (len(rr.differentTypes) > 0
|
||||||
or len(rr.presentOnlyInFirst) > 0
|
or len(rr.presentOnlyInFirst) > 0
|
||||||
or len(rr.presentOnlyInSecond) > 0
|
or len(rr.presentOnlyInSecond) > 0
|
||||||
or len(rr.differs) > 0
|
or len(rr.differs) > 0
|
||||||
):
|
):
|
||||||
r.differs.add( (relCurrentPath_what, filename) )
|
r.differs.add( (what, filename) )
|
||||||
elif len(rr.weird) > 0:
|
|
||||||
r.weird.add( filename )
|
|
||||||
else:
|
else:
|
||||||
r.same.add( ( relCurrentPath_what, filename ) )
|
r.same.add( ( what, filename ) )
|
||||||
|
|
||||||
case "f"|"l":
|
case "f"|"l":
|
||||||
getData:Callable[[str],str|bytes] = (
|
def getContent(path:str) -> str|bytes:
|
||||||
(lambda path: open(path, "rb").read())
|
nonlocal what
|
||||||
if relCurrentPath_what == "f" else
|
if what == "l":
|
||||||
(lambda path: readlink(path))
|
return readlink(path)
|
||||||
)
|
return open(path, "rb").read()
|
||||||
(
|
(
|
||||||
r.same if (
|
r.same if (
|
||||||
getData(absPathA + "/" + relCurrentPath)
|
getContent(pathA + "/" + path)
|
||||||
== getData(absPathB + "/" + relCurrentPath)
|
== getContent(pathB + "/" + path)
|
||||||
) else r.differs
|
) else r.differs
|
||||||
).add( (relCurrentPath_what, filename) )
|
).add( (what, filename) )
|
||||||
|
|
||||||
case "w":
|
case _:
|
||||||
r.weird.add( filename )
|
assert_never(what)
|
||||||
|
|
|
@ -1,16 +1,90 @@
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"PathType",
|
"PathType",
|
||||||
"PathAndType",
|
"PathTypeW",
|
||||||
|
"Filename",
|
||||||
|
"Path",
|
||||||
|
"AbsPath",
|
||||||
|
"NameAndType",
|
||||||
|
"NameAndTypeW",
|
||||||
"reprPathType",
|
"reprPathType",
|
||||||
"lineForPathAndType",
|
"reprFileAndType",
|
||||||
|
"PathAndType",
|
||||||
]
|
]
|
||||||
|
|
||||||
from typing import Literal
|
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:
|
match t:
|
||||||
case "d":
|
case "d":
|
||||||
return "directory"
|
return "directory"
|
||||||
|
@ -20,9 +94,11 @@ def reprPathType(t:PathType) -> str:
|
||||||
return "symbolic link"
|
return "symbolic link"
|
||||||
case "w":
|
case "w":
|
||||||
return "unknown type"
|
return "unknown type"
|
||||||
|
case None:
|
||||||
|
return "(doesn't exist)"
|
||||||
|
|
||||||
|
def reprFileAndType(v:NameAndTypeW|PathAndTypeW) -> str:
|
||||||
type PathAndType = tuple["PathType",str]
|
"""
|
||||||
|
equivalent to repr() for a NameAndTypeW.
|
||||||
def lineForPathAndType(v:PathAndType) -> str:
|
"""
|
||||||
return f"{reprPathType(v[0])}: {v[1]}"
|
return f"{reprPathType(v[0])}: {v[1]}"
|
||||||
|
|
|
@ -5,7 +5,12 @@ import os.path
|
||||||
from .mytypes import *
|
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):
|
if os.path.islink(path):
|
||||||
return "l"
|
return "l"
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
|
|
Loading…
Add table
Reference in a new issue