#!/bin/bash
#
# 25 May 1999 - Andrew Wood <andrew.wood@poboxes.com>

pn=multirun
vn=1.0.1
rn=$0

help_text="Usage: $rn [OPTIONS] PROGRAM [[OPTIONS] PROGRAM]...
Run all PROGRAMs simultaneously, each with their own portion of the screen.

Options apply to the program immediately afterwards ONLY.  Default values are
shown in [brackets].  To set the defaults for all subsequent programs, prefix
the option name with \`default-', eg \`--default-lines'.

  --minlines=NUM          allow at least NUM lines for text [2]
  --maxlines=NUM          allow at most NUM lines for text  [0]
  --foreground=COL        PROGRAM output foreground colour  [white]
  --background=COL        PROGRAM output background colour  [black]
  --title=TITLE           PROGRAM title text                [PROGRAM]
  --title-foreground=COL  title text foreground colour      [white]
  --title-background=COL  title text background colour      [black]
  --title-attribute=ATTR  title text attribute              [reversed]
  --log=FILE              also append output to FILE        [/dev/null]

  --help                  display this help
  --version               show version and warranty information

Colours can be any of \`black', \`red', \`green', \`yellow', \`blue', \`magenta',
\`cyan', or \`white', or the ANSI colour code (0-7).  Attributes can be any of
\`normal', \`bold', \`underlined', or \`reversed', or the code (0,1,4,7)."

version_text="$pn version $vn
Copyright (C) 1999 Andrew Wood

    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.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, write to the Free Software
    Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

Please report bugs to Andrew Wood <andrew.wood@poboxes.com>."


# Variables that can be set for each program
#
allvars="minlines maxlines foreground background title
         title_foreground title_background title_attribute log"

# Kludge
#
title_foreground=title-foreground
title_background=title-background
title_attribute=title-attribute


# Set up evaluation script to deal with all options above
#
opt_eval="case \$i in"
for i in $allvars; do
  case $i in
    *_*) eval o=\$$i ;;
    *) o=$i ;;
  esac
  opt_eval="$opt_eval
    --$o) store_next=$i ;;
    --$o=*) $i=\`echo \$i|sed 's/^[^=]*=//'\` ;;
    --default-$o) store_next=default_$i ;;
    --default-$o=*) default_$i=\`echo \$i|sed 's/^[^=]*=//'\` ;;"
done
opt_eval="$opt_eval
    --*) errexit \"unknown option \\\`\$i'.\" ;;
  esac"


# Program defaults
#
default_minlines=2
default_maxlines=0
default_foreground=white
default_background=black
default_title=PROGRAM
default_title_foreground=white
default_title_background=black
default_title_attribute=reversed
default_log=/dev/null


# Abort with error $1.
#
function errexit () {
  echo "$rn: $1" >&2
  echo "Try \`$rn --help' for more information." >&2
  exit 1
}


# Convert the colour values in variables $* to numbers and store them back.
#
function decode_colour () {
  local var val
  for var in $*; do
    eval val="\$$var"
    case $val in
      [bB][lL][aA]*) val=0 ;;
      [rR]*) val=1 ;;
      [gG]*) val=2 ;;
      [yY]*|[oO]*) val=3 ;;
      [bB][lL][uU]*) val=4 ;;
      [mM][aA]*|[pP][uU]*) val=5 ;;
      [cC]*) val=6 ;;
      [wW]*) val=7 ;;
      0|1|2|3|4|5|6|7) ;;
      *) errexit "invalid colour \`$val'" ;;
    esac
    eval $var=$val
  done
}


# Convert the attribute value in $1 to a number and store it back.
#
function decode_attribute () {
  local var val
  var=$1
  eval val="\$$1"
  case $val in
    [nN]*) val=0 ;;
    [bB]*) val=1 ;;
    [uU]*) val=4 ;;
    [rR]*) val=7 ;;
    0|1|4|7) ;;
    *) errexit "invalid attribute \`$val'" ;;
  esac
  eval $var=$val
}


# Add program $1 to the list of programs to be executed.
#
function add_program () {
  progs="${progs} <($1 2>&1|awk '{print\"$n:\"\$0;\
print>>\"$log\";fflush(\"\")}'>&3)"
  if [ "$title" = "PROGRAM" ]; then title="$1"; fi
  decode_colour foreground background title_foreground title_background
  decode_attribute title_attribute
  if [ "$minlines" -lt 2 ]; then
    errexit "must use at least 2 lines - one for title, one for text"
  fi
  eval _tt_$n=\""$title"\"
  eval _te_$n=\""\033[3$title_foreground;\
4$title_background;${title_attribute}m"\"
  eval _be_$n=\""\033[3$foreground;4${background}m"\"
  eval _lsize_$n=$minlines
  eval _lmax_$n=$maxlines
  n=$[1+$n]
  lines_used=$[$lines_used+$minlines]
  for v in $allvars; do eval $v="\$default_$v"; done
}


# Process command line options
#
for v in $allvars; do eval $v="\$default_$v"; done
store_next=""
progs=""
lines_used=0
n=1
for i; do
  if [ -n "$store_next" ]; then eval $store_next="$i"; store_next=""; else
    o=$i
    case $i in
      -[hH?]|--help) echo "$help_text"; exit 0 ;;
      -[vV]|--version) echo "$version_text"; exit 0 ;;
      --*) eval "$opt_eval" ;;
      *) add_program "$i" ;;
    esac
  fi
done

if [ -n "$store_next" ]; then errexit "\`$o' option requires an argument"; fi
if [ $n = 1 ]; then errexit "no programs specified"; fi


# Check output is connected to a terminal
#
if [ ! -t 1 ] && [ -t 2 ]; then exec >&2; fi
if [ ! -t 1 ] || [ ! -t 0 ]; then
  errexit "must be connected to a terminal"
fi


# Find the current terminal size
#
ROWS=`stty -a | sed -n 's/^.* rows \([0-9]\+\);.*$/\1/p'`


# Make sure we can fit everything on the screen
#
if [ $lines_used -gt $ROWS ]; then
  errexit "need at least $lines_used lines, terminal only has $ROWS"
fi


# Expand windows to fill the screen
#
atmax=1
j=1
while [ $lines_used -lt $ROWS ] && [ $atmax -lt $n ]; do
  if [ $j -ge $n ]; then
    j=1
    atmax=1
  fi
  eval size=\$_lsize_$j
  eval max=\$_lmax_$j
  if [ $max -gt 0 ] && [ $size -ge $max ]; then
    atmax=$[1+$atmax]
    j=$[1+$j]
  else
    eval _lsize_$j=$[1+$size]
    lines_used=$[1+$lines_used]
    j=$[1+$j]
  fi
done


# Clear the screen and output the titles, also working out Y co-ordinates of
# each window and storing them for use in the AWK script
#
echo -n -e "\033[H\033[J"
j=1
ypos=1
awkprg='BEGIN{'
while [ $j -lt $n ]; do
  eval text=\""\$_tt_$j"\"
  eval trib=\""\$_te_$j"\"
  eval lines=\$_lsize_$j
  eval body=\""\$_be_$j"\"
  echo -n -e "\033[${ypos}H${trib}${text}\033[0;37;40m"
  awkprg="${awkprg};yt[$j]=$[1+$ypos];yb[$j]=$[$ypos+$lines-1]"
  awkprg="${awkprg};be[$j]=\"$body\""
  ypos=$[$ypos+$lines]
  j=$[1+$j]
done


# Finish off the AWK output program
#
awkprg="${awkprg}}{
  t=substr(\$0,3)
  n=substr(\$0,1,1)
  printf \"\033[%d;%dr\033[%dH\n\",yt[n],yb[n],yb[n]
  printf \"%s%s\033[0;37;40m\",be[n],t
  fflush(\"\")
}"


# Make sure screen is reset to normal on exit
#
trap "echo -n -e '\033[0;37;40m\033[1;${ROWS}r\033[${ROWS};1H\n'" 0


# Run the pipeline
#
eval "{ exec /bin/true $progs >/dev/null; } 3>&1 | awk '$awkprg'"

# EOF
