summaryrefslogtreecommitdiffstats
path: root/Lib/dircmp.py
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>1990-10-13 19:23:40 (GMT)
committerGuido van Rossum <guido@python.org>1990-10-13 19:23:40 (GMT)
commitc636014c430620325f8d213e9ba10d925991b8d7 (patch)
tree058a21f7da3d8c6e7da0756ef7b1402fe7169a1a /Lib/dircmp.py
parentdf79a1ee192231a75a381798bb35cefaf6c31a2a (diff)
downloadcpython-c636014c430620325f8d213e9ba10d925991b8d7.zip
cpython-c636014c430620325f8d213e9ba10d925991b8d7.tar.gz
cpython-c636014c430620325f8d213e9ba10d925991b8d7.tar.bz2
Initial revision
Diffstat (limited to 'Lib/dircmp.py')
-rw-r--r--Lib/dircmp.py215
1 files changed, 215 insertions, 0 deletions
diff --git a/Lib/dircmp.py b/Lib/dircmp.py
new file mode 100644
index 0000000..762a186
--- /dev/null
+++ b/Lib/dircmp.py
@@ -0,0 +1,215 @@
+# Module 'dirmp'
+#
+# Defines a class to build directory diff tools on.
+
+import posix
+
+import path
+
+import dircache
+import cmpcache
+import statcache
+
+
+# File type constants from <sys/stat.h>.
+#
+S_IFDIR = 4
+S_IFREG = 8
+
+# Extract the file type from a stat buffer.
+#
+def S_IFMT(st): return st[0] / 4096
+
+
+# Directory comparison class.
+#
+class dircmp():
+ #
+ def new(dd, (a, b)): # Initialize
+ dd.a = a
+ dd.b = b
+ # Properties that caller may change before callingdd. run():
+ dd.hide = ['.', '..'] # Names never to be shown
+ dd.ignore = ['RCS', 'tags'] # Names ignored in comparison
+ #
+ return dd
+ #
+ def run(dd): # Compare everything except common subdirectories
+ dd.a_list = filter(dircache.listdir(dd.a), dd.hide)
+ dd.b_list = filter(dircache.listdir(dd.b), dd.hide)
+ dd.a_list.sort()
+ dd.b_list.sort()
+ dd.phase1()
+ dd.phase2()
+ dd.phase3()
+ #
+ def phase1(dd): # Compute common names
+ dd.a_only = []
+ dd.common = []
+ for x in dd.a_list:
+ if x in dd.b_list:
+ dd.common.append(x)
+ else:
+ dd.a_only.append(x)
+ #
+ dd.b_only = []
+ for x in dd.b_list:
+ if x not in dd.common:
+ dd.b_only.append(x)
+ #
+ def phase2(dd): # Distinguish files, directories, funnies
+ dd.common_dirs = []
+ dd.common_files = []
+ dd.common_funny = []
+ #
+ for x in dd.common:
+ a_path = path.cat(dd.a, x)
+ b_path = path.cat(dd.b, x)
+ #
+ ok = 1
+ try:
+ a_stat = statcache.stat(a_path)
+ except posix.error, why:
+ # print 'Can\'t stat', a_path, ':', why[1]
+ ok = 0
+ try:
+ b_stat = statcache.stat(b_path)
+ except posix.error, why:
+ # print 'Can\'t stat', b_path, ':', why[1]
+ ok = 0
+ #
+ if ok:
+ a_type = S_IFMT(a_stat)
+ b_type = S_IFMT(b_stat)
+ if a_type <> b_type:
+ dd.common_funny.append(x)
+ elif a_type = S_IFDIR:
+ dd.common_dirs.append(x)
+ elif a_type = S_IFREG:
+ dd.common_files.append(x)
+ else:
+ dd.common_funny.append(x)
+ else:
+ dd.common_funny.append(x)
+ #
+ def phase3(dd): # Find out differences between common files
+ xx = cmpfiles(dd.a, dd.b, dd.common_files)
+ dd.same_files, dd.diff_files, dd.funny_files = xx
+ #
+ def phase4(dd): # Find out differences between common subdirectories
+ # A new dircmp object is created for each common subdirectory,
+ # these are stored in a dictionary indexed by filename.
+ # The hide and ignore properties are inherited from the parent
+ dd.subdirs = {}
+ for x in dd.common_dirs:
+ a_x = path.cat(dd.a, x)
+ b_x = path.cat(dd.b, x)
+ dd.subdirs[x] = newdd = dircmp().new(a_x, b_x)
+ newdd.hide = dd.hide
+ newdd.ignore = dd.ignore
+ newdd.run()
+ #
+ def phase4_closure(dd): # Recursively call phase4() on subdirectories
+ dd.phase4()
+ for x in dd.subdirs.keys():
+ dd.subdirs[x].phase4_closure()
+ #
+ def report(dd): # Print a report on the differences between a and b
+ # Assume that phases 1 to 3 have been executed
+ # Output format is purposely lousy
+ print 'diff', dd.a, dd.b
+ if dd.a_only:
+ print 'Only in', dd.a, ':', dd.a_only
+ if dd.b_only:
+ print 'Only in', dd.b, ':', dd.b_only
+ if dd.same_files:
+ print 'Identical files :', dd.same_files
+ if dd.diff_files:
+ print 'Differing files :', dd.diff_files
+ if dd.funny_files:
+ print 'Trouble with common files :', dd.funny_files
+ if dd.common_dirs:
+ print 'Common subdirectories :', dd.common_dirs
+ if dd.common_funny:
+ print 'Common funny cases :', dd.common_funny
+ #
+ def report_closure(dd): # Print reports on dd and on subdirs
+ # If phase 4 hasn't been done, no subdir reports are printed
+ dd.report()
+ try:
+ x = dd.subdirs
+ except NameError:
+ return # No subdirectories computed
+ for x in dd.subdirs.keys():
+ print
+ dd.subdirs[x].report_closure()
+ #
+ def report_phase4_closure(dd): # Report and do phase 4 recursively
+ dd.report()
+ dd.phase4()
+ for x in dd.subdirs.keys():
+ print
+ dd.subdirs[x].report_phase4_closure()
+
+
+# Compare common files in two directories.
+# Return:
+# - files that compare equal
+# - files that compare different
+# - funny cases (can't stat etc.)
+#
+def cmpfiles(a, b, common):
+ res = ([], [], [])
+ for x in common:
+ res[cmp(path.cat(a, x), path.cat(b, x))].append(x)
+ return res
+
+
+# Compare two files.
+# Return:
+# 0 for equal
+# 1 for different
+# 2 for funny cases (can't stat, etc.)
+#
+def cmp(a, b):
+ try:
+ if cmpcache.cmp(a, b): return 0
+ return 1
+ except posix.error:
+ return 2
+
+
+# Remove a list item.
+# NB: This modifies the list argument.
+#
+def remove(list, item):
+ for i in range(len(list)):
+ if list[i] = item:
+ del list[i]
+ break
+
+
+# Return a copy with items that occur in skip removed.
+#
+def filter(list, skip):
+ result = []
+ for item in list:
+ if item not in skip: result.append(item)
+ return result
+
+
+# Demonstration and testing.
+#
+def demo():
+ import sys
+ import getopt
+ options, args = getopt.getopt(sys.argv[1:], 'r')
+ if len(args) <> 2: raise getopt.error, 'need exactly two args'
+ dd = dircmp().new(args[0], args[1])
+ dd.run()
+ if ('-r', '') in options:
+ dd.report_phase4_closure()
+ else:
+ dd.report()
+
+# demo()