mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 03:44:55 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			484 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			484 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
#! /usr/bin/env python
 | 
						|
 | 
						|
# A window-oriented recursive diff utility.
 | 
						|
# NB: This uses undocumented window classing modules.
 | 
						|
 | 
						|
# TO DO:
 | 
						|
#	- faster update after moving/copying one file
 | 
						|
#	- diff flags (-b, etc.) should be global or maintained per window
 | 
						|
#	- use a few fixed windows instead of creating new ones all the time
 | 
						|
#	- ways to specify patterns to skip
 | 
						|
#	  (best by pointing at a file and clicking a special menu entry!)
 | 
						|
#	- add rcsdiff menu commands
 | 
						|
#	- add a way to view status of selected files without opening them
 | 
						|
#	- add a way to diff two files with different names
 | 
						|
#	- add a way to rename files
 | 
						|
#	- keep backups of overwritten/deleted files
 | 
						|
#	- a way to mark specified files as uninteresting for dircmp
 | 
						|
 | 
						|
import sys
 | 
						|
import os
 | 
						|
import rand
 | 
						|
import commands
 | 
						|
import dircache
 | 
						|
import statcache
 | 
						|
import cmp
 | 
						|
import cmpcache
 | 
						|
import stdwin
 | 
						|
import gwin
 | 
						|
import textwin
 | 
						|
import filewin
 | 
						|
import tablewin
 | 
						|
import anywin
 | 
						|
 | 
						|
mkarg = commands.mkarg
 | 
						|
mk2arg = commands.mk2arg
 | 
						|
 | 
						|
# List of names to ignore in dircmp()
 | 
						|
#
 | 
						|
skiplist = ['RCS', 'CVS', '.Amake', 'tags', 'TAGS', '.', '..']
 | 
						|
 | 
						|
# Function to determine whether a name should be ignored in dircmp().
 | 
						|
#
 | 
						|
def skipthis(file):
 | 
						|
	return file[-1:] == '~' or file in skiplist
 | 
						|
 | 
						|
 | 
						|
def anydiff(a, b, flags): # Display differences between any two objects
 | 
						|
	print 'diff', flags, a, b
 | 
						|
	if os.path.isdir(a) and os.path.isdir(b):
 | 
						|
		w = dirdiff(a, b, flags)
 | 
						|
	else:
 | 
						|
		w = filediff(a, b, flags)
 | 
						|
	addstatmenu(w, [a, b])
 | 
						|
	w.original_close = w.close
 | 
						|
	w.close = close_dirwin
 | 
						|
	return w
 | 
						|
 | 
						|
def close_dirwin(w):
 | 
						|
	close_subwindows(w, (), 0)
 | 
						|
	w.original_close(w)
 | 
						|
 | 
						|
def filediff(a, b, flags): # Display differences between two text files
 | 
						|
	diffcmd = 'diff'
 | 
						|
	if flags: diffcmd = diffcmd + mkarg(flags)
 | 
						|
	diffcmd = diffcmd + mkarg(a) + mkarg(b)
 | 
						|
	difftext = commands.getoutput(diffcmd)
 | 
						|
	return textwin.open_readonly(mktitle(a, b), difftext)
 | 
						|
 | 
						|
def dirdiff(a, b, flags): # Display differences between two directories
 | 
						|
	data = diffdata(a, b, flags)
 | 
						|
	w = tablewin.open(mktitle(a, b), data)
 | 
						|
	w.flags = flags
 | 
						|
	w.a = a
 | 
						|
	w.b = b
 | 
						|
	addviewmenu(w)
 | 
						|
	addactionmenu(w)
 | 
						|
	return w
 | 
						|
 | 
						|
