#! /bin/bash
# $Id: runfex,v 1.7 1998/03/31 10:49:08 schmidt Exp $

#{{{ docu
#
# runfex - run factory example collection.
#
#}}}

set -o nounset
set -o noglob

trap signalHandlerInterrupt SIGINT
trap signalHandlerQuit SIGQUIT

#
# - functions.
#

#{{{ signalHandlerInterrupt (), signalHandlerQuit ()
#{{{ docu
#
# signalHandler*() - catch signals.
#
#}}}
signalHandlerInterrupt()
{
    warn "received signal SIGINT"
    exit
}

signalHandlerQuit()
{
    warn "received signal SIGQUIT"
    exit
}
#}}}

#{{{ warn ()
#{{{ docu
#
# warn() - print arguments to stderr prefixed by ExecName.
#
# ExecName (global constant): name of the shell script
#   being executed
#
#}}}
readonly ExecName="$0"

warn()
{
    echo "$ExecName:" "${@-}" >&2
}
#}}}

#{{{ usage ()
usage()
{
echo "
Usage: runfex [<options>] [<factoryOpts>] [<factoryEnvSpec>] <fexFile> [<examplePatterns>]

Runs all examples in <fexFile> matching <examplePattern>.
<examplePatterns> should be regular shell patterns, which may be
preceded by a \`^' to exclude all examples matching <examplePattern>.

When run in foreground, SIGQUIT immediately interrupts \`runfex',
while SIGINT only interrupts the example currently being
calculated.  In background, SIGTERM interrupts \`runfex' after the
current example has been finished.

<options>:  -d:         debug mode.  In debug mode, examples are
                        echoed to stdout instead of being executed.
            -C <runConfiguration>: specifies which object/executable
                        configuration to use.  Defaults to \`opt'.
            -t:         do not run checks

Besides from regular shell commands, the commands \`collection'
and \`example' may be used in a Factory example file.
\`collection' specifies options and environment common to a
collection of examples, \`example' defines an example.

collection <collectionName> [<collectionOptions>] [<factoryOpts>] [<factoryEnvSpec>]
example <exampleName> [<exampleOptions>] [<factoryOpts>] [<factoryEnvSpec>] <example>

<collectionOptions>/<exampleOptions>:
            -n <note>: specifies comment on the collection/example

<factoryOpts>:
            -a <time>:  sets alarm option
            -c <times>: sets times option
            -O <options>: specifies optional arguments
            -o <outputOptions>: specifies which results to print

<outputOptions>:
            <number>: column width              x: do not print anything
            h: header                           p: characteristic
            s: switches                         v: variables
            n: number fo runs                   g: random generator seed
            f: Factories version                t: time
            c: check                            r: result" \
    >& 2
}
#}}}

#{{{ defineSkip ()
#{{{ docu
#
# defineSkip() - define function skipExample().
#
# rest: the patterns
#
# One function at least that does not use neither global
# variables nor constants.
#
#}}}
defineSkip()
{
    typeset regExp=""
    typeset notRegExp=""
    typeset rawArg arg

    for rawArg; do
	# check for leading ^
	arg="${rawArg#^}"
	if [ "$rawArg" = "$arg" ]; then
	    regExp="$regExp|$arg"
	else
	    notRegExp="$notRegExp|$arg"
	fi
	shift
    done
    regExp="${regExp#|}"
    notRegExp="${notRegExp#|}"

    if [ -z "$regExp" -a -z "$notRegExp" ]; then
	eval "
skipExample()
{
    return 1
}
"
    elif [ -n "$regExp" -a -z "$notRegExp" ]; then
	eval "
skipExample()
{
    case \"\$1\" in
	($regExp) return 1 ;;
	(*) return 0 ;;
    esac
}
"
    elif [ -z "$regExp" -a -n "$notRegExp" ]; then
	eval "
skipExample()
{
    case \"\$1\" in
	($notRegExp) return 0 ;;
	(*) return 1 ;;
    esac
}
"
    else
	eval "
skipExample()
{
    case \"\$1\" in
	($notRegExp) return 0 ;;
	($regExp) return 1 ;;
	(*) return 0 ;;
    esac
}
"
    fi
}
#}}}

#{{{ runAlgorithm ()
#{{{ docu
#
# runAlgorithm() - run algorithm with the correct options.
#
# To a pity, there is a lot of global variables necessary to
# determine the "correct options".  The run* variables are set
# in main program, the collection* variables in collection()
# and the example* variables in example().
# All of these variables are exclusively used here.
#
# $1: debugMode
# $2: algorithm to run
# rest: its arguments
#
# runConfiguration (global variable): which configurations'
#   executables to use
# run* (global variables): options, optional arguments, and
#   environment specified at run level
# collection* (global variables): options, optional arguments,
#   and environment specified at collection level
# example* (global variables): options, optional arguments,
#   and environment specified at example level
#
#}}}
runConfiguration="opt"

runOptions=""
runOptArguments=""
runEnvironment=""

collectionOptions=""
collectionOptArguments=""
collectionEnvironment=""

exampleOptions=""
exampleOptArguments=""
exampleEnvironment=""

runAlgorithm()
{
    typeset debugMode="$1" \
	    name="$2"
    shift 2

    # now its time to paste the options together
    $debugMode "$name.$runConfiguration" \
	$collectionOptions $exampleOptions $runOptions -oa -- \
	$collectionEnvironment $exampleEnvironment $runEnvironment \
	${runOptArguments:-${exampleOptArguments:-${collectionOptArguments}}} \
	"$@"
}
#}}}

#{{{ printData ()
#{{{ docu
#
# printData() - print example data.
#
# Yet another bunch of global variables.  The *OutputOptions are
# set by the main program, collection(), and example(), resp.,
# and determine which information to print.  All the variables
# are used exclusively here.
#
# $1: name
# $2: note
#
# defColWidth (global constant): default width to print results
# *OutputOptions (global variables): which information to print
#
#}}}
typeset	-ir defColWidth=80

runOutputOptions=""
collectionOutputOptions=""
exampleOutputOptions=""

printData()
{
    typeset -i colWidth

    typeset name="$1" \
	    note="$2" \
	    options="${runOutputOptions:-${exampleOutputOptions:-${collectionOutputOptions}}}" \
	    line=""

    # get column width (we use line as a dummy here)
    line="$( echo "$options" | sed s/[^0-9]//g )"
    colWidth=${line:-$defColWidth}

    case "$options" in
	(*x*) return ;;
    esac

    case "$options" in
	(*h*)
	    # do some pretty printing
	    if [ ${#name} -lt 7 ]; then
		echo "$name:		$note."
	    elif  [ ${#name} -lt 15 ]; then
		echo "$name:	$note."
	    else
		echo "$name: $note."
	    fi ;;
    esac
    if read line; then case "$options" in
	(*p*) echo "$line" ;;
    esac; fi
    if read line; then case "$options" in
	(*s*) echo "$line" ;;
    esac; fi
    if read line; then case "$options" in
	(*v*) echo "$line" ;;
    esac; fi
    if read line; then case "$options" in
	(*n*) echo "$line" ;;
    esac; fi
    if read line; then case "$options" in
	(*g*) echo "$line" ;;
    esac; fi
    if read line; then case "$options" in
	(*f*) echo "$line" ;;
    esac; fi
    if read line; then case "$options" in
	(*t*) echo "$line" ;;
    esac; fi
    if read line; then case "$options" in
	(*c*) echo "$line" ;;
    esac; fi

    # format the result
    case "$options" in
	(*r*)
	    if [ $colWidth = 0 ]; then
		cat -u
	    else
		fold -s -w "$colWidth"
	    fi ;;
    esac
}
#}}}

#{{{ example ()
#{{{ docu
#
# example() - run an example.
#
# Sets the example* variables from the commandline.  Runs the
# example in dependency on debugMode and on the result of
# skipExample().  Collects data from the example in the alg*
# variables for printData().
#
# debugMode (global variable): set by main program.  Whether to
#   run examples or to print them.
#
#}}}
debugMode=""

example()
{
    typeset name="" \
	    note="" \
	    \
	    options="" \
	    optArguments="" \
	    environment="" \
	    \
	    outputOptions=""

    # read example name and skip if necessary
    if [ "$#" = "0" ]; then
	warn "no example name specified"
	exit 1
    fi
    name="$1"
    shift

    if skipExample "$name"; then
	if [ -n "$debugMode" ]; then
	    echo "skipping $name"
	fi
	return
    else
	warn "running $name"
    fi
    
    # read options
    typeset opt
    while getopts "n:a:c:O:o:" opt; do
	case "$opt" in
	    (n)	note="$OPTARG" ;;
	    (a)	options="$options -a$OPTARG" ;;
	    (c)	options="$options -c$OPTARG" ;;
	    (O) optArguments="$optArguments $OPTARG" ;;
	    (o)	outputOptions="$OPTARG" ;;
	    (?)	warn "bad example option"; exit 1 ;;
	esac
    done
    # shift options and reset OPTIND
    typeset -i optind
    optind=OPTIND-1
    shift $optind
    OPTIND=1

    # read environment
    while [ "${1-}" != "${1+${1#/}}" ]; do
	environment="$environment $1"
	shift
    done

    # set global variables
    exampleOptions="$options"
    exampleOptArguments="$optArguments"
    exampleEnvironment="$environment"
    exampleOutputOptions="$outputOptions"

    # run algorithm and print data
    if [ -n "$debugMode" ]; then
	runAlgorithm "echo" "$@"
    else
	runAlgorithm "" "$@" | printData "$name" "$note"
    fi
}
#}}}

#{{{ collection ()
#{{{ docu
#
# collection() - set up collection data.
#
# Sets the collection* variables from commandline.
#
#}}}
collection()
{
    typeset options="" \
	    optArguments="" \
	    environment="" \
	    \
	    outputOptions="" \
    
    # read collection name (and ignore it)
    if [ "$#" = "0" ]; then
	warn "no collection name specified"
	exit 1
    fi
    shift

    # read options
    typeset opt
    while getopts "n:a:c:O:o:" opt; do
	case "$opt" in
	    (n)	: ;;
	    (a)	options="$options -a$OPTARG" ;;
	    (c)	options="$options -c$OPTARG" ;;
	    (O) optArguments="$optArguments $OPTARG" ;;
	    (o)	outputOptions="$OPTARG" ;;
	    (?)	warn "bad collection option"; exit 1 ;;
	esac
    done
    # shift options and reset OPTIND
    typeset -i optind
    optind=OPTIND-1
    shift $optind
    OPTIND=1

    # read environment
    while [ "${1-}" != "${1+${1#/}}" ]; do
	environment="$environment $1"
	shift
    done

    # set global variables
    collectionOptions="$options"
    collectionOptArguments="$optArguments"
    collectionEnvironment="$environment"
    collectionOutputOptions="$outputOptions"
}
#}}}

#{{{ main program
#{{{ docu
#
# - main program.
#
# Sets the run* variables for runAlgorithm() and printData()
# from commandline.  Sets debugMode for example().  Reads the
# collection.
#
#}}}
typeset configuration="opt" \
	\
	options="" \
	optArguments="" \
	environment="" \
	\
	outputOptions="" \
	\
	fexFile=""

# read options
typeset opt
while getopts "dC:ta:c:O:o:" opt; do
    case "$opt" in
	(d)  debugMode="1" ;;
	(C)  configuration="$OPTARG" ;;
	(t)  options="$options -t" ;;
	(a)  options="$options -a$OPTARG" ;;
	(c)  options="$options -c$OPTARG" ;;
	(O)  optArguments="$optArguments $OPTARG" ;;
	(o)  outputOptions="$OPTARG" ;;
	(?)  warn "bad run option"; usage; exit 1 ;;
    esac
done
# shift options and reset OPTIND
typeset -i optind
optind=OPTIND-1
shift $optind
OPTIND=1

# read environment
while [ "${1-}" != "${1+${1#/}}" ]; do
    environment="$environment $1"
    shift
done

# process rest of arguments
if [ "$#" = "0" ]; then
    warn "no fexFile specified"
    usage
    exit 1
fi
fexFile="${1%.fex}"
shift

defineSkip "$@"

# before going on, check for existence of collection
if [ ! -f "$fexFile.fex" ]; then
    warn "collection $fexFile.fex not found"
    exit 1
fi

# reset factory environemnt to exclude external influences
FTEST_ENV=""
FTEST_CIRCLE=""
FTEST_ALARM=""

# set global variables and execute collection
runConfiguration="$configuration"
runOptions="$options"
runOptArguments="$optArguments"
runEnvironment="$environment"
runOutputOptions="$outputOptions"

. "$fexFile.fex"
#}}}
