from cvsgui.App import *
from cvsgui.Cvs import *
from cvsgui.CvsEntry import *
from cvsgui.Macro import *
from cvsgui.Persistent import *
from cvsgui.SafeTk import *
from Tkinter import *
import os, os.path

"""
  CvsGui Macro "Recursive Add with auto-commit option"
  $Revision: 1.5 $

  written by Oliver Giesen, Aug 2002
  contact:
    email:  ogware@gmx.net
    jabber: ogiesen@jabber.org
    icq:    18777742

  Feel free to modify or distribute in whichever way you like,
   as long as it doesn't limit my personal rights to modify and
   redistribute this code.
   Apart from that the code is supplied "as-is", i.e. without warranty of any
   kind, either expressed or implied, regarding its quality or security.
   Have fun!

  ATENTION:
   In its current form this macro will only run with Python 2.2.1 !!!
   (with earlier Python builds you would get search path related errors,
   which I have not yet been able to resolve. Currently the macro will
   not register in the WinCvs menu at all if TK is not available.)

   You will also need at least WinCvs 1.3.5 to execute any Python macros
   from within WinCvs! This macro has been tested against WinCvs 1.3.8 .

  ======
  Usage:

  -Select one or more non-CVS folders

  -Run the Macro from the "Macros|CVS" menu.

  ~A question dialog will pop up asking whether you want to want ignored
   files to be included in the selection list.

  -Answer Yes or No accordingly

  ~You will be presented with a dialog in which you could specify which
   keyword substitution mode to use for which file extension
   (the defaults could be configured in the source below) by means of
   radio buttons in the left hand pane of the dialog.

  -Check the appropriate radio buttons. If you want to exclude a file type
   tick the "ignore" button.

  ~The right hand pane of the window allows you to set the options for
   "auto-commit", i.e. to automatically commit the newly added files.

   -If you want to automatically commit the added files, tick the
    "Auto-commit" checkbox.

   -Enter the commit comment for the new files.

   -If you want to specify additional commit options (e.g. -r to force
    a revision number) you could do so in
    the entry box at the bottom of the dialog. just type them as you
    would on the commandline (on invokation they will be used between
    "cvs commit" and the file names).

  -Hit the "Add!" button if you want the files and folders to be added or
   "Cancel" if you want to abort.

  =============
  Known Issues / "Un-niceties":

  - There is no detection of text/binary being done. The initial classification
   is solely based on the hardcoded default lists at the top of the source below.
   I haven't yet found a suitable algorithm to implement this but might well do
   so in the future.
   
  - The classifications done by the user are currently not remembered. I am
   already working on this but with WinCvs' persistency interface being as
   quirky and fragile as it is at the moment I will probably hold this back for
   a little longer.

  - I have not yet found a way to make the window scrollable, hence when
   there are a lot of file types the dialog might get very big to the extent
   that it might not even fit on the screen anymore.

  - I have not yet found a way to sort the list of file extensions. They are
   currently displayed in the order they were found. Will work on this.

  - The auto-commit settings, i.e. the log comment and additional options
   are currently not remembered. Once I have figured out how to use
   cvsgui.Persistent for "real" persistency (i.e. not only for one session)
   I might add this.

  - The dialog is not truly modal until you run the underlying CVS command.
   It is sometimes possible to work in WinCvs without closing the dialog.
   Although I haven't experienced any serious problems with this, you should
   definitely be careful about this.

  - If for some reason the macro crashes and is restarted via ReloadMacros,
   additional windows might pop up. I haven't found a way to prevent this yet.
   AFAICT they don't do any harm either, though.

  Please report any problems you encounter or suggestions you
    might have to ogware@gmx.net .
  
"""

#default extension lists:
BinExtensions = [ '.doc', '.exe', '.dll', '.res', '.dcr', '.obj', \
                  '.bpl', '.ocx', '.zip', '.rar', '.gz', '.tar', \
                  '.xls', '.lzh', '.ico', '.bmp', '.jpg', '.gif', \
                  '.jpeg', '.com', '.pyc', '.tlb', '.hlp', '.pdf', \
                  '.cab', '.msi', '.mst', '.png', '.dcp', '.class', \
                  '.tif', '.tiff', '.mdb', '.mde']
UniExtensions = [] #don't know any unicode file format
NoKwExtensions = [ '.dfm', '.cfg', '.dof', '.dti']
IgnExtensions = [ '.bup', '.bak', '.~pas', '.~dpr', '.~dfm', '.~dpk']

KModes = [ 'i', '-kb', '-ku', '-kkv', '-ko']

KMODE = 0
FILES = 1

