Bring back source check script
authorChristian Herdtweck <christian.herdtweck@intra2net.com>
Thu, 8 Aug 2019 09:53:48 +0000 (11:53 +0200)
committerChristian Herdtweck <christian.herdtweck@intra2net.com>
Thu, 8 Aug 2019 09:53:48 +0000 (11:53 +0200)
Old script was deleted a few commits ago for version 1.5
(commit 81f7f6c7a16bc5e63228996cedf8ac83f45f3717).
This is a modified version from i2n avocado source.

pycheck.sh [new file with mode: 0755]

diff --git a/pycheck.sh b/pycheck.sh
new file mode 100755 (executable)
index 0000000..6ac2594
--- /dev/null
@@ -0,0 +1,245 @@
+#!/bin/bash
+set -u
+
+# pycheck.sh
+# check a python source code file using
+# pylint, pycodestyle (former pep8) and pydocstyle
+
+# debug mode:
+#set -x
+
+readonly base_dir="$(realpath -m $(dirname $0))"
+readonly src_dir=$base_dir/src
+readonly test_dir=$base_dir/test
+
+readonly pylint_init_hook="
+import sys;
+sys.path.insert(0, 'base_dir');
+"
+readonly codestyle_ignores="E501,W503,E226,E265"
+readonly docstyle_ignores="D212,D205,D400,D401,D203,D105,D301,D302"
+
+usage_print_msg ()
+{
+    printf "usage: %s --all { FILENAME | --branch NAME }\n" "$0"
+    printf "\n"
+    printf "\twhere\n"
+    printf "\t\tFILENAME       file to check (expects the basename\n"
+    printf "\t\t               without suffix)\n"
+    printf "\t\t--all          enable all warnings\n"
+    printf "\t\t--branch NAME  a git branch to check changed files\n"
+    printf "\t\t               against\n"
+    printf "\n"
+}
+
+usage () {
+    local out=1
+    case "${1:-stdout}" in
+        2|err|stderr) out=2 ;;
+    esac
+    usage_print_msg >&"${out}";
+}
+
+# check params
+pylint_Wall=no
+lookup_mode=file
+target_name=""
+while : ; do
+    arg="${1:-}"
+    case "${arg}" in
+        "") break ;;
+        --help)
+            usage
+            exit 0
+            ;;
+        --all)
+            echo "Enabling all pylint warnings"
+            pylint_Wall=yes
+            ;;
+        --branch)
+            shift
+            arg="${1:-}"
+            if [ -z "${arg}" ]; then
+                printf "ERROR: --branch specified but no name given\n\n" >&2
+                usage err
+                exit 1
+            fi
+            target_name="${arg}"
+            lookup_mode=branch
+            ;;
+        *)
+            if [ -z "${target_name}" ]; then
+                target_name="${arg%%.py}"
+            else
+                printf "ERROR: filename ${target_name} already specified\n" >&2
+                printf "ERROR: please specify a single file name to check\n\n" >&2
+                usage err
+                exit 1
+            fi
+            ;;
+    esac
+    shift
+done
+
+if [ -z "${target_name}" ]; then
+    printf "ERROR: no file name or branch specified\n\n" >&2
+    usage err
+    exit 1
+fi
+
+# check for presence of pylint-3
+pylint-3 --version
+if [ $? = 0 ]; then
+    pylint_bin=pylint-3
+else
+    pylint --version
+    pylint_bin=pylint
+fi
+
+function has_errors {
+    local found_errors=no
+    # append extra rules passed to the function
+    codestyle_all_ignores="$codestyle_ignores,${2:-}"
+
+    # run pylint
+    local pylint_flags=(-E)
+    if [ "${pylint_Wall}" = yes ]; then
+        pylint_flags=(--disable=logging-format-interpolation \
+                      --variable-rgx='(?:[[a-z_][a-z0-9_]{2,30}$|vm)')
+    fi
+    echo "pylint:"
+    if ! ${pylint_bin} ${pylint_flags[@]} --reports=no --init-hook="$pylint_init_hook" "$1"; then
+        found_errors=yes
+    fi
+    echo
+
+    # run pycodestyle, former pep8 (or fall back to pep8 if not available)
+    style_checker="pycodestyle"
+    command -v $style_checker >/dev/null 2>&1 || style_checker=pep8
+
+    echo "pycodestyle|pep8:"
+    if ! $style_checker "$1" --ignore="$codestyle_all_ignores"; then
+        found_errors=yes
+    fi
+    echo
+
+    # run pydocstyle
+    echo "pydocstyle:"
+    if ! pydocstyle "$1" --ignore=$docstyle_ignores; then
+        found_errors=yes
+    fi
+    echo
+
+    [ "${found_errors}" = yes ]
+}
+
+declare -a found=()
+
+valid_branch ()
+{
+    local needle="${1:-master}"
+    git rev-parse --verify "${needle}" &>/dev/null
+}
+
+check_to_branch ()
+{
+    local br="${1:-master}"
+
+    if ! valid_branch ${br}; then
+        printf "ERROR: branch “%s” not known to Git\n\n" "${br}"
+        usage err
+        exit 1
+    fi
+
+    local allfiles=( $(git diff --name-only --diff-filter=d "${br}") )
+    local pyfiles=( )
+    for f in ${allfiles[@]}; do
+        if [[ "$f" =~ \.py$ ]]; then pyfiles+=( "$f" ); fi
+    done
+    if [ "${#pyfiles[@]}" -eq 0 ]; then
+        printf "ERROR: no Python files (*.py) changed between HEAD and\n"
+        printf "ERROR: asked branch “%s”\n\n" "${br}"
+        printf "ERROR: %d files changed:\n" ${#allfiles[@]}
+        for f in ${allfiles[@]}; do
+            printf "\t\t× %s\n" "$f"
+        done
+        printf "\n"
+        usage err
+        exit 1
+    fi
+
+    printf "checking %d files for tiny and understandable mistakes\n" ${#pyfiles[@]}
+    local i=0
+    for py in ${pyfiles[@]}; do
+        printf "[%d/%d] checking %s\n" $(( ++i )) ${#pyfiles[@]} "${py}"
+        if has_errors "${py}"; then
+            found+=( "${py}→BAD" )
+        else
+            found+=( "${py}→GOOD" )
+        fi
+    done
+}
+
+case "${lookup_mode}" in
+    file)
+        files=$(find $base_dir \( -path "${src_dir}/*" -name "${target_name}.py" \) \
+                          -or \( -path "${test_dir}/*" -name "${target_name}.py" \) )
+
+        for f in $files; do
+            echo "Found file ${f}"
+            if has_errors "${f}"; then
+                found+=( "${f}→BAD" )
+            else
+                found+=( "${f}→GOOD" )
+            fi
+        done
+
+        # error if not found as utility either
+        if [ "${#found[@]}" -eq 0 ]; then
+            echo "Could not find ${target_name} in src/test dirs!"
+            echo
+            usage stderr
+            exit 2
+        fi
+        ;;
+    branch)
+        check_to_branch "${target_name}"
+        ;;
+    *)
+        # can’t happen
+        printf "ERROR: internal error\n"
+        printf "ERROR: please run “git blame \"%s\"” to determine who" "$0"
+        printf "ERROR: is responsible for this mess\n\n"
+        exit 1
+esac
+
+declare -r wd=$(tput cols)
+
+printf "\n"
+printf "·%.0s" $(seq 1 ${wd})
+printf "\n\n"
+printf "$0 completed sucessfully\n"
+i=0
+good=0
+for result in ${found[@]} ; do
+    (( ++i ))
+    old_IFS="${IFS}"
+    IFS=→
+    read file verdict <<<"${result}"
+    IFS="${old_IFS}"
+    if [ "${verdict}" = GOOD ]; then
+        (( ++good ))
+    fi
+    printf "\t%d : %s\t→ %s\n" "$i" "${file}" "${verdict}"
+done
+printf "\n"
+printf "summary: %d files inspected, %d tested good, %d bad\n\n" \
+    $i ${good} $(( i - good ))
+
+printf "·%.0s" $(seq 1 ${wd})
+printf "\n\n"
+
+if [ ${good} -ne $i ]; then
+    exit 1
+fi
+