#!/bin/bash

###########################################################################
#
#	Shell program to release a SourceForge.net project module.
#
#	Copyright 2001-2002, William Shotts <bshotts@users.sourceforge.net>.
#
#	This program is free software; you can redistribute it and/or
#	modify it under the terms of the GNU General Public License as
#	published by the Free Software Foundation; either version 2 of the
#	License, or (at your option) any later version. 
#
#	This program is distributed in the hope that it will be useful, but
#	WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#	General Public License for more details.
#
#	SourceForge.net is a trademark of VA Software, Inc.
#
#	This software is part of the LinuxCommand.org project, a site for
#	Linux education and advocacy devoted to helping users of legacy
#	operating systems migrate into the future.
#
#	You may contact the LinuxCommand.org project at:
#
#		http://www.linuxcommand.org
#
#	Description:
#
#	This is a program to release a module on SourceForge.net.  It
#	performs the following functions:
#
#	1. If needed, it creates a "description file" which contains a
#	   short prose description of the module.  This description is
#	   used in the release announcements. (see below)
#
#	2. If needed, it creates a CHANGELOG file.
#
#	3. It invokes an editor (determined from the EDITOR environment
#	   variable) and allows you to edit the CHANGELOG for the current
#	   release.
#
#	4. It performs a final CVS commit and tags the CVS tree with a
#	   release tag that encodes the module's version number and
#	   release date.
#
#	5. A compressed tar file is created and uploaded to SourceForge.net.
#
#	6. A release announcement is constructed and sent to your
#	   project's announce mailing list.
#
#	The program requires two command arguments.  The first is the
#	module name which must be a name of a directory in the modules
#	directory.  Please see the sf-tools.README file for a
#	description the expected layout of directories.  The second
#	argument is the module's version number.  The following version
#	number formats are supported:
#
#		0.0.0
#		0.0.00
#		0.0word0
#		0.0word00
#
#		where "0" is any numeral 0-9 inclusive and
#		"word" is any of the following: alpha, beta, pre, test
#
#	Usage:
#
#		sft_release_module -h | --help | -m module -v version
#
#	Options:
#
#		-h, --help	Display this help message and exit.
#		-m  module	module (directory) name
#		-v  version	version number
#
#
#	Revisions:
#
#	02/02/2001	File created by lc_new_script v.2.0.6
#	03/18/2001	Changed name of ftp upload site (0.0.2)
#	03/07/2002	Various cosmetic updates (0.0.3)
#
#	$Id: sft_release_module,v 1.3 2002/03/09 16:50:43 bshotts Exp $
#
###########################################################################


###########################################################################
#	Constants
###########################################################################

PROGNAME=$(basename $0)
VERSION="0.0.3"

PROJECT_NAME="your_project"	# Put your project name here!

MAIL_LIST="${PROJECT_NAME}-announce@lists.sourceforge.net"
				# Name of your announce maining list.
				# Change this to "" to supress mailing a
				# release announcement
				
MAILER=mail			# Name of program to send mail

PROJECT_ROOT=~/$PROJECT_NAME



###########################################################################
#	Functions
###########################################################################


function clean_up
{

	#####	
	#	Function to remove temporary files and other housekeeping
	#	No arguments
	#####

	rm -f ${TEMP_FILE1}
}


function error_exit 
{
	#####	
	# 	Function for exit due to fatal program error
	# 	Accepts 1 argument
	#		string containing descriptive error message
	#####

	
	echo "${PROGNAME}: ${1:-"Unknown Error"}" >&2
	clean_up
	exit 1
}


function make_temp_files
{

	#####
	#	Function to create temporary files
	#	No arguments
	#####
	
	# Use user's local tmp directory if it exists
	
	if [ -d ~/tmp ]; then
		TEMP_DIR=~/tmp
	else
		TEMP_DIR=/tmp
	fi

	# Temp file for this script, using paranoid method of creation to
	# insure that file name is not predictable.  This is for security to
	# avoid "tmp race" attacks.  If more files are needed, create using
	# the same form.
	
	TEMP_FILE1=$(mktemp -q "${TEMP_DIR}/${PROGNAME}.$$.XXXXXX")
	if [ "$TEMP_FILE1" = "" ]; then
		error_exit "cannot create temp file!"
	fi
}


function graceful_exit
{
	#####
	#	Function called for a graceful exit
	#	No arguments
	#####

	clean_up
	exit
}


