#!/bin/sh
#\
exec tclsh $0 "$@"
########################################################################
#
# ship -- collect the necessary bits of a piece of software to ship
#
# The following input is used:
# o CVS is asked about the files to ship
# o -X specifies files to delete from those found in CVS
# o -I specifies additional files to copy into the dest. dir
#
# $Revision: 1.1 $, $Date: 2004/08/04 15:09:23 $
#
########################################################################

package require clig
namespace import ::clig::*

########################################################################
## Declare the command line and parse it

setSpec ::main

## begin clig (this is used to generate the manual page
Name ship

Usage {export a CVS-module into a .tar.gz plus some additional fixups}

String -d dstdir {directory where to leave resulting .tar.gz} -d .

String -e edfiles {
files to edit version into *after* checking out of cvs. The files must
contain in their first 10 lines the word VERSION and a pattern like
2.3.4. The version number is replaced by the shipping version. If a
file also contains VERDATE, the shipping date is edited into it.
} -c 1 oo

Flag -f force {
  force shipment, even if cvs complains about non checked in files
}

Flag -F Force {
  like -f, but files which are not yet checked in are copied from the
  working directory, if they are flagged by CVS with anything but `?'.
}

String -I includes {
  file(s) to be included into into the exported directory in addition to 
  those checked out of CVS
} -c 1 oo

String -q quiet {quiet operation, don't report progress}

String -ver verfile {
  name of file where the current VERSION is found
} -d .version

String -X excludes {files to exclude from the export} -c 1 oo

Description {
.B ship
can be used to export a cvs-module into a .tar.gz. In addition to just
calling "cvs export", a number of tasks is performed. In
particular the name of the resulting .tar.gz is derived automatically
from the name of the current directory, which
.B must
be a checked out working copy of a CVS-module. 

In addition to the module name, the name of the .tar.gz will contain the 
current version number. It is extracted from the
file given with option
.BR -ver .
in order to name the resulting .tar.gz. The version can also be
edited into additional files 
.B after
they are exported from CVS, which has the advantage, that these files don't
have to be checked in just because the version number was changed. 

The file given with
.B -ver
must contain in its first 10 lines the string 
.B VERSION 
and behind that on the same line a version number of the form 
.IR x.y.z ,
where
.IR x ", " y " and " z
are small non-negative numbers.

Normally 
.B ship 
refuses to export anything as long as CVS complains about unknown files
or files not yet checked in. Option
.B -f
forces an export. If instead
.B -F
is used, files marked by 
.I "cvs -n update"
with "?"
are copied into the exported .tar.gz from the current working directory.

The options
.BR -I " and " -X
can be used to tweak the list of exported files. 
}


## end clig (this is used to generate the manual page

set Program [file tail $argv0]
if {[catch {parseCmdline ::main $Program $argc $argv} err]} {
  puts stderr $err
  exit 1
}

if {![info exist edfiles]} {set edfiles {}}
if {![info exist includes]} {set includes {}}
if {![info exist quiet]} {set quiet 0}
if {![info exist excludes]} {set excludes {}}


########################################################################
#
# readVersion -- get the version number to ship
#
# The version is stored in versionFile. It is supposed to contain a
# line with the word VERSION and a version number of the form x.y.z
# (e.g. 2.3.1)
#
# It is an error for the file not to exist or not to contain a
# correctly formed version number.
#
proc readVersion {versionFile} {
  global Program
  set pattern {\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)}

  set a [exec sed $versionFile -n -e "1,10s/.*VERSION.*$pattern.*/\\1/p"]

  ## check return value of ugly sed-curse
  if {[llength $a]==0} {
    puts stderr "$Program: cannot find correct VERSION in $versionFile"
    exit 1
  }
  if {[llength $a]>1} {
    puts stderr \
	"$Program: found more than 1 VERSION in $versionFile, namely"
    puts stderr \
	"    `$a'"
    exit 1
  }

  return $a
}
########################################################################
#
# usage
#
proc usage {} {
  global Program

  puts stderr "usage:\
$Program \[-d dir\] \[-f\] \[-q\] \[-X files\] \[-I files\]
    \[-ver verfile\] \[-e files\]
    -d -- directory where to leave resulting .tar.gz (default .)
    -e -- files to edit version into *after* checking out of cvs. The
          files must contain in their first 10 lines the word VERSION and
          a pattern like 2.3.4. The version number is replaced by the
          shipping version. If the file also contain VERDATE, the
          shipping date is edited into it.
    -f -- force shipment, even if cvs complains
    -F -- like -f, but export files which are not yet checked in from
          the current directory, if they are flagged by CVS with
          anything but `?'.
    -I -- files to include in shipment besides those extracted from cvs
    -m -- module name to use for `cvs export' (usually derived autom.)
    -q -- quiet operation
    -X -- file to exclude from shipment

  -ver -- file where VERSION is found (default .version)

  If the environment variable SHIP is set, it is prepended to the
  command line.
"
  exit 1
}
########################################################################
#
# oneParam -- extract one parameter of a command line switch from the
# command line with error checking.
#
proc oneParam {opt _i _argc _argv} {
  global Program Options
  upvar $_i i  $_argc argc $_argv argv

  incr i
  if {$i>=$argc} {
    puts stderr "$Program: missing parameter after option $opt"
    exit 1
  }
  return [lindex $argv $i]
}
########################################################################
########################################################################
## MAIN

