summaryrefslogtreecommitdiff
path: root/bin/scons-diff.py
diff options
context:
space:
mode:
Diffstat (limited to 'bin/scons-diff.py')
-rw-r--r--bin/scons-diff.py195
1 files changed, 195 insertions, 0 deletions
diff --git a/bin/scons-diff.py b/bin/scons-diff.py
new file mode 100644
index 0000000..11d1bda
--- /dev/null
+++ b/bin/scons-diff.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python
+#
+# scons-diff.py - diff-like utility for comparing SCons trees
+#
+# This supports most common diff options (with some quirks, like you can't
+# just say -c and have it use a default value), but canonicalizes the
+# various version strings within the file like __revision__, __build__,
+# etc. so that you can diff trees without having to ignore changes in
+# version lines.
+#
+
+import difflib
+import getopt
+import os.path
+import re
+import sys
+
+Usage = """\
+Usage: scons-diff.py [OPTIONS] dir1 dir2
+Options:
+ -c NUM, --context=NUM Print NUM lines of copied context.
+ -h, --help Print this message and exit.
+ -n Don't canonicalize SCons lines.
+ -q, --quiet Print only whether files differ.
+ -r, --recursive Recursively compare found subdirectories.
+ -s Report when two files are the same.
+ -u NUM, --unified=NUM Print NUM lines of unified context.
+"""
+
+opts, args = getopt.getopt(sys.argv[1:],
+ 'c:dhnqrsu:',
+ ['context=', 'help', 'recursive', 'unified='])
+
+diff_type = None
+edit_type = None
+context = 2
+recursive = False
+report_same = False
+diff_options = []
+
+def diff_line(left, right):
+ if diff_options:
+ opts = ' ' + ' '.join(diff_options)
+ else:
+ opts = ''
+ print 'diff%s %s %s' % (opts, left, right)
+
+for o, a in opts:
+ if o in ('-c', '-u'):
+ diff_type = o
+ context = int(a)
+ diff_options.append(o)
+ elif o in ('-h', '--help'):
+ print Usage
+ sys.exit(0)
+ elif o in ('-n'):
+ diff_options.append(o)
+ edit_type = o
+ elif o in ('-q'):
+ diff_type = o
+ diff_line = lambda l, r: None
+ elif o in ('-r', '--recursive'):
+ recursive = True
+ diff_options.append(o)
+ elif o in ('-s'):
+ report_same = True
+
+try:
+ left, right = args
+except ValueError:
+ sys.stderr.write(Usage)
+ sys.exit(1)
+
+def quiet_diff(a, b, fromfile='', tofile='',
+ fromfiledate='', tofiledate='', n=3, lineterm='\n'):
+ """
+ A function with the same calling signature as difflib.context_diff
+ (diff -c) and difflib.unified_diff (diff -u) but which prints
+ output like the simple, unadorned 'diff" command.
+ """
+ if a == b:
+ return []
+ else:
+ return ['Files %s and %s differ\n' % (fromfile, tofile)]
+
+def simple_diff(a, b, fromfile='', tofile='',
+ fromfiledate='', tofiledate='', n=3, lineterm='\n'):
+ """
+ A function with the same calling signature as difflib.context_diff
+ (diff -c) and difflib.unified_diff (diff -u) but which prints
+ output like the simple, unadorned 'diff" command.
+ """
+ sm = difflib.SequenceMatcher(None, a, b)
+ def comma(x1, x2):
+ return x1+1 == x2 and str(x2) or '%s,%s' % (x1+1, x2)
+ result = []
+ for op, a1, a2, b1, b2 in sm.get_opcodes():
+ if op == 'delete':
+ result.append("%sd%d\n" % (comma(a1, a2), b1))
+ result.extend(map(lambda l: '< ' + l, a[a1:a2]))
+ elif op == 'insert':
+ result.append("%da%s\n" % (a1, comma(b1, b2)))
+ result.extend(map(lambda l: '> ' + l, b[b1:b2]))
+ elif op == 'replace':
+ result.append("%sc%s\n" % (comma(a1, a2), comma(b1, b2)))
+ result.extend(map(lambda l: '< ' + l, a[a1:a2]))
+ result.append('---\n')
+ result.extend(map(lambda l: '> ' + l, b[b1:b2]))
+ return result
+
+diff_map = {
+ '-c' : difflib.context_diff,
+ '-q' : quiet_diff,
+ '-u' : difflib.unified_diff,
+}
+
+diff_function = diff_map.get(diff_type, simple_diff)
+
+baseline_re = re.compile('(# |@REM )/home/\S+/baseline/')
+comment_rev_re = re.compile('(# |@REM )(\S+) 0.96.[CD]\d+ \S+ \S+( knight)')
+revision_re = re.compile('__revision__ = "[^"]*"')
+build_re = re.compile('__build__ = "[^"]*"')
+date_re = re.compile('__date__ = "[^"]*"')
+
+def lines_read(file):
+ return open(file).readlines()
+
+def lines_massage(file):
+ text = open(file).read()
+ text = baseline_re.sub('\\1', text)
+ text = comment_rev_re.sub('\\1\\2\\3', text)
+ text = revision_re.sub('__revision__ = "bin/scons-diff.py"', text)
+ text = build_re.sub('__build__ = "0.96.92.DXXX"', text)
+ text = date_re.sub('__date__ = "2006/08/25 02:59:00"', text)
+ return text.splitlines(1)
+
+lines_map = {
+ '-n' : lines_read,
+}
+
+lines_function = lines_map.get(edit_type, lines_massage)
+
+def do_diff(left, right, diff_subdirs):
+ if os.path.isfile(left) and os.path.isfile(right):
+ diff_file(left, right)
+ elif not os.path.isdir(left):
+ diff_file(left, os.path.join(right, os.path.split(left)[1]))
+ elif not os.path.isdir(right):
+ diff_file(os.path.join(left, os.path.split(right)[1]), right)
+ elif diff_subdirs:
+ diff_dir(left, right)
+
+def diff_file(left, right):
+ l = lines_function(left)
+ r = lines_function(right)
+ d = diff_function(l, r, left, right, context)
+ try:
+ text = ''.join(d)
+ except IndexError:
+ sys.stderr.write('IndexError diffing %s and %s\n' % (left, right))
+ else:
+ if text:
+ diff_line(left, right)
+ print text,
+ elif report_same:
+ print 'Files %s and %s are identical' % (left, right)
+
+def diff_dir(left, right):
+ llist = os.listdir(left)
+ rlist = os.listdir(right)
+ u = {}
+ for l in llist:
+ u[l] = 1
+ for r in rlist:
+ u[r] = 1
+ clist = [ x for x in u.keys() if x[-4:] != '.pyc' ]
+ clist.sort()
+ for x in clist:
+ if x in llist:
+ if x in rlist:
+ do_diff(os.path.join(left, x),
+ os.path.join(right, x),
+ recursive)
+ else:
+ print 'Only in %s: %s' % (left, x)
+ else:
+ print 'Only in %s: %s' % (right, x)
+
+do_diff(left, right, True)
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: