#!/bin/tcsh -f
#      del [-a] [-] [dirpat [filepat ...]]
#      keep1 [-a] [-] [dirpat [filepat ...]]
#      cleanup [-a] [-] [dirpat [filepat ...]]
#      lsd [ls_opts] [-] [dirpat [filepat ...]]
#      undel [undel_opts] [dirpat [filepat ...]]
#      purge [purge_opts] [dirpat [filepat ...]]
# Delete files, keep only one version of files (delete others), cleanup
# (delete) files with certain extensions, list deleted files, undelete deleted
# files, or purge (blow away with rm) deleted files.
# ls_opts is the same as the options to ls, but bogus options
# are not checked for; they are simply passed to ls.
# undel_opts is the same as the options to mv and purge_opts
# the same as the options to rm, except that -a is also allowed,
# and the options must appear singly, e.g., -i -r instead of -ir.
# Also purge_opts may include a -c option to cause a query for confirmation
# of the complete set of removals, as opposed to the query for each
# removal caused by -i.
# The function of -a is described below.  The other options are as for
# mv or rm, accordingly, except that -i is the default for undel.
# del avoids including an interactive mode by always renaming files
# (via mvnc) to avoid overwriting.
# The -- option indicates the end of the options so that other arguments
# may start with -.

# In the case of keep1 and cleanup, the filepats are modified by addition
# of all possible suffixes from the environment variable KEEP1_SUFFIXES,
# or CLEANUP_SUFFIXES, respectively.
# In any case the operation is then as follows.
# If dirpat and filepats are given, then for each directory matching
# dirpat and for each filepat, handles the deleted/undeleted files
# matching filepat.

# The arguments should be quoted if the shell is to be prevented from
# performing filename expansion prematurely.  For example,
#     keep1 *
# will cause the first file in the current directory to be used as the
# dirpat and the others to be used as the filepats.  To keep only the
# most recent version of files on all subdirectories of the current one,
# you must instead use
#     keep1 '*'

# Only for purge, is action different according to whether a file matching
# filepats is a directory or plain file.  If it is a directory,
# purge removes it iff it is empty or -r is in the options.
# If no dirpat is given, the result is as if . was given.
# If no filepats are given, then the result is as if '*' was given
# (or '* .[^.]*' if the -a option is specified,
# this being like '* .*' but not matching . or ..).

# WARNING:  Execution is slow if there are a lot of files to handle.
# 
# MORE DETAIL AND COMMENTARY ON IMPLEMENTATION

# The path used to find subcommands will normally be ~rig/bin/versctrl
# followed by the usual directories of UNIX commands.
# You can make commands be taken from somewhere else by setting the
# environment variable $DEL to whatever you want.  If you give it a null
# value, the path used will begin with the path in effect when the command
# is executed.

# The other key environment variables are KEEP1_SUFFIXES and CLEANUP_SUFFIXES,
# the basic function being described above.  You will probably want to set
# these in your .cshrc.  The suffixes may include wildcards, but you must
# make sure they are not globbed when you set the variables, e.g., use "".
# Example settings are as follows:
#     setenv KEEP1_SUFFIXES ".~*~ .asv"
#     setenv CLEANUP_SUFFIXES ".del .tmp .aux .log .blg .bbl .dvi .imp"

# Because I have chosen to gear these commands to the uniformity and
# simplicity of doing everything with suffixes, you may wish to
# include code in your .emacs file to change the way auto save file
# names are created.  You can make auto save file names end in .asv
# (or .%asv) instead of beginning with # (or #%) by including a line
# like (like because you have to do the shell substitution for "~rig"):
#     (load "~rig/emacs/lisp/auto-save-file-names" t t)

# WHEN files are deleted, they are moved to a .del subdirectory of
# the directory they were on previously.  As a side effect of executing
# any of the commands here, .del subdirectories of existing directories
# may be created even though it may not be necessary for the actual actions
# performed.  When files are deleted, they are moved to a file of the
# same name on the .del subdirectory, but if a file of that name already
# exists on the .del subdirectory, then the existing deleted file is
# first moved to a deleted file of the same name except that "~" is added
# to the name.  This renaming proceeds recursively so that existing files
# are never clobbered by deletion operations.  This renaming is not performed
# for undeletion, but the default mode of undeletion is interactive, so
# that you will be queried if something is about to be clobbered.
# 
# You may notice that it is just as easy to delete a directory as to delete
# a plain file.  In either case, it is simply moved to the .del
# subdirectory.  The place where you will have safeguards against massive
# destruction is when you purge.  Directories are only purged if they are
# empty or if the -r (recursive rm) option is used.

# Another thing you may want to include in your .emacs file is a line
# like ("like" because you have to do the shell substitution for "~rig"):
#     (load "~rig/emacs/lisp/find-backup-file-name" t t)
# which redefines the function used to create a name for a backup file.
# This redefinition will make the name be chosen as if the deleted
# files for the relevant directory were not deleted.  For example if
# you have a file named foo and a deleted file foo.~1~, and you are editing
# foo and perform a save which creates a backup, the backup will be on
# foo.~2~ rather than foo.~1~.  This allows you to maintain coherent version
# numbering even if you have tentatively deleted some versions.  There are
# still other ways you could create version numbering which will not look
# very sensible when you look at both deleted and undeleted files, which
# gives rise to the following warning.

# WARNING:  If you have been doing multiple deletions of files with the
# same name, you had better use -t in the ls_opts of lsd.