icon_xpm = \
  "13 12 5 1\n" + \
  ". c None\n" + \
  "# c #808080\n" + \
  "@ c #000000\n" + \
  "+ c #ff0000\n" + \
  "* c #ffee80\n" + \
  "...#####.....\n" + \
  "..#*****#....\n" + \
  ".#####**####.\n" + \
  "#*****#*****@\n" + \
  "#*****####**@\n" + \
  "#***++****@*@\n" + \
  "#***++****@*@\n" + \
  "#*++++++**@*@\n" + \
  "#*++++++**@@@\n" + \
  "#***++****@..\n" + \
  "#***++****@..\n" + \
  "@@@####@@@@.."
  #0123456789012345

class AutoCommitOptions:
  pass

class RecursiveAddDlg( Frame):
  def __init__( self, master, items, doItProc):
    Frame.__init__( self, master)
    self.master.title( 'Recursive Add')
    
    self.autoCommit = BooleanVar()
    self.commitOpts = StringVar()

    self.doItProc = doItProc
    
    self.createWidgets( items)
    self.pack( fill=BOTH, expand=1)

    #attempt to center the window on screen:
    self.update()
    w = self.winfo_width()
    h = min( self.winfo_height(), ( max( 4, len( items)) * 30 + 40))
    x = ( self.winfo_screenwidth() - w) / 2
    y = ( self.winfo_screenheight() - h) / 2
    self.master.geometry( newGeometry = '%dx%d+%d+%d' % (w,h,x,y)) # '+%d+%d' % (x,y))


  def createWidgets( self, items):
    filesFrm = Frame( self, relief=GROOVE, bd=2)
    filesFrm.grid( row=0, column=0, rowspan=2, sticky=N+E+S, padx=2, ipadx=2, pady=2)
    
    cidx = 0
    for label in [ 'Extension', 'ignore', 'binary', 'unicode', 'text', 'no kw']:
      Label( filesFrm, text=label).grid( row=0, column=cidx)
      cidx+=1
    
    ridx=1
    for ext, itm in items.items():
      Label( filesFrm, text='%s [%d files]'%(ext,len(itm[FILES])), height=1)\
           .grid( row=ridx, column=0, sticky=W+N)
      cidx = 1
      for kmode in KModes:
        Radiobutton( filesFrm, height=0, \
                     variable=itm[KMODE], value=kmode)\
                   .grid( row=ridx, column=cidx, sticky=N)
        cidx+=1
      ridx+=1

    commitFrm = Frame( self, relief=GROOVE, bd=2)
    commitFrm.grid( row=0, column=1, columnspan=2, sticky= N+E+W+S, padx=2, ipadx=2, pady=2)

    Checkbutton( commitFrm, text='Auto-commit', variable=self.autoCommit)\
    .grid( row=0, columnspan=2, sticky=W)
    Label( commitFrm, text='commit message').grid( row=1, column=0, columnspan=2, sticky=W)
    self.commitMsgTxt = Text( commitFrm, width=20, height=10)
    self.commitMsgTxt.grid( row=2, columnspan=2, sticky=N+E+W+S, padx=2)
    Label( commitFrm, text='additional commit options').grid( row=3, column=0, sticky=W)
    Entry( commitFrm, textvariable=self.commitOpts).grid( row=3, column=1, padx=2, sticky=W+E)
    commitFrm.rowconfigure( 2, weight=2)
    commitFrm.columnconfigure( 0, weight=3)
    commitFrm.columnconfigure( 1, weight=2)

    Button( self, text='Add!', command=self.DoIt)\
          .grid( column=1, row=1, padx=5, pady=5, sticky=W+E+N)
    Button( self, text='Cancel', command=self.master.destroy)\
          .grid( row=1, column=2, padx=5, pady=5, sticky=W+E+N)

    self.columnconfigure( 1, weight=2)
    self.rowconfigure( 0, weight=1)

  def DoIt( self):
    self['cursor']='watch'
    try:
      autoCommitOptions = AutoCommitOptions()
      autoCommitOptions.autoCommit = self.autoCommit.get()
      if autoCommitOptions.autoCommit:
       autoCommitOptions.commitOptions = self.commitOpts.get()
       autoCommitOptions.commitMessage = self.commitMsgTxt.get( 1.0, END).strip()
      self.doItProc( autoCommitOptions)
    finally:
      self['cursor']='arrow'
      self.master.destroy()


