home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pyos2bin.zip / Demo / stdwin / wdiff.py < prev   
Text File  |  1996-11-27  |  11KB  |  485 lines

  1. #! /usr/bin/env python
  2.  
  3. # A window-oriented recursive diff utility.
  4. # NB: This uses undocumented window classing modules.
  5.  
  6. # TO DO:
  7. #    - faster update after moving/copying one file
  8. #    - diff flags (-b, etc.) should be global or maintained per window
  9. #    - use a few fixed windows instead of creating new ones all the time
  10. #    - ways to specify patterns to skip
  11. #      (best by pointing at a file and clicking a special menu entry!)
  12. #    - add rcsdiff menu commands
  13. #    - add a way to view status of selected files without opening them
  14. #    - add a way to diff two files with different names
  15. #    - add a way to rename files
  16. #    - keep backups of overwritten/deleted files
  17. #    - a way to mark specified files as uninteresting for dircmp
  18.  
  19. import sys
  20. import os
  21. import rand
  22. import commands
  23. import dircache
  24. import statcache
  25. import cmp
  26. import cmpcache
  27. import stdwin
  28. import gwin
  29. import textwin
  30. import filewin
  31. import tablewin
  32. import anywin
  33.  
  34. mkarg = commands.mkarg
  35. mk2arg = commands.mk2arg
  36.  
  37. # List of names to ignore in dircmp()
  38. #
  39. skiplist = ['RCS', 'CVS', '.Amake', 'tags', 'TAGS', '.', '..']
  40.  
  41. # Function to determine whether a name should be ignored in dircmp().
  42. #
  43. def skipthis(file):
  44.     return file[-1:] == '~' or file in skiplist
  45.  
  46.  
  47. def anydiff(a, b, flags): # Display differences between any two objects
  48.     print 'diff', flags, a, b
  49.     if os.path.isdir(a) and os.path.isdir(b):
  50.         w = dirdiff(a, b, flags)
  51.     else:
  52.         w = filediff(a, b, flags)
  53.     addstatmenu(w, [a, b])
  54.     w.original_close = w.close
  55.     w.close = close_dirwin
  56.     return w
  57.  
  58. def close_dirwin(w):
  59.     close_subwindows(w, (), 0)
  60.     w.original_close(w)
  61.  
  62. def filediff(a, b, flags): # Display differences between two text files
  63.     diffcmd = 'diff'
  64.     if flags: diffcmd = diffcmd + mkarg(flags)
  65.     diffcmd = diffcmd + mkarg(a) + mkarg(b)
  66.     difftext = commands.getoutput(diffcmd)
  67.     return textwin.open_readonly(mktitle(a, b), difftext)
  68.  
  69. def dirdiff(a, b, flags): # Display differences between two directories
  70.     data = diffdata(a, b, flags)
  71.     w = tablewin.open(mktitle(a, b), data)
  72.     w.flags = flags
  73.     w.a = a
  74.     w.b = b
  75.     addviewmenu(w)
  76.     addactionmenu(w)
  77.     return w
  78.  
  79. def diffdata(a, b, flags): # Compute directory differences.
  80.     #
  81.     a_only = [('A only:', header_action), ('', header_action)]
  82.     b_only = [('B only:', header_action), ('', header_action)]
  83.     ab_diff = [('A <> B:', header_action), ('', header_action)]
  84.     ab_same = [('A == B:', header_action), ('', header_action)]
  85.     data = [a_only, b_only, ab_diff, ab_same]
  86.     #
  87.     a_list = dircache.listdir(a)[:]
  88.     b_list = dircache.listdir(b)[:]
  89.     dircache.annotate(a, a_list)
  90.     dircache.annotate(b, b_list)
  91.     a_list.sort()
  92.     b_list.sort()
  93.     #
  94.     for x in a_list:
  95.         if x in ['./', '../']:
  96.             pass
  97.         elif x not in b_list:
  98.             a_only.append(x, a_only_action)
  99.         else:
  100.             ax = os.path.join(a, x)
  101.             bx = os.path.join(b, x)
  102.             if os.path.isdir(ax) and os.path.isdir(bx):
  103.                 if flags == '-r':
  104.                     same = dircmp(ax, bx)
  105.                 else:
  106.                     same = 0
  107.             else:
  108.                 try:
  109.                     same = cmp.cmp(ax, bx)
  110.                 except (RuntimeError, os.error):
  111.                     same = 0
  112.             if same:
  113.                 ab_same.append(x, ab_same_action)
  114.             else:
  115.                 ab_diff.append(x, ab_diff_action)
  116.     #
  117.     for x in b_list:
  118.         if x in ['./', '../']:
  119.             pass
  120.         elif x not in a_list:
  121.             b_only.append(x, b_only_action)
  122.     #
  123.     return data
  124.  
  125. # Re-read the directory.
  126. # Attempt to find the selected item back.
  127.  
  128. def update(w):
  129.     setbusy(w)
  130.     icol, irow = w.selection
  131.     if 0 <= icol < len(w.data) and 2 <= irow < len(w.data[icol]):
  132.         selname = w.data[icol][irow][0]
  133.     else:
  134.         selname = ''
  135.     statcache.forget_dir(w.a)
  136.     statcache.forget_dir(w.b)
  137.     tablewin.select(w, (-1, -1))
  138.     tablewin.update(w, diffdata(w.a, w.b, w.flags))
  139.     if selname:
  140.         for icol in range(len(w.data)):
  141.             for irow in range(2, len(w.data[icol])):
  142.                 if w.data[icol][irow][0] == selname:
  143.                     tablewin.select(w, (icol, irow))
  144.                     break
  145.  
  146. # Action functions for table items in directory diff windows
  147.  
  148. def header_action(w, string, (icol, irow), (pos, clicks, button, mask)):
  149.     tablewin.select(w, (-1, -1))
  150.  
  151. def a_only_action(w, string, (icol, irow), (pos, clicks, button, mask)):
  152.     tablewin.select(w, (icol, irow))
  153.     if clicks == 2:
  154.         w2 = anyopen(os.path.join(w.a, string))
  155.         if w2:
  156.             w2.parent = w
  157.  
  158. def b_only_action(w, string, (icol, irow), (pos, clicks, button, mask)):
  159.     tablewin.select(w, (icol, irow))
  160.     if clicks == 2:
  161.         w2 = anyopen(os.path.join(w.b, string))
  162.         if w2:
  163.             w2.parent = w
  164.  
  165. def ab_diff_action(w, string, (icol, irow), (pos, clicks, button, mask)):
  166.     tablewin.select(w, (icol, irow))
  167.     if clicks == 2:
  168.         w2 = anydiff(os.path.join(w.a, string), os.path.join(w.b, string),'')
  169.         w2.parent = w
  170.  
  171. def ab_same_action(w, string, sel, detail):
  172.     ax = os.path.join(w.a, string)
  173.     if os.path.isdir(ax):
  174.         ab_diff_action(w, string, sel, detail)
  175.     else:
  176.         a_only_action(w, string, sel, detail)
  177.  
  178. def anyopen(name): # Open any kind of document, ignore errors
  179.     try:
  180.         w = anywin.open(name)
  181.     except (RuntimeError, os.error):
  182.         stdwin.message('Can\'t open ' + name)
  183.         return 0
  184.     addstatmenu(w, [name])
  185.     return w
  186.  
  187. def dircmp(a, b): # Compare whether two directories are the same
  188.     # To make this as fast as possible, it uses the statcache
  189.     print '  dircmp', a, b
  190.     a_list = dircache.listdir(a)
  191.     b_list = dircache.listdir(b)
  192.     for x in a_list:
  193.         if skipthis(x):
  194.             pass
  195.         elif x not in b_list:
  196.             return 0
  197.         else:
  198.             ax = os.path.join(a, x)
  199.             bx = os.path.join(b, x)
  200.             if statcache.isdir(ax) and statcache.isdir(bx):
  201.                 if not dircmp(ax, bx): return 0
  202.             else:
  203.                 try:
  204.                     if not cmpcache.cmp(ax, bx): return 0
  205.                 except (RuntimeError, os.error):
  206.                     return 0
  207.     for x in b_list:
  208.         if skipthis(x):
  209.             pass
  210.         elif x not in a_list:
  211.             return 0
  212.     return 1
  213.  
  214.  
  215. # View menu (for dir diff windows only)
  216.  
  217. def addviewmenu(w):
  218.     w.viewmenu = m = w.menucreate('View')
  219.     m.action = []
  220.     add(m, 'diff -r A B', diffr_ab)
  221.     add(m, 'diff A B', diff_ab)
  222.     add(m, 'diff -b A B', diffb_ab)
  223.     add(m, 'diff -c A B', diffc_ab)
  224.     add(m, 'gdiff A B', gdiff_ab)
  225.     add(m, ('Open A   ', 'A'), open_a)
  226.     add(m, ('Open B   ', 'B'), open_b)
  227.     add(m, 'Rescan', rescan)
  228.     add(m, 'Rescan -r', rescan_r)
  229.  
  230. # Action menu (for dir diff windows only)
  231.  
  232. def addactionmenu(w):
  233.     w.actionmenu = m = w.menucreate('Action')
  234.     m.action = []
  235.     add(m, 'cp A B', cp_ab)
  236.     add(m, 'rm B', rm_b)
  237.     add(m, '', nop)
  238.     add(m, 'cp B A', cp_ba)
  239.     add(m, 'rm A', rm_a)
  240.  
  241. # Main menu (global):
  242.  
  243. def mainmenu():
  244.     m = stdwin.menucreate('Wdiff')
  245.     m.action = []
  246.     add(m, ('Quit wdiff', 'Q'), quit_wdiff)
  247.     add(m, 'Close subwindows', close_subwindows)
  248.     return m
  249.  
  250. def add(m, text, action):
  251.     m.additem(text)
  252.     m.action.append(action)
  253.  
  254. def quit_wdiff(w, m, item):
  255.     if askyesno('Really quit wdiff altogether?', 1):
  256.         sys.exit(0)
  257.  
  258. def close_subwindows(w, m, item):
  259.     while 1:
  260.         for w2 in gwin.windows:
  261.             if w2.parent == w:
  262.                 close_subwindows(w2, m, item)
  263.                 w2.close(w2)
  264.                 break # inner loop, continue outer loop
  265.         else:
  266.             break # outer loop
  267.  
  268. def diffr_ab(w, m, item):
  269.     dodiff(w, '-r')
  270.  
  271. def diff_ab(w, m, item):
  272.     dodiff(w, '')
  273.  
  274. def diffb_ab(w, m, item):
  275.     dodiff(w, '-b')
  276.  
  277. def diffc_ab(w, m, item):
  278.     dodiff(w, '-c')
  279.  
  280. def gdiff_ab(w, m, item): # Call SGI's gdiff utility
  281.     x = getselection(w)
  282.     if x:
  283.         a, b = os.path.join(w.a, x), os.path.join(w.b, x)
  284.         if os.path.isdir(a) or os.path.isdir(b):
  285.             stdwin.fleep() # This is for files only
  286.         else:
  287.             diffcmd = 'gdiff'
  288.             diffcmd = diffcmd + mkarg(a) + mkarg(b) + ' &'
  289.             print diffcmd
  290.             sts = os.system(diffcmd)
  291.             if sts: print 'Exit status', sts
  292.  
  293. def dodiff(w, flags):
  294.     x = getselection(w)
  295.     if x:
  296.         w2 = anydiff(os.path.join(w.a, x), os.path.join(w.b, x), flags)
  297.         w2.parent = w
  298.  
  299. def open_a(w, m, item):
  300.     x = getselection(w)
  301.     if x:
  302.         w2 = anyopen(os.path.join(w.a, x))
  303.         if w2:
  304.             w2.parent = w
  305.  
  306. def open_b(w, m, item):
  307.     x = getselection(w)
  308.     if x:
  309.         w2 = anyopen(os.path.join(w.b, x))
  310.         if w2:
  311.             w2.parent = w
  312.  
  313. def rescan(w, m, item):
  314.     w.flags = ''
  315.     update(w)
  316.  
  317. def rescan_r(w, m, item):
  318.     w.flags = '-r'
  319.     update(w)
  320.  
  321. def rm_a(w, m, item):
  322.     x = getselection(w)
  323.     if x:
  324.         if x[-1:] == '/': x = x[:-1]
  325.         x = os.path.join(w.a, x)
  326.         if os.path.isdir(x):
  327.             if askyesno('Recursively remove A directory ' + x, 1):
  328.                 runcmd('rm -rf' + mkarg(x))
  329.         else:
  330.             runcmd('rm -f' + mkarg(x))
  331.         update(w)
  332.  
  333. def rm_b(w, m, item):
  334.     x = getselection(w)
  335.     if x:
  336.         if x[-1:] == '/': x = x[:-1]
  337.         x = os.path.join(w.b, x)
  338.         if os.path.isdir(x):
  339.             if askyesno('Recursively remove B directory ' + x, 1):
  340.                 runcmd('rm -rf' + mkarg(x))
  341.         else:
  342.             runcmd('rm -f' + mkarg(x))
  343.         update(w)
  344.  
  345. def cp_ab(w, m, item):
  346.     x = getselection(w)
  347.     if x:
  348.         if x[-1:] == '/': x = x[:-1]
  349.         ax = os.path.join(w.a, x)
  350.         bx = os.path.join(w.b, x)
  351.         if os.path.isdir(ax):
  352.             if os.path.exists(bx):
  353.                 m = 'Can\'t copy directory to existing target'
  354.                 stdwin.message(m)
  355.                 return
  356.             runcmd('cp -r' + mkarg(ax) + mkarg(w.b))
  357.         else:
  358.             runcmd('cp' + mkarg(ax) + mk2arg(w.b, x))
  359.         update(w)
  360.  
  361. def cp_ba(w, m, item):
  362.     x = getselection(w)
  363.     if x:
  364.         if x[-1:] == '/': x = x[:-1]
  365.         ax = os.path.join(w.a, x)
  366.         bx = os.path.join(w.b, x)
  367.         if os.path.isdir(bx):
  368.             if os.path.exists(ax):
  369.                 m = 'Can\'t copy directory to existing target'
  370.                 stdwin.message(m)
  371.                 return
  372.             runcmd('cp -r' + mkarg(bx) + mkarg(w.a))
  373.         else:
  374.             runcmd('cp' + mk2arg(w.b, x) + mkarg(ax))
  375.         update(w)
  376.  
  377. def nop(args):
  378.     pass
  379.  
  380. def getselection(w):
  381.     icol, irow = w.selection
  382.     if 0 <= icol < len(w.data):
  383.         if 0 <= irow < len(w.data[icol]):
  384.             return w.data[icol][irow][0]
  385.     stdwin.message('no selection')
  386.     return ''
  387.  
  388. def runcmd(cmd):
  389.     print cmd
  390.     sts, output = commands.getstatusoutput(cmd)
  391.     if sts or output:
  392.         if not output:
  393.             output = 'Exit status ' + `sts`
  394.         stdwin.message(output)
  395.  
  396.  
  397. # Status menu (for all kinds of windows)
  398.  
  399. def addstatmenu(w, files):
  400.     w.statmenu = m = w.menucreate('Stat')
  401.     m.files = files
  402.     m.action = []
  403.     for file in files:
  404.         m.additem(commands.getstatus(file))
  405.         m.action.append(stataction)
  406.  
  407. def stataction(w, m, item): # Menu item action for stat menu
  408.     file = m.files[item]
  409.     try:
  410.         m.setitem(item, commands.getstatus(file))
  411.     except os.error:
  412.         stdwin.message('Can\'t get status for ' + file)
  413.  
  414.  
  415. # Compute a suitable window title from two paths
  416.  
  417. def mktitle(a, b):
  418.     if a == b: return a
  419.     i = 1
  420.     while a[-i:] == b[-i:]: i = i+1
  421.     i = i-1
  422.     if not i:
  423.         return a + '  ' + b
  424.     else:
  425.         return '{' + a[:-i] + ',' + b[:-i] + '}' + a[-i:]
  426.  
  427.  
  428. # Ask a confirmation question
  429.  
  430. def askyesno(prompt, default):
  431.     try:
  432.         return stdwin.askync(prompt, default)
  433.     except KeyboardInterrupt:
  434.         return 0
  435.  
  436.  
  437. # Display a message "busy" in a window, and mark it for updating
  438.  
  439. def setbusy(w):
  440.     left, top = w.getorigin()
  441.     width, height = w.getwinsize()
  442.     right, bottom = left + width, top + height
  443.     d = w.begindrawing()
  444.     d.erase((0, 0), (10000, 10000))
  445.     text = 'Busy...'
  446.     textwidth = d.textwidth(text)
  447.     textheight = d.lineheight()
  448.     h, v = left + (width-textwidth)/2, top + (height-textheight)/2
  449.     d.text((h, v), text)
  450.     del d
  451.     w.change((0, 0), (10000, 10000))
  452.  
  453.  
  454. # Main function
  455.  
  456. def main():
  457.     print 'wdiff: warning: this program does NOT make backups'
  458.     argv = sys.argv
  459.     flags = ''
  460.     if len(argv) >= 2 and argv[1][:1] == '-':
  461.         flags = argv[1]
  462.         del argv[1]
  463.     stdwin.setdefscrollbars(0, 1)
  464.     m = mainmenu() # Create menu earlier than windows
  465.     if len(argv) == 2: # 1 argument
  466.         w = anyopen(argv[1])
  467.         if not w: return
  468.     elif len(argv) == 3: # 2 arguments
  469.         w = anydiff(argv[1], argv[2], flags)
  470.         w.parent = ()
  471.     else:
  472.         sys.stdout = sys.stderr
  473.         print 'usage:', argv[0], '[diff-flags] dir-1 [dir-2]'
  474.         sys.exit(2)
  475.     del w # It's preserved in gwin.windows
  476.     while 1:
  477.         try:
  478.             gwin.mainloop()
  479.             break
  480.         except KeyboardInterrupt:
  481.             pass    # Just continue...
  482.  
  483. # Start the main function (this is a script)
  484. main()
  485.