#!/bin/bash
# shellcheck disable=SC1090,SC1091,SC1117,SC2181

# The MIT License (MIT)
#
# Copyright (c) 2013-2020 Pablo Acosta-Serafini
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# Find directory where script is
# from http://stackoverflow.com/questions/59895/
# can-a-bash-script-tell-what-directory-its-stored-in)
# BASH_SOURCE[0] is the pathname of the currently executing function or script
# -h True if file exists and is a symbolic link
# cd -P does not follow symbolic links
current_dir() {
    local sdir="$1"
    local udir=""
    # Resolve ${sdir} until the file is no longer a symlink
    while [ -h "${sdir}" ]; do
        udir="$(cd -P "$(dirname "${sdir}")" && pwd)"
        sdir="$(readlink "${sdir}")"
        # If ${sdir} was a relative symlink, we need to resolve it
        # relative to the path where the symlink file was located
        [[ "${sdir}" != /* ]] && sdir="${udir}/${sdir}"
    done
    udir="$(cd -P "$(dirname "${sdir}")" && pwd)"
    echo "${udir}"
}

strcat() {
  local IFS=""
  echo -n "$*"
}
# Options {{{
print=0
non_ascii_file_names=0
trailing_white_space=0
email=0
personal_repo=0
code_standard=0
pep257=0
cfg_fname="$(current_dir "${BASH_SOURCE[0]}")/repo-cfg.sh"
if [ -f "${cfg_fname}" ]; then
    source "$(current_dir "${BASH_SOURCE[0]}")/repo-cfg.sh"
else
    echo -e "\tConfiguration file not found, using defaults"
fi
pkg_dir=$(git rev-parse --show-toplevel)
if [ "${print}" == 1 ]; then
    echo -e "\tGit pre-commit setup"
    echo -e "\t\tprint=${print}"
    echo -e "\t\tnon_ascii_file_names=${non_ascii_file_names}"
    echo -e "\t\ttrailing_white_space=${trailing_white_space}"
    echo -e "\t\tpep257=${pep257}"
    echo -e "\t\temail=${email}"
    echo -e "\t\tpersonal_repo=${personal_repo}"
    echo -e "\t\tcode_standard=${code_standard}"
fi
# }}}
# Redirect output to stderr {{{ STDOUT redirection
exec 1>&2
# }}}
# Determine initial commit {{{
if git rev-parse --verify HEAD >/dev/null 2>&1; then
    #echo "pre-commit check against HEAD"
    against=HEAD
else
    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
# }}}
# Check for non-ASCII file names {{{
if [ "${non_ascii_file_names}" == 1 ]; then
    if [ "${print}" == 1 ]; then
        echo -e "\tChecking for non-ASCII file names"
    fi
    # Cross platform projects tend to avoid non-ASCII filenames; prevent
    # them from being added to the repository. We exploit the fact that the
    # printable range starts at the space character and ends with tilde.
    # If you want to allow non-ASCII filenames set this variable to true.
    allownonascii=$(git config --bool hooks.allownonascii)
    if [ "$allownonascii" != "true" ] &&
        # Note that the use of brackets around a tr range is ok here,
        # (it's even required, for portability to Solaris 10's
        # /usr/bin/tr), since the square bracket bytes happen to fall
        # in the designated range.
        test "$(\
            git diff --cached --name-only --diff-filter=A -z "${against}" | \
            LC_ALL=C tr -d '[ -~]\0' | \
            wc -c \
        )" != 0
    then
        msg=$(strcat \
            "Error: attempt to add a non-ASCII file name.\n" \
            "This can cause problems if you want to work with people on\n" \
            "other platforms. To be portable it is advisable to rename\n" \
            "the file. If you know what you are doing you can disable\n" \
            " this check using:\n" \
            "    git config hooks.allownonascii true\n" \
        )
        >&2 echo "${msg}"
        exit 1
    fi
fi
# }}}
# Check for trailing whitespace {{{
if [ "${trailing_white_space}" == 1 ]; then
    if [ "${print}" == 1 ]; then
        echo -e "\tChecking for trailing whitespace"
    fi
    # If there are whitespace errors, print the offending file names and fail.
    diff_output=$(git diff-index --check --cached $against --)
    if [ "${diff_output}" != "" ]; then
        exec git diff-index --check --cached $against --
        exit 1
    fi
fi
# }}}
# Check for Python PEP8 (code style) compliance {{{
if [ "${code_standard}" == 1 ]; then
    python_files=$(git diff --name-only --cached -- *.py)
    if [ "|${python_files[*]}|" != "||" ]; then
        config_fname="${pkg_dir}/.pylintrc"
        if [ ! -f "${config_fname}" ]; then
            >&2 echo -e "Pylint config file ${config_fname} not found"
            exit 1
        fi
        if [ "${print}" == 1 ]; then
            echo -e "\tValidating Python PEP8 compliance with Pylint"
        fi
        python_files=$(git diff --name-only --cached -- *.py)
        cmd=$(strcat \
            "from __future__ import print_function;" \
            "import os;" \
            "plugin_dir = os.path.join(os.environ.get('REPO_DIR', ''), 'pylint_plugins');" \
            "print(plugin_dir if os.path.isdir(plugin_dir) else '')" \
        )
        PYLINT_PLUGINS_DIR=$(python -c "${cmd}")
        export PYLINT_PLUGINS_DIR
        cmd=$(strcat \
            "from __future__ import print_function;" \
            "import glob;" \
            "import os;" \
            "sdir = os.environ.get('PYLINT_PLUGINS_DIR', '');" \
            "print(" \
                "','.join(" \
                    "[" \
                        "os.path.basename(fname).replace('.py', '')" \
                        "for fname in glob.glob(os.path.join(sdir, '*.py'))" \
                    "]"\
                ")" \
                "if sdir else ''" \
            ")" \
        )
        PYLINT_PLUGINS_LIST=$(python -c "${cmd}")
        export PYLINT_PLUGINS_LIST
        cmd=$(strcat \
            "from __future__ import print_function;" \
            "import os;" \
            "svar=os.environ.get('PYLINT_PLUGINS_LIST', '');" \
            "print('--load-plugins='+svar if svar else '')" \
        )
        PYLINT_CLI_APPEND=$(python -c "${cmd}")
        export PYLINT_CLI_APPEND
        pep8_output=""
        # shellcheck disable=SC2068
        for file in ${python_files[@]}; do
            if [ "${file}" != "" ]; then
                tfile="$(mktemp)"
                file_output=""
                PYTHONPATH="${PYTHONPATH}:${PYLINT_PLUGINS_DIR}" \
                    pylint \
                        --rcfile="${config_fname}" \
                        "${PYLINT_CLI_APPEND}" \
                        "${file}" &> "${tfile}"
                if [ "$?" != 0 ]; then
                    file_output=$(cat "${tfile}")
                    rm -rf "${tfile}"
                fi
                if [ "${file_output}" != "" ]; then
                    pep8_output=${pep8_output}"\n"${file_output}
                fi
            fi
        done
        if [ "${pep8_output}" != "" ]; then
            >&2 echo -e "${pep8_output}"
            exit 1
        fi
        #if [ "${python_files}" != "" ]; then
        #   echo -e "All Python files are PEP8 compliant"
        #fi
    fi
fi
# }}}
# Check for Python PEP257 (docstring style) compliance {{{
if [ "${pep257}" == 1 ]; then
    python_files=$(git diff --name-only --cached -- *.py)
    if [ "|${python_files[*]}|" != "||" ]; then
        config_fname="${pkg_dir}/.pydocstyle"
        if [ ! -f "${config_fname}" ]; then
            >&2 echo -e "Pydocstyle config file ${config_fname} not found"
            exit 1
        fi
        if [ "${print}" == 1 ]; then
            echo -e "\tValidating Python PEP257 compliance with pydocstyle"
        fi
        pep257_output=""
        # shellcheck disable=SC2068
        for file in ${python_files[@]}; do
            if [ "${file}" != "" ]; then
                tfile="$(mktemp)"
                file_output=""
                pydocstyle --config="${config_fname}" "${file}" &> "${tfile}"
                if [ "$?" != 0 ]; then
                    file_output=$(cat "${tfile}")
                    rm -rf "${tfile}"
                fi
                if [ "${file_output}" != "" ]; then
                    pep257_output=${pep257_output}"\n"${file_output}
                fi
            fi
        done
        if [ "${pep257_output}" != "" ]; then
            >&2 echo -e "${pep257_output}"
            exit 1
        fi
        #if [ "${python_files}" != "" ]; then
        #   echo -e "All Python files are PEP257 compliant"
        #fi
    fi
fi
# }}}
# Check for congruence between author file and Git contact information {{{
if [ "${email}" == 1 ]; then
    fname=${pkg_dir}/AUTHORS.rst
    use_author_file=1
    if [ ! -f "${fname}" ]; then
        use_author_file=0
        >&2 echo -e "${fname} not found, using first commit information"
        names="$(\
            git \
            --no-pager \
            show \
            -s \
            --pretty=format:'%an <%ae>' \
            "$(git rev-list --max-parents=0 HEAD)" \
        )"
    else
        names=$(\
            grep -E '^\s*.*\s+<.*>\s*$' "${fname}" | \
            sed -r -e 's/(\s*\*\s+)(.*)/\2/g' | \
            sed -r -e 's/\s+/ /g' \
        )
    fi
    names=${names//[$'\t\r\n']}
    git_author="$(git config user.name) <$(git config user.email)>"
    sep=">"
    found=0
    while [ "${names}" != "${names#*${sep}}" ]; do
        name="${names%%${sep}*}${sep}"
        name="${name## }"
        echo "|${name}|"
        if [ "${name}" == "${git_author}" ]; then
            found=1
            break
        fi
        names="${names#*${sep}}"
    done
    if [ "${found}" == 0 ]; then
        if [ "${use_author_file}" == 1 ]; then
            >&2 echo -e "Error: git author ${git_author} not found in ${fname}"
        else
            >&2 echo -e "Error: git author ${git_author} does not match first commit author"
        fi
        exit 1
    fi
fi
# }}}
