#!/bin/bash
#
#  whizzytex --- WhizzyTeX, a WYSIWIG environment for TeX
#  Copyright (C) 2001, 2002 Didier Rmy
#  
#  WhizzyTeX 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, or (at your option)
#  any later version.
#
#  WhizzyTeX 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 
#  (enclosed in the file GPL).
#
#  See the file COPYING enclosed with the distribution.
#############################################################################
#  File whizzytex (Bash shell-script)
#############################################################################


### Configuration

# name (or full path) of the dump latex2e package file (without the extension)
PACKAGE=whizzytex.sty

# limit the length of error messages sent back to stdout.
TEXERRORMAXLINES=50
TEXERRORAROUND=20

# time limit for running initex in case this could loop, undetectedly!
LIMIT=20

# Signals telling gv and xdvi/advi to refresh
SIGDVI=SIGUSR1
SIGPS=SIGHUP

# Option to read command, could be use to send timeout, in case, whizzytex 
# should watch other files that those controlled by emacs
# READOPTION=-t 5
READOPTION=

# Translatin DVI to Postscript
DVIPS=dvips

# Default LaTeX implementation
INITEX=initex
LATEX=latex
LATEXFMT=latex

### End of manual configuration

DEBUG=false

case $# in
 0) echo 'Usage: whizzytex [ OPTIONS ] FILENAME'; exit 1;;
esac

COMMAND="$0 $*"
TOUCH=false
MAKE=defaultmake

DO=normal
DOMARKS=false
VIEW=wdvi
VIEWCOMMAND=advi
WATCHFILES=
DUPLEX=false
ADVI=0
while true
do
  case $1 in
   -kill) DO=kill; shift;;
   -wakeup) DO=wakeup; shift;;
   -format) DO=format; shift;;
   -pre) MAKE="$2"; shift 2;;
   -duplex) DUPLEX="true"; shift;;
   -marks) DOMARKS=true; shift;;
   -initex) INITEX="$2"; shift 2;;
   -latex) LATEX="$2"; shift 2;;
   -fmt) LATEXFMT="$2"; shift 2;;
   -marksonly) DO=marks; shift;;
   -watch) WATCHFILES="$*"; shift $#;;
   -dvi)
       VIEW=wdvi;
       case $2 in
       .) VIEWCOMMAND=xdvi;;
       *) VIEWCOMMAND=$2;;
       esac
       shift 2;;
   -advi)
       ADVI=1
       VIEW=wdvi;
       case $2 in
       .) VIEWCOMMAND=advi;;
       *) VIEWCOMMAND=$2;;
       esac
       shift 2;;
   -ps)
       VIEW=ps
       case $2 in
       .) VIEWCOMMAND=gv;;
       *) VIEWCOMMAND=$2;;
       esac
       shift 2;;
   -*)
       echo 'Unrecognized argument '"$1"'

Usage whizzytex: 

  whizzytex <option> ... <option> <file>

where <option> is 

    -kill 
    -format 
    -marks 
    -pre <preproces-command>
    -dvi <view-command> 
    -ps <view-command> 

  ' 1>&2
       exit 1;;
   *) break;;
  esac
done

HERE=$(/bin/pwd)

BASENAME=$(basename $1)
EXT=$(expr "$BASENAME" : '.*\(\.[^.]*\)')
NAME=$(basename "$BASENAME" "$EXT")
WHIZZY=_whizzy_$NAME
WHIZZYDIR=${WHIZZY}_d
FORMAT=\&$WHIZZY
LOCK=.$WHIZZY$EXT
SLICE=$WHIZZY.new
INPUT=$HERE/$WHIZZYDIR/input
OUTPUT=$HERE/$WHIZZYDIR/output
TMP=$HERE/$WHIZZYDIR/tmp
SECTIONS=$OUTPUT/sections
LOG=$TMP/log
ERR=$TMP/err

errlog () { echo "$*" 1>&2; }
fatal () { 
  errlog '*** Fatal error: ' "$*"
  [ -f $ERR ] && cat $ERR
  echo '<Fatal error>'
  suicide
}
warning () {
  echo "Warning: " $*
}  

quit () { 
  echo '<Quitting>'
  errlog 'Warning: ' "$*"
  cleanquit
}