def diffdata(a, b, flags): # Compute directory differences.
 | 
						|
	#
 | 
						|
	a_only = [('A only:', header_action), ('', header_action)]
 | 
						|
	b_only = [('B only:', header_action), ('', header_action)]
 | 
						|
	ab_diff = [('A <> B:', header_action), ('', header_action)]
 | 
						|
	ab_same = [('A == B:', header_action), ('', header_action)]
 | 
						|
	data = [a_only, b_only, ab_diff, ab_same]
 | 
						|
	#
 | 
						|
	a_list = dircache.listdir(a)[:]
 | 
						|
	b_list = dircache.listdir(b)[:]
 | 
						|
	dircache.annotate(a, a_list)
 | 
						|
	dircache.annotate(b, b_list)
 | 
						|
	a_list.sort()
 | 
						|
	b_list.sort()
 | 
						|
	#
 | 
						|
	for x in a_list:
 | 
						|
		if x in ['./', '../']:
 | 
						|
			pass
 | 
						|
		elif x not in b_list:
 | 
						|
			a_only.append(x, a_only_action)
 | 
						|
		else:
 | 
						|
			ax = os.path.join(a, x)
 | 
						|
			bx = os.path.join(b, x)
 | 
						|
			if os.path.isdir(ax) and os.path.isdir(bx):
 | 
						|
				if flags == '-r':
 | 
						|
					same = dircmp(ax, bx)
 | 
						|
				else:
 | 
						|
					same = 0
 | 
						|
			else:
 | 
						|
				try:
 | 
						|
					same = cmp.cmp(ax, bx)
 | 
						|
				except (RuntimeError, os.error):
 | 
						|
					same = 0
 | 
						|
			if same:
 | 
						|
				ab_same.append(x, ab_same_action)
 | 
						|
			else:
 | 
						|
				ab_diff.append(x, ab_diff_action)
 | 
						|
	#
 | 
						|
	for x in b_list:
 | 
						|
		if x in ['./', '../']:
 | 
						|
			pass
 | 
						|
		elif x not in a_list:
 | 
						|
			b_only.append(x, b_only_action)
 | 
						|
	#
 | 
						|
	return data
 | 
						|
 | 
						|
# Re-read the directory.
 | 
						|
# Attempt to find the selected item back.
 | 
						|
 | 
						|
def update(w):
 | 
						|
	setbusy(w)
 | 
						|
	icol, irow = w.selection
 | 
						|
	if 0 <= icol < len(w.data) and 2 <= irow < len(w.data[icol]):
 | 
						|
		selname = w.data[icol][irow][0]
 | 
						|
	else:
 | 
						|
		selname = ''
 | 
						|
	statcache.forget_dir(w.a)
 | 
						|
	statcache.forget_dir(w.b)
 | 
						|
	tablewin.select(w, (-1, -1))
 | 
						|
	tablewin.update(w, diffdata(w.a, w.b, w.flags))
 | 
						|
	if selname:
 | 
						|
		for icol in range(len(w.data)):
 | 
						|
			for irow in range(2, len(w.data[icol])):
 | 
						|
				if w.data[icol][irow][0] == selname:
 | 
						|
					tablewin.select(w, (icol, irow))
 | 
						|
					break
 | 
						|
 | 
						|
# Action functions for table items in directory diff windows
 | 
						|
 | 
						|
def header_action(w, string, (icol, irow), (pos, clicks, button, mask)):
 | 
						|
	tablewin.select(w, (-1, -1))
 | 
						|
 | 
						|
def a_only_action(w, string, (icol, irow), (pos, clicks, button, mask)):
 | 
						|
	tablewin.select(w, (icol, irow))
 | 
						|
	if clicks == 2:
 | 
						|
		w2 = anyopen(os.path.join(w.a, string))
 | 
						|
		if w2:
 | 
						|
			w2.parent = w
 | 
						|
 | 
						|
def b_only_action(w, string, (icol, irow), (pos, clicks, button, mask)):
 | 
						|
	tablewin.select(w, (icol, irow))
 | 
						|
	if clicks == 2:
 | 
						|
		w2 = anyopen(os.path.join(w.b, string))
 | 
						|
		if w2:
 | 
						|
			w2.parent = w
 | 
						|
 | 
						|
def ab_diff_action(w, string, (icol, irow), (pos, clicks, button, mask)):
 | 
						|
	tablewin.select(w, (icol, irow))
 | 
						|
	if clicks == 2:
 | 
						|
		w2 = anydiff(os.path.join(w.a, string), os.path.join(w.b, string),'')
 | 
						|
		w2.parent = w
 | 
						|
 | 
						|
def ab_same_action(w, string, sel, detail):
 | 
						|
	ax = os.path.join(w.a, string)
 | 
						|
	if os.path.isdir(ax):
 | 
						|
		ab_diff_action(w, string, sel, detail)
 | 
						|
	else:
 | 
						|
		a_only_action(w, string, sel, detail)
 | 
						|
 | 
						|
def anyopen(name): # Open any kind of document, ignore errors
 | 
						|
	try:
 | 
						|
		w = anywin.open(name)
 | 
						|
	except (RuntimeError, os.error):
 | 
						|
		stdwin.message('Can\'t open ' + name)
 | 
						|
		return 0
 | 
						|
	addstatmenu(w, [name])
 | 
						|
	return w
 | 
						|
 | 
						|
