# vim: set ft=sh:

indent() {
  printf '%*s' $(( (_shpec_indent - 1) * 2))
}

echoe() { printf "%b\n" "$*"; }

iecho() {
  indent && echoe "$@"
}

sanitize() {
  IFS= echoe "$1" | tr '\n' 'n' | tr "'" 'q'
}

describe() {
  : $((_shpec_indent += 1))
  iecho "$1"
}

end() {
  : $((_shpec_indent -= 1))
  _shpec_assertion_printed=0
  [ $_shpec_indent -ge 0 ] && return 0
  echo >& 2 "shpec: $_shpec_file: unexpected 'end'"
  exit 1
}

end_describe() {
  iecho "Warning: end_describe will be deprecated in shpec 1.0." \
    "Please use end instead."
  end
}

# Beware: POSIX shells are not required to accept
# any identifier as a function name.

stub_command() {
  _shpec_stub_body="${2:-:}"
  eval "$1() { ${_shpec_stub_body}; }"
}

unstub_command() { unset -f "$1"; }

it() {
  : $((_shpec_indent += 1))
  : $((_shpec_examples += 1))
  _shpec_assertion="$1"
}

is_function() {
  case $(LC_ALL=C type "$1" 2> /dev/null) in
    (*function*) return 0;;
  esac
  return 1
}

assert() {
  case "x$1" in
  ( xequal )
    print_result "[ '$(sanitize "$2")' = '$(sanitize "$3")' ]" \
      "Expected [$2] to equal [$3]"
  ;;
  ( xunequal )
    print_result "[ '$(sanitize "$2")' != '$(sanitize "$3")' ]" \
      "Expected [$2] not to equal [$3]"
  ;;
  ( xgt )
    print_result "[ $2 -gt $3 ]" \
      "Expected [$2] to be > [$3]"
  ;;
  ( xlt )
    print_result "[ $2 -lt $3 ]" \
      "Expected [$2] to be < [$3]"
  ;;
  ( xglob )
    print_result "case '$2' in $3) :;; *) false;; esac" \
      "Expected [$2] to match [$3]"
  ;;
  ( xno_glob )
    print_result "case '$2' in $3) false ;; *) :;; esac" \
      "Expected [$2] not to match [$3]"
  ;;
  ( xpresent )
    print_result "[ -n '$2' ]" \
      "Expected [$2] to be present"
  ;;
  ( xblank )
    print_result "[ -z '$2' ]" \
      "Expected [$2] to be blank"
  ;;

  ( xfile_present )
    print_result "[ -e $2 ]" \
      "Expected file [$2] to exist"
  ;;
  ( xfile_absent )
    print_result "[ ! -e $2 ]" \
      "Expected file [$2] not to exist"
  ;;
  ( xsymlink )
    link="$(readlink "$2")"
    print_result "[ '$link' = '$3' ]" \
      "Expected [$2] to link to [$3], but got [$link]"
  ;;
  ( xtest )
    print_result "$2" \
      "Expected $2 to be true"
  ;;
  ( xgrep )
    print_result "echo \"$2\" | grep -q \"$3\"" "Expected [$2] to match [$3]"
  ;;
  ( xno_grep )
    print_result "echo \"$2\" | grep -qv \"$3\"" "Expected [$2] to not match [$3]"
  ;;
  ( xegrep )
    print_result "echo \"$2\" | egrep -q \"$3\"" "Expected [$2] to match [$3]"
  ;;
  ( xno_egrep )
    print_result "echo \"$2\" | egrep -qv \"$3\"" "Expected [$2] to not match [$3]"
  ;;
  ( * )
    if is_function "$1"; then
      _shpec_matcher="$1"; shift
      $_shpec_matcher "$@"
      return 0
    else
      print_result false "Error: Unknown matcher [$1]"
    fi
  ;;
  esac
}

print_result() {
  if eval "$1"; then
    : $((_shpec_assertion_printed += 1))
    if [ ${_shpec_assertion_printed} -le 1 ]; then
      iecho "$_shpec_green$_shpec_assertion$_shpec_norm"
    else
      printf "%b" "$_shpec_clear_ln"
      iecho "$_shpec_green$_shpec_assertion$_shpec_norm(x$_shpec_assertion_printed)"
    fi
  else
    : $((_shpec_failures += 1))
    _shpec_assertion_printed=0
    iecho "$_shpec_red$_shpec_assertion"
    iecho "($2)$_shpec_norm"
  fi
}

final_results() {
  [ $_shpec_failures -eq 0 ] && _shpec_color=$_shpec_green || _shpec_color=$_shpec_red
  echoe "${_shpec_color}${_shpec_examples} examples, ${_shpec_failures} failures${_shpec_norm}"
  times
  [ $_shpec_failures -eq 0 ]
  exit
}

shpec_version() {
  (
    VERSION=0.3.0
    echo $VERSION
  )
}

shpec() {
  (
    case "$1" in
    ( -v | --version )
      shpec_version
      exit 0
    ;;
    esac

    _shpec_examples=0
    _shpec_failures=0
    _shpec_indent=0
    _shpec_red="\033[0;31m"
    _shpec_green="\033[0;32m"
    _shpec_norm="\033[0m"
    _shpec_clear_ln="\033[1A\033[K"

    _shpec_root=${SHPEC_ROOT:-$(
      [ -d './shpec' ] && echo './shpec' || echo '.'
    )}
    SHPEC_ROOT=${_shpec_root}

    _shpec_matcher_files=$(
      find "$_shpec_root/matchers" -name '*.sh' 2>/dev/null
    )

    for _shpec_matcher_file in $_shpec_matcher_files; do
      . "$_shpec_matcher_file"
    done

    if [ $# -gt 0 ] ; then
      _shpec_files="${*}"
    else
      _shpec_files=$(
        find "$_shpec_root" -name '*_shpec.*'
      )
    fi

    for _shpec_file in $_shpec_files; do
      . "$_shpec_file"
    done

    final_results
  )
}

(
  _progname=shpec
  _pathname=$( command -v "$0" )
  _cmdname=${_pathname##*/}
  _main=shpec

  case $_progname in (${_cmdname%.sh}) $_main "$@";; esac
)
