#!/bin/sh
# Wrapper around checkbashisms/shellcheck/whatever other shell checkers I can find
#
# Copyright (C) 2016 Austin English
#
# This software comes with ABSOLUTELY NO WARRANTY.
#
# This is free software, placed under the terms of the GNU Lesser
# Public License version 2.1 (or later), as published by the Free
# Software Foundation. Please see the file COPYING for details.

set -e
set -x

###################################################################################################
# Helpers
###################################################################################################

w_die() {
    echo "$* failed"
    exit 1
}

w_try() {
    "$@"
    status=$?
    if test ${status} -ne 0; then
        w_die "Note: command $* returned status ${status}.  Aborting."
    fi
}

###################################################################################################
# Setup
###################################################################################################

if [ ! -f Makefile ] ; then
    w_die "$0 should be run from the top of the source tree"
fi

temp="$(mktemp -d)"

trap 'rm -fr "$temp"' EXIT

###################################################################################################
# Test functions
###################################################################################################

# tests using bashate
test_bashate() {
    if [ ! -x "$(command -v bashate 2>/dev/null)" ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then
        echo "On OSX and bashate not available, skipping."
        return
    else
        bashate="$(command -v bashate)"

        #echo "======================== Begin bashate version info ==========================="
        "${bashate}" --help > /dev/null || w_die "bashate must be installed!"
        ## bashate doesn't have a --version option (as of bashate-0.3.1)
        #"$bashate" --version
        #echo "======================== End bashate version info ==========================="

        # Can't ignore individual things for now, filed bug:
        # https://bugs.launchpad.net/bash8/+bug/1698088
        # E006=line length check
        # E010=do/while same line (in some embedded perl in winetricks)
        # E044=Use [[ for non-POSIX comparisions
        echo "Checking ${shellscript} with bashate:"
        w_try "${bashate}" -i E006,E010,E044 "${shellscript}"
    fi
}

# tests using checkbashisms
test_checkbashisms() {
    if [ ! -x "$(command -v checkbashisms 2>/dev/null)" ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then
        echo "On OSX and checkbashisms not available, skipping."
        return
    else
        checkbashisms="$(command -v checkbashisms)"

        echo "======================== Begin checkbashisms version info ==========================="
        "${checkbashisms}" --help > /dev/null || w_die "checkbashisms must be installed!"
        "${checkbashisms}" --version
        echo "======================== End checkbashisms version info ==========================="

        # Check if checkbashisms supports `command -v`. If not, warn, or if on travis, patch it:
        cat > "${temp}/command.sh" <<_EOF
#!/bin/sh
command -v grep
_EOF

        if ! "${checkbashisms}" --posix "${temp}/command.sh"; then
            echo "Use a sed expression, to add checkbashisms support for 'command -v', based on the patch from https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=733511"
            echo "Creating a copy of the checkbashisms script, in ${temp}, to workaround this issue"
            # /usr/bin/ (or /usr/local/bin on OSX) isn't writable on Travis, so we have to patch it in /tmp and use that:
            (
                sed -e '/qr'\''command\\s+\-\[\^p\]\\s'\''/ { ' -e 's|\[\^p\]|[^pvV]|' -e 's| \-p>| -p/-v/-V>|' -e '}' "${checkbashisms}" > "${temp}/checkbashisms.patched" && \
                chmod +x "${temp}/checkbashisms.patched"
            )
            checkbashisms="${temp}/checkbashisms.patched"
        else
            echo "checkbashisms workaround not needed, using vanilla script"
        fi

        # FIXME: if we add a third patch, clean this up
        # Check if checkbashisms supports `command -v`. If not, warn, or if on travis, patch it:
        cat > "${temp}/kill.sh" <<_EOF
#!/bin/sh
sleep 5 & spid="$!"
kill -HUP \$spid
_EOF

        if ! "${checkbashisms}" --posix "${temp}/kill.sh"; then
            echo "Disabling the kill -signal warning"
            echo "Creating a copy of the checkbashisms script, in ${temp}, to workaround this issue"
            # /usr/bin/ (or /usr/local/bin on OSX) isn't writable on Travis, so we have to patch it in /tmp and use that:
            (
                sed -e '/bashisms.*kill/d' "${checkbashisms}" >  "${temp}/checkbashisms.patched.kill" && \
                chmod +x "${temp}/checkbashisms.patched.kill"
            )
            checkbashisms="${temp}/checkbashisms.patched.kill"
        fi

        echo "Checking ${shellscript} for bashisms:"
        w_try "${checkbashisms}" --posix "${shellscript}"
    fi
}

# check formatting, (no trailing whitespace, no tabs)
test_formatting() {
    # check for trailing spaces
    # git diff misses some stuff? (2.18.0 / 2.22.0)
    #w_try git diff --check --exit-code "${shellscript}"
    if grep -n -r '[[:blank:]]$' "${shellscript}"; then
        w_die "${shellscript} contains trailing spaces, remove them."
    fi

    # check for tabs
    if grep -n "$(printf '\t')" "${shellscript}"; then
        w_die "${shellscript} contains tabs, please use spaces instead."
    fi

    # make sure `do` isn't on its own line:
    if grep -n -w -e '  do$' -e '^do$' "${shellscript}"; then
        w_die "Put 'do' on the same line as 'for/while'"
    fi

    # make sure `then` isn't on its own line:
    if grep -n -w -e '  then$' -e '^then$' "${shellscript}"; then
        w_die "Put 'then' on the same line as 'if'"
    fi
}

# tests using shellcheck
test_shellcheck() {
    if [ ! -x "$(command -v shellcheck 2>/dev/null)" ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then
        echo "On OSX and shellcheck not available, skipping."
        return
    else
        shellcheck="$(command -v shellcheck)"

        echo "======================== Begin shellcheck version info ==========================="
        "${shellcheck}" --version > /dev/null || w_die "shellcheck must be installed!"
        "${shellcheck}" --version
        echo "======================== End shellcheck version info ==========================="

        echo "Checking ${shellscript} with shellcheck:"
        w_try "${shellcheck}" -s sh "${shellscript}"
    fi
}

# tests for linkcheck
test_linkcheck() {
    # Check for uses of variables in w_download when w_linkcheck_ignore isn't set
    # Using w_download https://example.com/${file1} breaks src/linkcheck.sh
    # FIXME: technically '$' is valid in a URL, if there's actually a URL using it this will need a tweak
    if grep "^ *w_download " src/winetricks | grep -E "ftp|http" | grep -v "w_linkcheck_ignore=1" | sed "s/^ *//"  | tr -d "\\\\" | cut -d " " -f2 | grep "\\$"; then
        w_die "Do not use variables in these URLs, it breaks src/linkcheck.sh"
    else
        echo "linkcheck checks passed"
    fi
}

test_travis_yml() {
    if [ ! -x "$(command -v yq 2>/dev/null)" ]; then
        echo "warning: yq unavilable, skipping .travis.yml validation"
    else
        yq="$(command -v yq)"

        if [ -n "${yq}" ]; then
            echo "yq available, validating .travis.yml"
            yq . .travis.yml || true
        else
            echo "warning: yq not available, not validating .travis.yml"
        fi
    fi
}

###################################################################################################

# Test wrapper
main() {
    # Use git ls-files if available, this prevents 'finding' scripts that aren't checked into git.
    # E.g., if patching foo fails, then foo.orig would also be 'found'.
    # The find fallback is for non git users, e.g., distros packaging winetricks or end users
    # running shell-checks from a tarball download.
    if [ -d .git ] ; then
        files_to_check="$(git ls-files | xargs file | grep -e 'POSIX shell script' | cut -d : -f1)"
    else
        files_to_check="$(find . -type f -exec file {} \; | grep -e 'POSIX shell script' | cut -d : -f1)"
    fi

    # Run once tests:

    # really, this checks winetricks, to make sure it doesn't break linkcheck.sh:
    test_linkcheck

    # make sure yq (if present), can validate .travis_yml.
    # Available in brew, not ubuntu (without snap), but if .travis_yml is invalid,
    # travis won't run anyway. Only useful when running locally.
    test_travis_yml

    # Generic shellscript checks:
    for shellscript in ${files_to_check}; do
        test_formatting
        test_bashate
        test_checkbashisms
        test_shellcheck
    done
}

main

# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