log () { 
  if [ -f $OUTPUT/$1 ] 
  then
    grep -B 3 -A $TEXERRORAROUND -e '^!' $OUTPUT/$1 | head -$TEXERRORMAXLINES;
  fi
  false; 
}
errlog "$COMMAND"

if [ -f $NAME$EXT ]
then 
   :
else
   echo "File $NAME$EXT does not exist"
   exit 1
fi

clean () {
   [ -f $TMP/pids ] && kill -QUIT $(cat  $TMP/pids) 2>/dev/null
   rm -f $WHIZZY.* $NAME.fmt $NAME.$VIEW
   rm -rf $WHIZZYDIR
   rm -f $LOCK
   [ -f $WHIZZY ] && "Cleaning up..."
}

cleanquit () { clean; exit 0; }
trap cleanquit SIGQUIT
trap cleanquit SIGHUP

suicide () {
   if [ -f $LOCK ] 
   then
      PID=$(cat $LOCK)
      clean 
      kill -KILL $PID
   else
      clean
   fi
   exit 2
}

wakeup () { [ -f $LOCK ] && kill -CONT $(cat $LOCK); }
runinitex () { ( ulimit -t $LIMIT; $INITEX "$@" ); }
runlatex    () { ( ulimit -t $LIMIT; $LATEX  "$@" ); }


# Making format

format () {
 ( runinitex "&$LATEXFMT" '\nonstopmode\let\Documentclass\documentclass\renewcommand{\documentclass}[2][]{\Documentclass[#1]{#2}\let\WhizzyAdvi'"$ADVI"'\def\WhizzyExt{'"$EXT"'}\input{'"$PACKAGE"'}}\input{'"$NAME$EXT"'}' )
}

############################################################################
# BEGIN sections

SECTION='\\\(chapter\|section\|subsection\|subsubsection\)'
intersect () {
    sort $TMP/sec $WHIZZY.pag > $TMP/spg
    sort -n -t : +0 +1 -u $WHIZZY.pag $TMP/sec | sort | comm -1 -3 - $TMP/spg > $TMP/sec.1
    sort -n -t : +0 +1 -u $TMP/sec $WHIZZY.pag | sort | comm -1 -3 - $TMP/spg > $TMP/pag
}

premark () {
    if [ -f $WHIZZY.pag ]
    then 
       SOURCES=$(sed -n -e 's/^\([^:]*\):\(.*\)$/\1/p' $WHIZZY.pag | sort -u); 
    else
       SOURCES=$NAME$EXT
    fi
    grep -n -e "^$SECTION" $SOURCES /dev/null | \
    sed -e 's/^\([^:]*\):\([0-9]*\):'"$SECTION"'/\1:\2:\3@/' -e 's/"/\\"/' \
      > $TMP/sec
}
postmark () {
    intersect
    ( join -t @  $TMP/pag $TMP/sec.1 | \
      sed -e 's/^\([^:]*\):[0-9]*:\([^@]*\)@\([^@]*\)@\(.*\)$/\1:("\\\2\4"."\\WhizzySlice\3")/' -e 's/\\/\\\\/g' ) > $TMP/tmp
    ( echo "(setq tmp '("; 
      for file in $SOURCES
      do
        if [ -f $file ]
        then
          echo '("'$file'".('
          ( grep -e "$file" $TMP/tmp | sed -e 's/^\([^:]*:\)\(.*\)/\2/' ) 
          echo '))'
        fi
      done
      echo '))'
    ) > $SECTIONS
}
marks () {
    premark
    runlatex $FORMAT '\WhizzytexInput{'"$NAME"'}' > /dev/null
    postmark
    cat $SECTIONS
}

batchmarks () {
    premark
    if runlatex $FORMAT '\WhizzytexInput{'"$NAME"'}' 1>$OUTPUT/whole
    then
      cp $NAME.dvi $NAME.$VIEW
      if $1
      then
         postmark
      fi
      [ -f $NAME.toc ] && cp $NAME.toc $WHIZZY.toc
      if grep 'LaTeX Warning: Citation' $NAME.log
      then
         bibtex $NAME; 
         [ -f $NAME.bbl ] && cp $NAME.bbl $WHIZZY.bbl
         true
      fi
    else 
       rm -f $WHIZZY.pag; false
    fi
    
}

# END sections

############################################################################
# BEGIN Worddiff