set Program [file tail $argv0]
set Options(d) .	;# destination for the resulting .tar.gz
set Options(f) 0	;# force, even if cvs complains
set Options(F) 0	;# force, even if cvs complains and add file
			 # not marked ?
set Ffiles {}		;# list of those files
set Options(n) 0	;# only print package-x.y.z
set Options(q) 0	;# quiet
set Options(I) {}	;# list of additional files
set Options(X) {}	;# list of files to kill
set Options(ver) .version	;# file with version info
set Options(e) ""	;# file to edit VERSION and VERDATE into just
			 # before the .tar.gz is created

if [info exist env(SHIP)] {
  set argv [concat $env(SHIP) $argv]
  set argc [llength $argv]
}
########################################
# parse command line

set rest {}
set currentList rest

for {set i 0} {$i<$argc} {incr i} {
  set opt [lindex $argv $i]
  switch -glob -- $opt {
    -d {
      set Options(d) [glob [oneParam $opt i argc argv]]
    }
    -e {
      set currentList Options(e)
      continue
    }
    -f {
      set Options(f) 1
    }
    -F {
      set Options(F) 1
    }
    -I {
      set currentList Options(I)
      continue
    }
    -n {
      set Options(n) 1
    }
    -q {
      set Options(q) 1
    }
    -ver {
      set Options(ver) [oneParam $opt i argc argv]
    }
    -X {
      set currentList Options(X)
      continue
    }
    -* {
      puts "$Program: unknown option `$opt'\n"
      usage 
    }
    -- {
      set currentList rest
    }
    * {
      lappend $currentList $opt
      continue
    }
  }
  set currentList rest
}

if [llength $rest] {
  puts stderr "$Program: superfluous arguments `$rest'"
  exit 1
}

## make sure that all the files in includes exist
foreach x $includes {
  if ![file exist $x] {
    puts stderr "$Program: cannot include `$x', does not exist"
    exit 1
  }
}


## extract the last shipped version from .version
set version [readVersion $verfile]

## guess the name of this package
if {![info exists Options(m)]} {
  if [catch "open CVS/Repository r" in] {
    puts "$Program: $msg"
    exit 1
  }
  set package [file tail [lindex [read $in] 0]]
  close $in
  set fullName $package-$version
} else {
  set package $Options(m)
  set fullName [file tail $package]-$version
}