def dircmp(a, b): # Compare whether two directories are the same
 | 
						|
	# To make this as fast as possible, it uses the statcache
 | 
						|
	print '  dircmp', a, b
 | 
						|
	a_list = dircache.listdir(a)
 | 
						|
	b_list = dircache.listdir(b)
 | 
						|
	for x in a_list:
 | 
						|
		if skipthis(x):
 | 
						|
			pass
 | 
						|
		elif x not in b_list:
 | 
						|
			return 0
 | 
						|
		else:
 | 
						|
			ax = os.path.join(a, x)
 | 
						|
			bx = os.path.join(b, x)
 | 
						|
			if statcache.isdir(ax) and statcache.isdir(bx):
 | 
						|
				if not dircmp(ax, bx): return 0
 | 
						|
			else:
 | 
						|
				try:
 | 
						|
					if not cmpcache.cmp(ax, bx): return 0
 | 
						|
				except (RuntimeError, os.error):
 | 
						|
					return 0
 | 
						|
	for x in b_list:
 | 
						|
		if skipthis(x):
 | 
						|
			pass
 | 
						|
		elif x not in a_list:
 | 
						|
			return 0
 | 
						|
	return 1
 | 
						|
 | 
						|
 | 
						|
# View menu (for dir diff windows only)
 | 
						|
 | 
						|
def addviewmenu(w):
 | 
						|
	w.viewmenu = m = w.menucreate('View')
 | 
						|
	m.action = []
 | 
						|
	add(m, 'diff -r A B', diffr_ab)
 | 
						|
	add(m, 'diff A B', diff_ab)
 | 
						|
	add(m, 'diff -b A B', diffb_ab)
 | 
						|
	add(m, 'diff -c A B', diffc_ab)
 | 
						|
	add(m, 'gdiff A B', gdiff_ab)
 | 
						|
	add(m, ('Open A   ', 'A'), open_a)
 | 
						|
	add(m, ('Open B   ', 'B'), open_b)
 | 
						|
	add(m, 'Rescan', rescan)
 | 
						|
	add(m, 'Rescan -r', rescan_r)
 | 
						|
 | 
						|
# Action menu (for dir diff windows only)
 | 
						|
 | 
						|
def addactionmenu(w):
 | 
						|
	w.actionmenu = m = w.menucreate('Action')
 | 
						|
	m.action = []
 | 
						|
	add(m, 'cp A B', cp_ab)
 | 
						|
	add(m, 'rm B', rm_b)
 | 
						|
	add(m, '', nop)
 | 
						|
	add(m, 'cp B A', cp_ba)
 | 
						|
	add(m, 'rm A', rm_a)
 | 
						|
 | 
						|
# Main menu (global):
 | 
						|
 | 
						|
def mainmenu():
 | 
						|
	m = stdwin.menucreate('Wdiff')
 | 
						|
	m.action = []
 | 
						|
	add(m, ('Quit wdiff', 'Q'), quit_wdiff)
 | 
						|
	add(m, 'Close subwindows', close_subwindows)
 | 
						|
	return m
 | 
						|
 | 
						|
def add(m, text, action):
 | 
						|
	m.additem(text)
 | 
						|
	m.action.append(action)
 | 
						|
 | 
						|
def quit_wdiff(w, m, item):
 | 
						|
	if askyesno('Really quit wdiff altogether?', 1):
 | 
						|
		sys.exit(0)
 | 
						|
 | 
						|
def close_subwindows(w, m, item):
 | 
						|
	while 1:
 | 
						|
		for w2 in gwin.windows:
 | 
						|
			if w2.parent == w:
 | 
						|
				close_subwindows(w2, m, item)
 | 
						|
				w2.close(w2)
 | 
						|
				break # inner loop, continue outer loop
 | 
						|
		else:
 | 
						|
			break # outer loop
 | 
						|
 | 
						|
def diffr_ab(w, m, item):
 | 
						|
	dodiff(w, '-r')
 | 
						|
 | 
						|
def diff_ab(w, m, item):
 | 
						|
	dodiff(w, '')
 | 
						|
 | 
						|
def diffb_ab(w, m, item):
 | 
						|
	dodiff(w, '-b')
 | 
						|
 | 
						|
def diffc_ab(w, m, item):
 | 
						|
	dodiff(w, '-c')
 | 
						|
 | 
						|