wordify () { 
    tr '\n' '
' | 
    sed -e 's/[ 
][ 
]*/
/g' | \
    tr '
' '\n' 
}

worddiff () { 
  FST=$1
  SND=$2
  DIF=$WHIZZY.dif
  diff $FST $SND > $DIF
  if [ $(grep -v -e '^[-><]' $DIF | wc -l) -eq 1 ] && \
     [ $(wc -l < $DIF) -lt 3 ] && \
       grep -e '^[1-9][0-9,]*[ac]' $DIF > $DIF.lin
  then
    sed $DIF -n -e '/^< /s/^< //p' > $FST.lin
    sed $DIF -n -e '/^> /s/^> //p' > $SND.lin

    wordify < $FST.lin > $FST.wrd
    wordify < $SND.lin > $SND.wrd

    diff $FST.wrd $SND.wrd > $DIF.wrd

    if [ $(grep -v -e '^[-><]' $DIF.wrd | wc -l) -eq 1 ] && \
         grep -e '^[1-9][0-9,]*[ac]' $DIF.wrd > /dev/null
    then
       (
        echo '<Error in Line'
        cat $DIF.lin
        echo Word
        grep -e '^[0-9]' $DIF.wrd
        echo ':'
        sed $DIF.wrd -n -e 's/^> \(.*\)$/\1/p'; 
        echo '>'
       ) | tr '\n' ' '
       echo     
    else
      false
    fi
  else 
    false
  fi
}

# END Wordify

############################################################################
# So what should we do

case $DO in
  kill) suicide && exit 0 || exit 1;;
  wakeup) wakeup && exit 0 || exit 1;;
  format) format && exit 0 || exit 1;;
  marks) marks && exit 0 || exit 1;;
  normal) 
    # To ensure that only one deamon is running on the spool file.
    if [ -f $LOCK ] && kill -CONT $(cat $LOCK) 2>/dev/null 
    then
      fatal 'Remove running WhizzyTeX process first'
    else
       echo $$ > $LOCK
    fi
   ;;
esac

############################################################################
# We go for good

mkdir $WHIZZYDIR $INPUT $OUTPUT $TMP 2>/dev/null

if [ -f $NAME.fmt -a $NAME$EXT -ot $NAME.fmt ]
then 
  :
else
  echo -n '<Initial formating '
  if { format >$OUTPUT/format 2>$ERR || \
       { echo 'failed>'; 
         echo Removing $NAME.aux; 
         echo '<Retrying '; 
         rm -f $NAME.aux && format >$OUTPUT/format 2>$ERR; }; } \
     && [ -f $NAME.fmt ] 
  then
     mv $NAME.fmt $WHIZZY.fmt
     echo 'succeeded>'
  else
     rm -f $NAME.fmt
     echo 'failed>'
     log format
     fatal 'could not build initial format'
  fi
fi

# Initial file

echo '\begin{document}\WhizzyEmptyPage\end{document}' > ${WHIZZY}_nil$EXT

# Texing...

echo $VIEWCOMMAND 1>&2

ANTIDATE=$(date +%m%d%H%M.%S)
case $VIEW in
  ps)
    preview () {
        $DVIPS -o $WHIZZY.pst $WHIZZY.dvi 2>/dev/null && \
        { [ ! $WHIZZY.pst -nt $WHIZZY.ps ] && touch -t $ANTIDATE $WHIZZY.pst
          mv $WHIZZY.pst $WHIZZY.ps ; }
    }
    SIG=$SIGPS
    ;;   
  wdvi)
    preview () { mv $WHIZZY.dvi $WHIZZY.$VIEW; }
    SIG=$SIGDVI
    ;;   
esac

newfiles () {
  mv $(find $INPUT -type f -print || suicide) $HERE/  2>/dev/null
}

defaultmake () { mv $SLICE $WHIZZY$EXT 2>/dev/null; }

preprocess () {
  if $MAKE $WHIZZY$EXT >$OUTPUT/make 2>$ERR && [ -f $WHIZZY$EXT ]
  then 
     true
  else
     echo '<Preprocessing failed>'
     [ -f $ERR ] && cat $ERR
     log make
     false
  fi
}

