#!/bin/sh -eu

#  Copyright (C) 2009 ALT Linux Team.
#
#  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 3 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.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#  Contact information:
#
#  SMTP: devel [at] lists [dot] altlinux [dot] org
#

# Verborum Caterva -- simple engine for arbitrary file
# structure generation.

PROG="${0##*/}"

. shell-getopt
. shell-args
. shell-error
. shell-quote

# Prints short usage information.
show_help()
{
  cat <<EOF
$PROG - simple engine for arbitrary file structure generation

Usage: $PROG [options] -i TEMPLATE_DIRECTORY -o OUTPUT_DIRECTORY

Options:
  -i, --in-dir=DIR          path to the directory DIR with templates;
  -o, --out-dir=DIR         path to the output directory DIR;
      --backup[=CONTROL]    make a backup of each existing generated file;
  -b                        like --backup but does not accept an argument;
  -S, --suffix=SUFFIX       override the usual backup suffix;
  -r, --recursive           process templates recursively;
  -v, --verbose             print a message for each action;
  -h, --help                show this text and exit.

Report bugs to http://bugzilla.altlinux.org/

EOF
  exit
}

TEMP=`getopt -n $PROG -o "i:,o:,b,S:,r,v,h" -l "in-dir:,out-dir:,backup::,suffix:,recursive,verbose,help" -- "$@"` ||
	show_usage
eval set -- "$TEMP"

# Default values:
INDIR=
OUTDIR=
RECURSIVE=
BACKUP=
SUFFIX=
verbose=
while :; do
	case "$1" in
	    -i|--in-dir)
		INDIR="$(opt_check_dir "$1" "$2")"
		shift
		;;
	    -o|--out-dir)
		OUTDIR="$(opt_check_dir "$1" "$2")"
		shift
		;;
	    -b)
		BACKUP='-b'
		;;
	    --backup)
		BACKUP="--backup${2:+="$(quote_shell "$2")"}"
		shift
		;;
	    -S|--suffix)
		SUFFIX="$(quote_shell "$1=$2")"
		shift
		;;
	    -b|--backup-dir)
		BACKUPDIR="$2/`date +'%Y-%m-%d-%H:%M:%S'`"
		shift
		;;
	    -r|--recursive)
		RECURSIVE=1
		;;
	    -d|--delete-dirs)
		DELDIRS=1
		;;
	    -v|--verbose)
		verbose=-v
		;;
	    -h|--help)
		show_help
		;;
	    --)
		shift
		break
		;;
	esac
	shift
done

[ -n "$INDIR" -a -n "$OUTDIR" ] ||
    show_usage

# Tests if a directory is a file template.
# args: filename
is_file_template()
{
    local fn

    for fn in "$1"/*; do
	[ -f "$fn" ] ||
	    return 1
    done
}

# Generates a file from a template.
# args: indir outfile
generate_file()
{
    local fn p n v ret
    local template="$1" && shift
    local file="$1" && shift
    local script="$(mktemp -t verborum-caterva.XXXXXXXXXX)"
    local tmpfile="$(mktemp "$file.XXXXXXXXXX")"

    verbose "Generating $file from template $template"

    cat <<EOF >"$script"
CATERVA_VERBOSE=${verbose:+1}
CATERVA_TEMPLATE="$(quote_shell "$template")"
CATERVA_VALTABLE="$CATERVA_VALTABLE"
EOF

    for p; do
	n="${p%%=*}"
	v="${p#*=}"
	if [ -z "$n" -o -z "${n##*[^A-Za-z0-9_]*}" ]; then
	    message "Ignoring invalid assingnment \`$p'"
	    continue
	fi
	printf 'ARG_%s="%s"\n' "$n" "$(quote_shell "$v")" >>"$script"
    done

    for fn in "$template"/*; do
	fn="${fn#$template/}"
	[ -f "$template/$fn" ] ||
	    continue

	if [ -x "$template/$fn" ]; then
	    verbose "Executing $template/$fn with output to $file"
	    printf '. "%s"\n' "$(quote_shell "$template/$fn")" >> "$script"
	else
	    verbose "Copying contents of $template/$fn to $file"
	    printf 'cat "%s"\n' "$(quote_shell "$template/$fn")" >> "$script"
	fi
    done

    verbose "Executing template script"
    sh ${verbose:+-x} -- "$script" > "$tmpfile"
    ret=$?

    if [ $ret -eq 0 ]; then
	if [ -f "$file" ]; then
	    chmod --reference="$file" -- "$tmpfile" ||:
	    chown --reference="$file" -- "$tmpfile" ||:
	fi
	mv -f $BACKUP $SUFFIX -- "$tmpfile" "$file"
	rm -f -- "$tmpfile" "$script"
    else
	echo "Error in the template script '$tmpfile'. Not deleted."
    fi

    return $ret
}

# Processes a template directory.
# args: indir outdir [recursive]
process_templates()
{
    local fn
    local indir="$1" && shift
    local outdir="$1" && shift
    local rec="${1}" && shift

    verbose "Processing $indir"

    for fn in "$indir"/*; do
	fn="${fn#$indir/}"
	[ -e "$indir/$fn" ] ||
	    continue

	if [ ! -d "$indir/$fn" ]; then
	    verbose "Skipping non-directory $indir/$fn"
	    continue
	fi

	if is_file_template "$indir/$fn"; then
	    if [ -f "$outdir/$fn" -o ! -e "$outdir/$fn" ]; then
		generate_file "$indir/$fn" "$outdir/$fn" "$@" ||
		    message "$outdir/$fn: generation failed"
	    else
		verbose "Target $outdir/$fn exists and is not a file, skipping"
		continue
	    fi
	else
	    if [ -n "$rec" ]; then
		verbose "Creating target directory $outdir/$fn"
		if ! mkdir -p -- "$outdir/$fn"; then
		    message "$outdir/$fn: mkdir failed"
		    continue
		fi
		process_templates "$indir/$fn" "$outdir/$fn" "$rec" "$@"
	    fi
	fi
    done
}

# Compares two backup directories.
# args: dir1 dir2
cmp_backup()
{
    diff -q "$1" "$2" &> /dev/null
}

# Finds and deletes a non-uniqe backup.
# args: backupdir
clean_backup()
{
    local backupdir="$1"
    local backupname=`basename "$backupdir"`

    print_info "Searching $backupdir/.. for identical backup"
    ls -c1 "$backupdir/.." | while read fn; do
	if [ -d "$backupdir/../$fn" ] && [ "$fn" != "$backupname" ]; then
	    if cmp_backup "$backupdir/../$fn" "$backupdir"; then
		print_info "Backup in $fn is identical to $backupname"
		print_info "Removing backup $backupdir"
		rm -r "$backupdir"
		exit 101
	    fi
	fi
    done
    [ $? -eq 101 ] || print_info "No identical backup found."
}

# Prints current program configuration
verbose "Template root directory: $INDIR"
verbose "File output directory: $OUTDIR"
[ -n "$RECURSIVE" ] &&
    verbose "Recursive mode is on" ||
    verbose "Recursive mode is off"

CATERVA_VALTABLE=`mktemp -t verborum-caterva.valtable.XXXXXXXXXX`

# Process templates in the starting directory.
process_templates "$INDIR" "$OUTDIR" "$RECURSIVE" "$@"

rm "$CATERVA_VALTABLE"