def gdiff_ab(w, m, item): # Call SGI's gdiff utility
 | 
						|
	x = getselection(w)
 | 
						|
	if x:
 | 
						|
		a, b = os.path.join(w.a, x), os.path.join(w.b, x)
 | 
						|
		if os.path.isdir(a) or os.path.isdir(b):
 | 
						|
			stdwin.fleep() # This is for files only
 | 
						|
		else:
 | 
						|
			diffcmd = 'gdiff'
 | 
						|
			diffcmd = diffcmd + mkarg(a) + mkarg(b) + ' &'
 | 
						|
			print diffcmd
 | 
						|
			sts = os.system(diffcmd)
 | 
						|
			if sts: print 'Exit status', sts
 | 
						|
 | 
						|
def dodiff(w, flags):
 | 
						|
	x = getselection(w)
 | 
						|
	if x:
 | 
						|
		w2 = anydiff(os.path.join(w.a, x), os.path.join(w.b, x), flags)
 | 
						|
		w2.parent = w
 | 
						|
 | 
						|
def open_a(w, m, item):
 | 
						|
	x = getselection(w)
 | 
						|
	if x:
 | 
						|
		w2 = anyopen(os.path.join(w.a, x))
 | 
						|
		if w2:
 | 
						|
			w2.parent = w
 | 
						|
 | 
						|
def open_b(w, m, item):
 | 
						|
	x = getselection(w)
 | 
						|
	if x:
 | 
						|
		w2 = anyopen(os.path.join(w.b, x))
 | 
						|
		if w2:
 | 
						|
			w2.parent = w
 | 
						|
 | 
						|
def rescan(w, m, item):
 | 
						|
	w.flags = ''
 | 
						|
	update(w)
 | 
						|
 | 
						|
def rescan_r(w, m, item):
 | 
						|
	w.flags = '-r'
 | 
						|
	update(w)
 | 
						|
 | 
						|
def rm_a(w, m, item):
 | 
						|
	x = getselection(w)
 | 
						|
	if x:
 | 
						|
		if x[-1:] == '/': x = x[:-1]
 | 
						|
		x = os.path.join(w.a, x)
 | 
						|
		if os.path.isdir(x):
 | 
						|
			if askyesno('Recursively remove A directory ' + x, 1):
 | 
						|
				runcmd('rm -rf' + mkarg(x))
 | 
						|
		else:
 | 
						|
			runcmd('rm -f' + mkarg(x))
 | 
						|
		update(w)
 | 
						|
 | 
						|
def rm_b(w, m, item):
 | 
						|
	x = getselection(w)
 | 
						|
	if x:
 | 
						|
		if x[-1:] == '/': x = x[:-1]
 | 
						|
		x = os.path.join(w.b, x)
 | 
						|
		if os.path.isdir(x):
 | 
						|
			if askyesno('Recursively remove B directory ' + x, 1):
 | 
						|
				runcmd('rm -rf' + mkarg(x))
 | 
						|
		else:
 | 
						|
			runcmd('rm -f' + mkarg(x))
 | 
						|
		update(w)
 | 
						|
 | 
						|
def cp_ab(w, m, item):
 | 
						|
	x = getselection(w)
 | 
						|
	if x:
 | 
						|
		if x[-1:] == '/': x = x[:-1]
 | 
						|
		ax = os.path.join(w.a, x)
 | 
						|
		bx = os.path.join(w.b, x)
 | 
						|
		if os.path.isdir(ax):
 | 
						|
			if os.path.exists(bx):
 | 
						|
				m = 'Can\'t copy directory to existing target'
 | 
						|
				stdwin.message(m)
 | 
						|
				return
 | 
						|
			runcmd('cp -r' + mkarg(ax) + mkarg(w.b))
 | 
						|
		else:
 | 
						|
			runcmd('cp' + mkarg(ax) + mk2arg(w.b, x))
 | 
						|
		update(w)
 | 
						|
 | 
						|
def cp_ba(w, m, item):
 | 
						|
	x = getselection(w)
 | 
						|
	if x:
 | 
						|
		if x[-1:] == '/': x = x[:-1]
 | 
						|
		ax = os.path.join(w.a, x)
 | 
						|
		bx = os.path.join(w.b, x)
 | 
						|
		if os.path.isdir(bx):
 | 
						|
			if os.path.exists(ax):
 | 
						|
				m = 'Can\'t copy directory to existing target'
 | 
						|
				stdwin.message(m)
 | 
						|
				return
 | 
						|
			runcmd('cp -r' + mkarg(bx) + mkarg(w.a))
 | 
						|
		else:
 | 
						|
			runcmd('cp' + mk2arg(w.b, x) + mkarg(ax))
 | 
						|
		update(w)
 | 
						|
 | 
						|
def nop(args):
 | 
						|
	pass
 | 
						|
 | 
						|
