At the release of 1.0.1.
[python/dscho.git] / Lib / dircmp.py
blob1227aa753c3a4683f756ab178da3240c56098af5
1 # Module 'dirmp'
3 # Defines a class to build directory diff tools on.
5 import os
7 import dircache
8 import cmpcache
9 import statcache
10 from stat import *
12 # Directory comparison class.
14 class dircmp:
16 def new(self, a, b): # Initialize
17 self.a = a
18 self.b = b
19 # Properties that caller may change before calling self.run():
20 self.hide = [os.curdir, os.pardir] # Names never to be shown
21 self.ignore = ['RCS', 'tags'] # Names ignored in comparison
23 return self
25 def run(self): # Compare everything except common subdirectories
26 self.a_list = filter(dircache.listdir(self.a), self.hide)
27 self.b_list = filter(dircache.listdir(self.b), self.hide)
28 self.a_list.sort()
29 self.b_list.sort()
30 self.phase1()
31 self.phase2()
32 self.phase3()
34 def phase1(self): # Compute common names
35 self.a_only = []
36 self.common = []
37 for x in self.a_list:
38 if x in self.b_list:
39 self.common.append(x)
40 else:
41 self.a_only.append(x)
43 self.b_only = []
44 for x in self.b_list:
45 if x not in self.common:
46 self.b_only.append(x)
48 def phase2(self): # Distinguish files, directories, funnies
49 self.common_dirs = []
50 self.common_files = []
51 self.common_funny = []
53 for x in self.common:
54 a_path = os.path.join(self.a, x)
55 b_path = os.path.join(self.b, x)
57 ok = 1
58 try:
59 a_stat = statcache.stat(a_path)
60 except os.error, why:
61 # print 'Can\'t stat', a_path, ':', why[1]
62 ok = 0
63 try:
64 b_stat = statcache.stat(b_path)
65 except os.error, why:
66 # print 'Can\'t stat', b_path, ':', why[1]
67 ok = 0
69 if ok:
70 a_type = S_IFMT(a_stat[ST_MODE])
71 b_type = S_IFMT(b_stat[ST_MODE])
72 if a_type <> b_type:
73 self.common_funny.append(x)
74 elif S_ISDIR(a_type):
75 self.common_dirs.append(x)
76 elif S_ISREG(a_type):
77 self.common_files.append(x)
78 else:
79 self.common_funny.append(x)
80 else:
81 self.common_funny.append(x)
83 def phase3(self): # Find out differences between common files
84 xx = cmpfiles(self.a, self.b, self.common_files)
85 self.same_files, self.diff_files, self.funny_files = xx
87 def phase4(self): # Find out differences between common subdirectories
88 # A new dircmp object is created for each common subdirectory,
89 # these are stored in a dictionary indexed by filename.
90 # The hide and ignore properties are inherited from the parent
91 self.subdirs = {}
92 for x in self.common_dirs:
93 a_x = os.path.join(self.a, x)
94 b_x = os.path.join(self.b, x)
95 self.subdirs[x] = newdd = dircmp().new(a_x, b_x)
96 newdd.hide = self.hide
97 newdd.ignore = self.ignore
98 newdd.run()
100 def phase4_closure(self): # Recursively call phase4() on subdirectories
101 self.phase4()
102 for x in self.subdirs.keys():
103 self.subdirs[x].phase4_closure()
105 def report(self): # Print a report on the differences between a and b
106 # Assume that phases 1 to 3 have been executed
107 # Output format is purposely lousy
108 print 'diff', self.a, self.b
109 if self.a_only:
110 print 'Only in', self.a, ':', self.a_only
111 if self.b_only:
112 print 'Only in', self.b, ':', self.b_only
113 if self.same_files:
114 print 'Identical files :', self.same_files
115 if self.diff_files:
116 print 'Differing files :', self.diff_files
117 if self.funny_files:
118 print 'Trouble with common files :', self.funny_files
119 if self.common_dirs:
120 print 'Common subdirectories :', self.common_dirs
121 if self.common_funny:
122 print 'Common funny cases :', self.common_funny
124 def report_closure(self): # Print reports on self and on subdirs
125 # If phase 4 hasn't been done, no subdir reports are printed
126 self.report()
127 try:
128 x = self.subdirs
129 except AttributeError:
130 return # No subdirectories computed
131 for x in self.subdirs.keys():
132 print
133 self.subdirs[x].report_closure()
135 def report_phase4_closure(self): # Report and do phase 4 recursively
136 self.report()
137 self.phase4()
138 for x in self.subdirs.keys():
139 print
140 self.subdirs[x].report_phase4_closure()
143 # Compare common files in two directories.
144 # Return:
145 # - files that compare equal
146 # - files that compare different
147 # - funny cases (can't stat etc.)
149 def cmpfiles(a, b, common):
150 res = ([], [], [])
151 for x in common:
152 res[cmp(os.path.join(a, x), os.path.join(b, x))].append(x)
153 return res
156 # Compare two files.
157 # Return:
158 # 0 for equal
159 # 1 for different
160 # 2 for funny cases (can't stat, etc.)
162 def cmp(a, b):
163 try:
164 if cmpcache.cmp(a, b): return 0
165 return 1
166 except os.error:
167 return 2
170 # Remove a list item.
171 # NB: This modifies the list argument.
173 def remove(list, item):
174 for i in range(len(list)):
175 if list[i] == item:
176 del list[i]
177 break
180 # Return a copy with items that occur in skip removed.
182 def filter(list, skip):
183 result = []
184 for item in list:
185 if item not in skip: result.append(item)
186 return result
189 # Demonstration and testing.
191 def demo():
192 import sys
193 import getopt
194 options, args = getopt.getopt(sys.argv[1:], 'r')
195 if len(args) <> 2: raise getopt.error, 'need exactly two args'
196 dd = dircmp().new(args[0], args[1])
197 dd.run()
198 if ('-r', '') in options:
199 dd.report_phase4_closure()
200 else:
201 dd.report()
203 # demo()