function term_exit
{
	#####
	#	Function to perform exit if termination signal is trapped
	#	No arguments
	#####

	echo "${PROGNAME}: Terminated"
	clean_up
	exit
}


function int_exit
{
	#####
	#	Function to perform exit if interrupt signal is trapped
	#	No arguments
	#####

	echo "${PROGNAME}: Aborted by user"
	clean_up
	exit
}


function usage
{
	#####
	#	Function to display usage message (does not exit)
	#	No arguments
	#####

	echo "Usage: ${PROGNAME} -h | --help | -m module -v version"
}


function helptext
{
	#####
	#	Function to display help message for program
	#	No arguments
	#####
	
	local tab=$(echo -en "\t\t")
		
	cat <<- -EOF-

	${PROGNAME} ver. ${VERSION}	
	Program to release a SourceForge.net project module.
	
	$(usage)
	
	Options:
	
	-h, --help	Display this help message and exit.
	-m  module	module (directory) name
	-v  version	version number

-EOF-
}	


function check_version
{
	#####
	#	validates a module version number
	#	Arguments:
	#		1	version number (required)
	#####

	# Fatal error if required arguments are missing

	if [ "$1" = "" ]; then 
		error_exit "check_version: missing argument 1"
	fi

	echo $1 | awk '
	
		/^[0-9]\.[0-9]\.([0-9]|[0-9][0-9])$/ { print $0 }
		/^[0-9]\.[0-9](alpha|beta|pre|test)([0-9]|[0-9][0-9])$/ { print $0 }
	'
	
}	# end of check_version


function release_tag
{
	#####
	#	creates cvs release tag
	#	Arguments:
	#		1	version number (required)
	#####

	# Fatal error if required arguments are missing

	if [ "$1" = "" ]; then 
		error_exit "release_tag: missing argument 1"
	fi

	local release_date
	local release_version
	
	release_date=$(date +"%Y-%m-%d")
	release_version=$(echo $1 | awk '{gsub(/\./, "_"); print $0}')
	echo "release_ver_${release_version}--${release_date}"
	
}	# end of release_tag


function create_description
{
	#####
	#	Create module description file
	#	Arguments:
	#		none
	#####

	local desc_file=${module}.DESCRIPTION
	
	if [ ! -f $desc_file ]; then
		echo -e "\nThere is no description file for this module."
		echo -e "Would you like to create one now? (y/n) -> \c"
		read foo
		if [ "$foo" = "y" ]; then
			cat > ${module}.DESCRIPTION <<- _EOF_
				$(underline_string "$module")
				
				SFT: Please edit the module description in the lines above.
				SFT: All lines starting with "SFT:" will be ommited during
				SFT: processing.
_EOF_
			${EDITOR:-"vi"} +3 $desc_file
		fi
	fi
	
}	# end of create_description


function create_changelog
{
	#####
	#	Create CHANGELOG file
	#	Arguments:
	#		none
	#####

	echo "CHANGELOG not found. Creating..."
	echo -e "$(underline_string "CHANGELOG for $module")\n" > CHANGELOG
	echo -e "\n\n\$Id\$" >> CHANGELOG
	cvs add CHANGELOG
	
}	# end of create_changelog


function edit_changelog
{
	#####
	#	Edit CHANGELOG file
	#	Arguments:
	#		none
	#####

	head --lines=3 CHANGELOG > $TEMP_FILE1
	echo -e "$(underline_string "Version $version -- $(date +"%a, %b %d, %Y %r")" "-")\n\n" >> $TEMP_FILE1
	tail --lines=+4 CHANGELOG >> $TEMP_FILE1
	${EDITOR:-"vi"} +6 $TEMP_FILE1
	cp $TEMP_FILE1 CHANGELOG
	
}	# end of edit_changelog


function underline_string
{
	#####
	#	Underline a string with a specified character
	#	Arguments:
	#		1	string to underline (required)
	#		2	underline character (optional)
	#####

	# Fatal error if required arguments are missing

	if [ "$1" = "" ]; then 
		error_exit "underline_string: missing argument 1"
	fi

	local ulc=${2:-"="}
	
	echo "$1" | awk --assign ulc=$ulc '
	
		{	print $0
			for (i = 0; i < length($0); i++)
			{	ul = ul ulc
			}
			print ul
		}
	'
}	# end of underline_string