set fullDir $dstdir/$fullName

## This might be all there is to do
if $Options(n) {
  puts $fullName
  exit 0
}

## abort, if cvs update complains and neither -f nor -F were given
set msg [exec cvs -qn update]

if {""!="$msg"} {
  if { ![info exist force] && ![info exist Force]} { 
    puts $msg
    puts -nonewline stderr \
	"$Program: cannot proceed because `cvs update' complains. "
    puts stderr "Use -f to override."
    exit 1
  }
  if {[info exist Force]} {
    foreach line [split $msg "\n"] {
      if {"[lindex $line 0]"!="?"} {
	lappend Ffiles [lindex $line 1]
      }
    }
  }
}

## make sure we can create a clean `cvs export'-copy for $fullname
if [file exist $fullDir] {
  puts "$Program: $fullDir exists already, please delete it first."
  exit 1
}

if {!$quiet} {
  puts "shipping $fullName"
}


## export the module with cvs and add assorted information
if [catch "exec cvs -Q export -D today -d $fullDir $package" msg] {
  puts "$Program: $msg"
  exit 1
}
if {[llength $Ffiles]} {
  if {!$quiet} {
    puts "adding non-checked in files: $Ffiles"
  }
  eval exec tar cf $fullDir/tmp.tar $Ffiles
  exec tar xfC $fullDir/tmp.tar $fullDir
  exec rm $fullDir/tmp.tar
}
exec digestLog >$fullDir/CHANGES
exec cvs -q status | grep "Repository r" >$fullDir/.CVS-Versions


## Delete files which shall not be exported
if {!$quiet && [llength $excludes]} {
  puts "deleting `$excludes' from export-directory"
}
foreach file $excludes {
  exec rm -f $fullDir/$file
}

## Add files which are not part of CVS. We use tar here to preserve
## the directory structure, if any.
## The temporary file is used instead of a pipe, because tar seems to
## return error codes (probable missing return in main).
##
if {[llength $includes]} {
  if {!$quiet } {
    puts "adding `$includes'"
  }
  eval exec tar cf $fullDir/tmp.tar $includes
  exec tar xfC $fullDir/tmp.tar $fullDir
  exec rm $fullDir/tmp.tar
}

## edit VERSION and VERDATE into the files given with option -e
foreach efile $edfiles {
  set realE $fullDir/$efile
  if !$quiet {
    puts "editing VERSION and VERDATE into `$realE'"
  }
  exec cp $realE $realE.orig
  
  set verpat {[0-9]+\.[0-9]+\.[0-9]+}
  set datepat {[0-9]+-[0-9]+-[0-9]+}
  set in [open $realE.orig r]
  set out [open $realE w]
  set lineNo 0
  set hadVersion 0
  set hadDate 0
  while {-1!=[gets $in line]} {
    incr lineNo
    if {$lineNo>10} {
      puts $out $line 
      continue
    }
    if {[regexp "VERSION.*$verpat" $line]} {
      regsub $verpat $line $version line
      puts $out $line
      set hadVersion 1
      continue
    }
    if [regexp "VERDATE.*$datepat" $line] {
      set verdate [clock format [clock seconds] -format %Y-%m-%d]
      regsub $datepat $line $verdate line
      puts $out $line
      set hadDate 1
      continue
    }
    puts $out $line
  }
  close $in
  close $out
  exec rm -f $realE.orig
  if {!$hadVersion} {
    puts "$Program\(warning): no VERSION found in $efile"
  } 
  if {!$hadDate} {
    puts "$Program\(warning): no VERDATE found in $efile"
  } 
}


## pack everthing into a tar-file
if !$quiet {
  puts "packing $dstdir/$fullName.tar.gz"
}
exec tar cvzfC $dstdir/$fullName.tar.gz $dstdir $fullName
exec rm -rf $fullDir

## Local Variables: ##
## mode: tcl ##
## End: ##