class CvsRecursiveAddTk( Macro):
  def __init__( self):
    Macro.__init__( self, 'Recursive Add (auto-commit)...', MACRO_SELECTION, 0, 'CVS', icon_xpm)

  def OnCmdUI( self, cmdui):
    self.sel = App.GetSelection()
    enabled= len( self.sel) > 0
    cmdui.Enable( enabled)

  def collectItems( self, entries, inclIgnored, tkMaster):
    """ prepare lists of files and folders to be added

      will recursively scan the given entries list for unknown files and folders
      and associate each distinct extension with a kmode and a list of files in
      the self.items dictionary like this:
      
      { ext1:{ KMODE:kmode1,
               FILES:[file1,file2,...]},
        ext2:{ KMODE:kmode2,
               FILES:[file3,file4,...]},
        ...}

      If inclIgnored is false, no keys will be created for ignored files.

      The kmodes are stored in StringVar instances so they could easily be
      reused by the Tk GUI. For this reason the Tk master has to be given.
    """
    for entry in entries:
      if entry.IsUnknown():
        if entry.IsFile():
          #extract extension:
          root, ext = os.path.splitext( entry.GetName())
          ext = string.lower( ext)

          #determine default keyword mode:
          if entry.IsIgnored():
            if inclIgnored:
              kmode = KModes[0]
            else: #skip ignored files
              continue
          elif ext in BinExtensions:
            kmode = KModes[1]
          elif ext in UniExtensions:
            kmode = KModes[2]
          elif ext in NoKwExtensions:
            kmode = KModes[4]
          else:
            kmode = KModes[3]

          #create dictionary entry for extension if necessary:
          if ext not in self.items:
            self.items[ext] = {}

          #apply keyword mode
          if KMODE not in self.items[ext]:
            self.items[ext][KMODE] = StringVar( tkMaster)
          self.items[ext][KMODE].set( kmode)

          #initialize file list if necessary:          
          if FILES not in self.items[ext]:
            self.items[ext][FILES] = []

          #append file list:
          self.items[ext][FILES].append( entry.GetFullName())

      #recurse into folder:
      if not entry.IsFile():
        print '\t'+entry.GetFullName()
        self.collectItems( GetCvsEntriesList( entry.GetFullName()), inclIgnored, tkMaster)
    
  def addFolder( self, folder, cvs):
    folderCount = 0
    if os.path.exists( folder):
      parent, dir = os.path.split( folder)
      if not os.path.exists( os.path.join( parent, 'CVS')):
        folderCount += self.addFolder( parent, cvs)
      os.chdir( parent)
      #print 'cvs add %s (in %s)' % ( dir, os.getcwd())
      code, out, err = cvs.Run( 'add', dir)
      if code == 0:
        print '\t'+folder
        folderCount+=1
      else:
        print '[ERROR]', err
      return folderCount
    else:
      raise Exception, 'Invalid sandbox!'


  def commitFiles( self, files, autoCommitOptions, cvs):
    cvsArgs = []
    if autoCommitOptions.commitOptions != '':
      cvsArgs += autoCommitOptions.commitOptions.split()
    if autoCommitOptions.commitMessage != '':
      cvsArgs.append( '-m'+autoCommitOptions.commitMessage)
    cvsArgs += files
    #print 'cvs commit %s (in %s)' % ( string.join( cvsArgs), os.getcwd())
    code, out, err = cvs.Run( 'commit', *cvsArgs)
    if code == 0:
      return len( files)
    else:
      print '[ERROR]', err
      return 0

  def DoIt( self, autoCommitOptions):
    folderCount = 0
    fileCount = 0
    commitCount = 0
    try:
      cvs = Cvs( 1, 0)
      #create batches grouped by parent folder and kmode
      batches = {}
      print 'reorganizing file list...'
      for ext, item in self.items.items():
        kmode = item[KMODE].get()
        if not kmode == 'i':
          for file in item[FILES]:
            parent, filename = os.path.split( file)
            if parent not in batches:
              batches[parent] = {}
            if kmode not in batches[parent]:
              batches[parent][kmode] = []
            batches[parent][kmode].append( filename)

      #run batches:
      print 'adding files and folders...'
      for dir, batch in batches.items():
        #ensure folder is added already:
        if not os.path.exists( os.path.join( dir, 'CVS')):
          folderCount += self.addFolder( dir, cvs)

        os.chdir( dir)
        
        for kmode, files in batch.items():
          cvsArgs = files
          if kmode != KModes[3]:
            cvsArgs = [kmode] + files
          #print 'cvs add %s (in %s)' % ( string.join( cvsArgs), os.getcwd())
          code, out, err = cvs.Run( 'add', *cvsArgs)
          if code == 0:
            fileCount+=len( files)
            if autoCommitOptions.autoCommit:
              commitCount += self.commitFiles( files, autoCommitOptions, cvs)
          else:
            print '[ERROR]', err
    finally:
      print 'Added %d folders and %d files' % ( folderCount, fileCount)
      if autoCommitOptions.autoCommit:
        print 'Committed %d files' % commitCount

  def Run( self):
    try:
      incIgn = App.PromptMessage( 'Offer ignored files for inclusion?', \
                                  title='Question', default='Yes', cncl='No')
      
      self.items = {}
      self.folders = []
      try:
        tk = SafeTkRoot( self)
        print 'analyzing selection...'
        self.collectItems( self.sel, incIgn, tk)
        dlg = RecursiveAddDlg( tk, self.items, self.DoIt)
        tk.wait_window( dlg)
      finally:
        del self.items
        del self.folders
    except:
      if tk:
        tk.destroy()
      raise

if checkTk():
  CvsRecursiveAddTk()