PROCESS=false
process () {
  PROCESS=false
  rm -f $WHIZZY.aux
  echo '<Recompiling>'
  if { runlatex $FORMAT $WHIZZY$EXT && preview; } 1>$OUTPUT/slice
  then
     echo "<Compilation succeeded>"
     ln -f $WHIZZY$EXT $WHIZZY.last
  else
     echo "<Recompilation failed>"
     if [ -f $WHIZZY.last ] && worddiff  $WHIZZY.last $WHIZZY$EXT
     then 
       :
     else
       log slice
       [ -f $WHIZZY.last ] && echo 'l.'$[ $(wc -l < $WHIZZY.last) - 1 ]' '
     fi
     echo '<Continuing>'
     false
  fi
}

############################################################################
# Initial run

# We ensure that the dvi file exists 
if runlatex $FORMAT ${WHIZZY}_nil$EXT > $OUTPUT/nil
then
  mv ${WHIZZY}_nil.dvi $WHIZZY.dvi
  preview || fatal 'Cannot preview initial page'
else
  cat $OUTPUT/nil
  fatal 'Cannot build initial page'
fi

# To give it a chance to see citations and other global information.

[ -f $NAME.bbl ] && cp $NAME.bbl $WHIZZY.bbl
[ -f $NAME.toc ] && cp $NAME.toc $WHIZZY.toc

# process $WHIZZY$EXT is present. Will override $WHIZZY.dvi if it succeeds

if newfiles; then preprocess && process; fi

# lauch the previewer(s) in background, storing its pid in $ID
# and putting a handler on its termination. 

rm -f $TMP/ID
{
  $VIEWCOMMAND $WHIZZY.$VIEW 2>$ERR &
  echo $! >> $TMP/pids
  ID=$!
  echo $ID > $TMP/ID
  wait $ID \
      && quit 'Quitting viewer' \
      || fatal 'Viewing process terminated abnormally'
} &

checkviewer () {
  kill -$SIG $ID || fatal 'Previewer is not responding to signal' $SIG
}

whole () {
   echo '<Recompiling whole document>'
   if batchmarks $1 </dev/null >$LOG
   then
      echo '<Whole document updated>'
      if $1; then echo '<Pages and sections updated>'; fi
      [ -f $TMP/DID ] && kill -$SIG $(cat $TMP/DID) || rm -f $TMP/DID
      true
   else
      echo '<Whole document recompilation failed>'
   fi
}
whole $DOMARKS

## Reformating

reformat () {
  echo '<Reformating>'
  touch -r $NAME$EXT $WHIZZY.fmt
  if format 1>$OUTPUT/format 2>$ERR
  then
      mv -f $NAME.fmt $WHIZZY.fmt
      echo '<Reformatting succeeded>'
      PROCESS=true
      whole $DOMARKS
  else
      echo '<Reformatting failed>'
      log format
      echo '<Contuning with the old format>'
  fi
}

## Duplex

duplex () {
  [ -f $NAME.$VIEW ] || \
  { cp $WHIZZY.$VIEW $NAME.$VIEW; 
    warning '(duplex) Using ' $WHIZZY.$VIEW ' instead of missing ' $NAME.$VIEW
  }
  $VIEWCOMMAND $NAME.$VIEW &
  echo $! > $TMP/DID
  echo $! >> $TMP/pids
}
if $DUPLEX; then duplex; fi

## wait for viewer, this is here, so luckily one does not need to wait

while [ ! -f $TMP/ID ];
do if [ -x /bin/usleep ]; then /bin/usleep 10; else /bin/sleep 1; fi; done
ID=$(cat $TMP/ID)


## The loop watching changes

while true
do
  if [ ! -d $WHIZZYDIR ]
  then fatal 'No more _whizzy_ directory'
  elif [ $WHIZZY.fmt -ot $NAME$EXT ]
  then reformat
  elif newfiles && preprocess || $PROCESS
  then process && checkviewer
  else
     echo '<Waiting>'
     read $READOPTION COMMAND REST
     case /$COMMAND/ in
       /duplex/) duplex ;;
       /reformat/) reformat ;;
       /debug/)
          case $REST in
              on) set -o xtrace; DEBUG=true;; 
             off) set +o xtrace; DEBUG=false;; 
          esac
          echo "<debug is $DEBUG>"
          ;;
       /exit/) break ;;
     esac
  fi
done

cleanquit

# whizzytex ends here