# In conjunction with the pretty commands here, you may wish to place
# safeguards on the use of commands which can still blow things away.
# For example your .cshrc might include:
#     set noclobber
#     alias rm 'rm -i'
#     alias cp 'cp -i'
#     alias mv 'mv -i'
#     alias purge 'purge -c'


set debug = 0  # Use to turn on/off echoing of debugging information

# 
# PATH SETUP
if ($?DEL) then
  if ($DEL == "") then
    set path = ($path /bin /usr/bin /usr/ucb)
  else
    set path = ($DEL /bin /usr/bin /usr/ucb)
  endif
else
  set path = (~rig/bin/versctrl /bin /usr/bin /usr/ucb)
endif
if ($debug) echo del: path=\"$path\"


set noglob  # OPTION AND ARGUMENT HANDLING MUST BE DONE IN NOGLOB MODE

# OPTION HANDLING
switch ($0)
case *lsd:
  set valid_opts = -?*
  set match_on = del
  breaksw
case *undel:
  set valid_opts = -[afi]
  set match_on = del
  breaksw
case *del:  # doesn't catch undel, since got undel above.
case *keep1:
case *cleanup:
  set valid_opts = -[a]
  set match_on = undel
  breaksw
case *purge:
  set valid_opts = -[afirc]
  set match_on = del
  breaksw
endsw
set options
set aopt
set copt
foreach i ($*)
  if ("$i" !~ $valid_opts) break
  shift
  if ("$i" == "-a") then
    set aopt = -a
  else if ("$i" == "-c") then
    set copt = -c
  else
    set options="$options $i"
  endif
end
if ("$1" == "-") shift
if ($debug) echo options=\"$options\"  aopt=\"$aopt\"  match_on=\"$match_on\"
# 
# ARGUMENT HANDLING
if ($#argv == 0) then
  set dirpat = .
else
  set dirpat = "$1"; shift
endif
if ($#argv == 0) then
  set filepats = *
  if ("$aopt" == "-a") set filepats = (* .[^.]*)
else
  set filepats = ($*)
endif
if ($debug) echo dirpat=\"$dirpat\"  filepats=\"$filepats\"

# ARGUMENT MODIFICATION
switch ($0)
case *cleanup:
  set suffixes = ($CLEANUP_SUFFIXES)
  breaksw
case *keep1:
  set suffixes = ($KEEP1_SUFFIXES)
  breaksw
endsw
switch($0)
case *cleanup:
case *keep1:
  set origpats = ($filepats)
  set filepats
  foreach i ($origpats)
    foreach j ($suffixes)
      set filepats = ($filepats $i$j)
    end
  end
  breaksw
endsw
if ($debug) echo dirpat=\"$dirpat\"  filepats=\"$filepats\"
# 
# GENERATION OF FILE AND DELETED FILE NAMES
set origdir = "`pwd`"
set dir_modifier
if ($match_on == del) set dir_modifier = /.del
set delfiles
set files
unset noglob
foreach dir (/dev/null* $dirpat)  # /dev/null* avoids no match; is not a dir
  set noglob
  if (-d $dir) then
    set found_a_dir
    if ($debug) echo processing dir $dir with dir_modifier $dir_modifier
    if (! -d $dir/.del) then
      if (! -e $dir/.del) then
        mkdir $dir/.del
      else
        echo "${0}: Directory $dir contains plain file .del"
        continue
      endif
    endif
    cd $dir${dir_modifier}
    unset noglob
    foreach file (/dev/null* $filepats)  # /dev/null* avoids no match
      set noglob
      if ("$file" !~ /dev/null* && "$file" !~ ".del") then
        set delfiles = ($delfiles $dir/.del/$file)
        set files = ($files $dir/$file)
      endif
    end
    if ($debug) echo processed dir $dir , yielding delfiles $delfiles and files $files
    cd "$origdir"
  endif
end
if ($debug) echo files=\"$files\"
if ($debug) echo delfiles=\"$delfiles\"
if (! $?found_a_dir) then
  echo "${0}: No existing directories among ${dirpat}."
  echo "${0}: Did you use dirpat then filepats & quote arguments correctly?"
  exit 2
endif
if ("$files[1]" == "") then
  if ($#argv != 0) then
    echo "${0}: No match."
    echo "${0}: Did you use dirpat then filepats & quote arguments correctly?"
    exit 1
  endif
  exit 0
endif
# 
# COMMAND EXECUTION
switch ($0)
case *lsd:
  ls $options $delfiles
  breaksw
case *undel:
  set count = 1
  while ($count <= $#files)
    mv -i $options -- $delfiles[$count] $files[$count] && echo undeleted $files[$count]
    @ count++
  end
  breaksw
case *del:  # doesn't catch undel, since got undel above.
case *keep1:
case *cleanup:
  set count = 1
  while ($count <= $#files)
    mvnc $files[$count] $delfiles[$count] && echo deleted $files[$count]
    @ count++
  end
  breaksw
case *purge:
  if ("$copt" == "-c") then
    echo "purge: Remove the following files?"
    echo $delfiles
    if ($< !~ y*) exit 0
  endif
  foreach i ($delfiles)
    if (-d $i && "$options" !~ *r*) then
      (rmdir $i || rm $options -- $i) && echo purged $i #rm catches links to dirs
    else
      (rm $options -- $i) && echo purged $i
    endif
  end
  breaksw
endsw