function update_cvs
{
	#####
	#	Perform last CVS commit and tag
	#	Arguments:
	#		none
	#####

	echo "Performing CVS commit and tag..."
	cvs commit -m "final commit before release"
	cvs tag "$(release_tag $version)"
	
}	# end of update_cvs


function mail_release_announcement
{
	#####
	#	Construct release announcement and mail it
	#	Arguments:
	#		none
	#####

	local desc_file=../$module.DESCRIPTION
	
	if [ ! -n "$MAIL_LIST" ]; then
		return
	fi
	
	echo "Mailing release announcement..."

	$MAILER -s "$module $version released" $MAIL_LIST  <<- _EOF_
	$module version $version has been released.
	
	$([ -f "$desc_file" ] && grep -v "SFT:" $desc_file)
	
	$(cat CHANGELOG)
_EOF_

}	# end of mail_release_announcment


function upload_module
{
	#####
	#	Package module and upload to SourceForge.net
	#	Arguments:
	#		none
	#####

	# Create compressed archive

	echo "Creating compressed archive..."
	mv $module ${module}-${version}
	tar czvf ${module}-${version}.tar.gz --exclude CVS ${module}-${version}
	mv ${module}-${version} $module

	# Transfer archive

	echo "Transferring archive to SourceForge.net..."
	ncftpput -V upload.sourceforge.net /incoming ${module}-${version}.tar.gz
	
}	# end of upload_module


function release_tag
{
	#####
	#	creates cvs release tag
	#	Arguments:
	#		1	version number (required)
	#####

	# Fatal error if required arguments are missing

	if [ "$1" = "" ]; then 
		error_exit "release_tag: missing argument 1"
	fi

	local release_date
	local release_version
	
	release_date=$(date +"%Y-%m-%d")
	release_version=$(echo $1 | awk '{gsub(/\./, "_"); print $0}')
	echo "release_ver_${release_version}--${release_date}"
	
}	# end of release_tag


###########################################################################
#	Program starts here
###########################################################################
	
##### Initialization And Setup #####

# Trap TERM, HUP, and INT signals and properly exit

trap term_exit TERM HUP
trap int_exit INT

# Create temporary file(s)

make_temp_files


##### Command Line Processing #####

if [ "$1" = "--help" -o "$1" = "-h" ]; then
	helptext
	graceful_exit
fi

# Some sanity checks

if [ "$PROJECT_NAME" = "your_project" -o "$PROJECT_NAME" = "" ]; then
	echo >&2 "
PROJECT_NAME has not been set.  Please edit this
script and assign your project name to the
PROJECT_NAME constant."
	error_exit "Configuration error"
fi

if [ ! -d $PROJECT_ROOT ]; then
	echo >&2 "
Project directory does not exist.
Perhaps project name (PROJECT_NAME) is misspelled."
	error_exit "Configuration error"
fi

module=
version=

while getopts ":hm:v:" opt; do
	case $opt in
		m )	module=$OPTARG
			;;
		v )	version=$OPTARG
			;;
		h )	helptext
			graceful_exit
			;;
		* )	usage
			clean_up
			exit 1
	esac
done


##### Main Logic #####

# check if module exists

cd ${PROJECT_ROOT}/modules || error_exit "cannot cd to modules directory"

if [ "$module" = "" -o "$version" = "" ]; then
	usage
	exit 1
fi

if [ ! -d $module ]; then
	error_exit "no such directory \"$module\""
fi

if [ "$(check_version $version)" = "" ]; then
	error_exit "invalid version number format.  Please see README."
fi

if [ ! -f "../${module}.DESCRIPTION" ]; then
	create_description
fi

# Create CHANGELOG if needed and edit

cd ${PROJECT_ROOT}/modules/${module} || error_exit "cannot change to ${module} directory"
if [ ! -f CHANGELOG ]; then
	create_changelog
fi
edit_changelog

# Perform final CVS commit and CVS tag

cd ${PROJECT_ROOT}/modules/${module} || error_exit "cannot change to ${module} directory"
update_cvs

# Construct compressed archive and upload to SourceForge.net

cd ${PROJECT_ROOT}/modules || error_exit "cannot change to modules directory"
upload_module

# Send release announcement to mailing list

cd ${PROJECT_ROOT}/modules/${module} || error_exit "cannot change to ${module} directory"
mail_release_announcement
graceful_exit


syntax highlighted by Code2HTML, v. 0.9.1