#!/bin/bash

# Cronic v2 - cron job report wrapper
# Copyright 2007 Chuck Houpt
# Documentation at http://habilis.net/cronic/
# In brief:
#   Cronic is a small shim shell script for wrapping cron jobs so that cron
#   only sends email when an error has occurred. Cronic defines an error as
#   any non-trace error output or a non-zero result code. Cronic filters
#   Bash execution traces (or anything matching PS4) from the error output,
#   so jobs can be run with execution tracing to aid forensic
#   debugging. Cronic has no options, it simply executes its arguments.
# Extended by Michael Ernst to add --expected-status, --permit-stderr, and --duplicate-stderr flags.
# --duplicate-stderr prints stderr twice so that it is less likely to be overlooked.

# Exit if a command exits with a non-zero status, and treat unset variables
# as an error.
set -eu

DEBUG=false
# DEBUG=true

OUT=/tmp/cronic.out.$$
ERR=/tmp/cronic.err.$$
REDUCED_ERR=/tmp/cronic.err.reduced.$$
TRACE=/tmp/cronic.trace.$$

EXPECTED=0
if [ "$1" == "--expected-status" ]; then
  shift
  EXPECTED=$1
  shift
fi
PERMITSTDERR=0
if [ "$1" == "--permit-stderr" ]; then
  shift
  PERMITSTDERR=1
fi
DUPLICATE_STDERR=0
if [ "$1" == "--duplicate-stderr" ]; then
  shift
  DUPLICATE_STDERR=1
fi

set +e
"$@" > $OUT 2> $TRACE
RESULT=$?
set -e

PATTERN="^${PS4:0:1}\\+${PS4:1}"
if grep -q "$PATTERN" $TRACE; then
  grep -v "$PATTERN" $TRACE > $ERR
else
  ERR=$TRACE
fi

# Certain Unix programs send output to stderr even when a command is successful!
# For example, git does this, and the POSIX standard requires it for make.
# The full stderr output is shown, but file REDUCED_ERR is checked for emptiness below.
# TODO: Expand this.
cat $ERR \
  | grep -v "^Everything up-to-date$" \
  | grep -v '^make[\[0-9\]\+]: \(Entering\|Leaving\) directory '"'"'.*'"'"'$' \
    > $REDUCED_ERR || true

if [ $RESULT -ne "$EXPECTED" ] || { [ $PERMITSTDERR -eq 0 ] && [ -s "$REDUCED_ERR" ]; }; then
  echo "Cronic detected failure or error output for the command:"
  echo "$@"
  echo
  if [[ $EXPECTED -eq 0 ]]; then
    echo "RESULT CODE: $RESULT  (0 means success)"
  else
    echo "RESULT CODE: $RESULT  (expected: $EXPECTED)"
  fi
  echo
  echo "ERROR OUTPUT:"
  cat "$ERR"
  echo
  if [ "$DEBUG" != "true" ]; then
    if ! cmp --silent "$ERR" "$REDUCED_ERR"; then
      echo "REDUCED ERROR OUTPUT:"
      cat "$REDUCED_ERR"
      echo
    fi
  fi
  echo "STANDARD OUTPUT:"
  cat "$OUT"
  if [ $TRACE != $ERR ]; then
    echo
    echo "TRACE-ERROR OUTPUT:"
    cat "$TRACE"
  fi
  if [ $DUPLICATE_STDERR -eq 1 ]; then
    echo
    echo "ERROR OUTPUT:"
    cat "$ERR"
  fi
  echo
  echo "END OF CRONIC OUTPUT."
fi

if [ "$DEBUG" == "true" ]; then
  # Don't "echo" anything here because it gets appended to stderr.
  :
else
  rm -f "$OUT"
  if [ ! $ERR == /dev/null ]; then
    rm -f "$ERR"
  fi
  rm -f "$REDUCED_ERR"
  rm -f "$TRACE"
fi

exit $RESULT