def getselection(w):
 | 
						|
	icol, irow = w.selection
 | 
						|
	if 0 <= icol < len(w.data):
 | 
						|
		if 0 <= irow < len(w.data[icol]):
 | 
						|
			return w.data[icol][irow][0]
 | 
						|
	stdwin.message('no selection')
 | 
						|
	return ''
 | 
						|
 | 
						|
def runcmd(cmd):
 | 
						|
	print cmd
 | 
						|
	sts, output = commands.getstatusoutput(cmd)
 | 
						|
	if sts or output:
 | 
						|
		if not output:
 | 
						|
			output = 'Exit status ' + `sts`
 | 
						|
		stdwin.message(output)
 | 
						|
 | 
						|
 | 
						|
# Status menu (for all kinds of windows)
 | 
						|
 | 
						|
def addstatmenu(w, files):
 | 
						|
	w.statmenu = m = w.menucreate('Stat')
 | 
						|
	m.files = files
 | 
						|
	m.action = []
 | 
						|
	for file in files:
 | 
						|
		m.additem(commands.getstatus(file))
 | 
						|
		m.action.append(stataction)
 | 
						|
 | 
						|
def stataction(w, m, item): # Menu item action for stat menu
 | 
						|
	file = m.files[item]
 | 
						|
	try:
 | 
						|
		m.setitem(item, commands.getstatus(file))
 | 
						|
	except os.error:
 | 
						|
		stdwin.message('Can\'t get status for ' + file)
 | 
						|
 | 
						|
 | 
						|
# Compute a suitable window title from two paths
 | 
						|
 | 
						|
def mktitle(a, b):
 | 
						|
	if a == b: return a
 | 
						|
	i = 1
 | 
						|
	while a[-i:] == b[-i:]: i = i+1
 | 
						|
	i = i-1
 | 
						|
	if not i:
 | 
						|
		return a + '  ' + b
 | 
						|
	else:
 | 
						|
		return '{' + a[:-i] + ',' + b[:-i] + '}' + a[-i:]
 | 
						|
 | 
						|
 | 
						|
# Ask a confirmation question
 | 
						|
 | 
						|
def askyesno(prompt, default):
 | 
						|
	try:
 | 
						|
		return stdwin.askync(prompt, default)
 | 
						|
	except KeyboardInterrupt:
 | 
						|
		return 0
 | 
						|
 | 
						|
 | 
						|
# Display a message "busy" in a window, and mark it for updating
 | 
						|
 | 
						|
def setbusy(w):
 | 
						|
	left, top = w.getorigin()
 | 
						|
	width, height = w.getwinsize()
 | 
						|
	right, bottom = left + width, top + height
 | 
						|
	d = w.begindrawing()
 | 
						|
	d.erase((0, 0), (10000, 10000))
 | 
						|
	text = 'Busy...'
 | 
						|
	textwidth = d.textwidth(text)
 | 
						|
	textheight = d.lineheight()
 | 
						|
	h, v = left + (width-textwidth)/2, top + (height-textheight)/2
 | 
						|
	d.text((h, v), text)
 | 
						|
	del d
 | 
						|
	w.change((0, 0), (10000, 10000))
 | 
						|
 | 
						|
 | 
						|
# Main function
 | 
						|
 | 
						|
def main():
 | 
						|
	print 'wdiff: warning: this program does NOT make backups'
 | 
						|
	argv = sys.argv
 | 
						|
	flags = ''
 | 
						|
	if len(argv) >= 2 and argv[1][:1] == '-':
 | 
						|
		flags = argv[1]
 | 
						|
		del argv[1]
 | 
						|
	stdwin.setdefscrollbars(0, 1)
 | 
						|
	m = mainmenu() # Create menu earlier than windows
 | 
						|
	if len(argv) == 2: # 1 argument
 | 
						|
		w = anyopen(argv[1])
 | 
						|
		if not w: return
 | 
						|
	elif len(argv) == 3: # 2 arguments
 | 
						|
		w = anydiff(argv[1], argv[2], flags)
 | 
						|
		w.parent = ()
 | 
						|
	else:
 | 
						|
		sys.stdout = sys.stderr
 | 
						|
		print 'usage:', argv[0], '[diff-flags] dir-1 [dir-2]'
 | 
						|
		sys.exit(2)
 | 
						|
	del w # It's preserved in gwin.windows
 | 
						|
	while 1:
 | 
						|
		try:
 | 
						|
			gwin.mainloop()
 | 
						|
			break
 | 
						|
		except KeyboardInterrupt:
 | 
						|
			pass	# Just continue...
 | 
						|
 | 
						|
# Start the main function (this is a script)
 | 
						|
main()
 |