5 # check a python source code file using
6 # pylint, pycodestyle (former pep8) and pydocstyle
11 readonly base_dir="$(realpath -m $(dirname $0))"
12 readonly src_dir=$base_dir/src
13 readonly test_dir=$base_dir/test
15 readonly pylint_init_hook="
17 sys.path.insert(0, 'base_dir');
19 readonly codestyle_ignores="E501,W503,E226,E265"
20 readonly docstyle_ignores="D212,D205,D400,D401,D203,D105,D301,D302"
24 printf "usage: %s --all { FILENAME | --branch NAME }\n" "$0"
27 printf "\t\tFILENAME file to check (expects the basename\n"
28 printf "\t\t without suffix)\n"
29 printf "\t\t--all enable all warnings\n"
30 printf "\t\t--branch NAME a git branch to check changed files\n"
31 printf "\t\t against\n"
37 case "${1:-stdout}" in
38 2|err|stderr) out=2 ;;
40 usage_print_msg >&"${out}";
56 echo "Enabling all pylint warnings"
62 if [ -z "${arg}" ]; then
63 printf "ERROR: --branch specified but no name given\n\n" >&2
71 if [ -z "${target_name}" ]; then
72 target_name="${arg%%.py}"
74 printf "ERROR: filename ${target_name} already specified\n" >&2
75 printf "ERROR: please specify a single file name to check\n\n" >&2
84 if [ -z "${target_name}" ]; then
85 printf "ERROR: no file name or branch specified\n\n" >&2
90 # check for presence of pylint-3
100 local found_errors=no
101 # append extra rules passed to the function
102 codestyle_all_ignores="$codestyle_ignores,${2:-}"
105 local pylint_flags=(-E)
106 if [ "${pylint_Wall}" = yes ]; then
107 pylint_flags=(--disable=logging-format-interpolation \
108 --variable-rgx='(?:[[a-z_][a-z0-9_]{2,30}$|vm)')
111 if ! ${pylint_bin} ${pylint_flags[@]} --reports=no --init-hook="$pylint_init_hook" "$1"; then
116 # run pycodestyle, former pep8 (or fall back to pep8 if not available)
117 style_checker="pycodestyle"
118 command -v $style_checker >/dev/null 2>&1 || style_checker=pep8
120 echo "pycodestyle|pep8:"
121 if ! $style_checker "$1" --ignore="$codestyle_all_ignores"; then
128 if ! pydocstyle "$1" --ignore=$docstyle_ignores; then
133 [ "${found_errors}" = yes ]
140 local needle="${1:-master}"
141 git rev-parse --verify "${needle}" &>/dev/null
146 local br="${1:-master}"
148 if ! valid_branch ${br}; then
149 printf "ERROR: branch “%s” not known to Git\n\n" "${br}"
154 local allfiles=( $(git diff --name-only --diff-filter=d "${br}") )
156 for f in ${allfiles[@]}; do
157 if [[ "$f" =~ \.py$ ]]; then pyfiles+=( "$f" ); fi
159 if [ "${#pyfiles[@]}" -eq 0 ]; then
160 printf "ERROR: no Python files (*.py) changed between HEAD and\n"
161 printf "ERROR: asked branch “%s”\n\n" "${br}"
162 printf "ERROR: %d files changed:\n" ${#allfiles[@]}
163 for f in ${allfiles[@]}; do
164 printf "\t\t× %s\n" "$f"
171 printf "checking %d files for tiny and understandable mistakes\n" ${#pyfiles[@]}
173 for py in ${pyfiles[@]}; do
174 printf "[%d/%d] checking %s\n" $(( ++i )) ${#pyfiles[@]} "${py}"
175 if has_errors "${py}"; then
176 found+=( "${py}→BAD" )
178 found+=( "${py}→GOOD" )
183 case "${lookup_mode}" in
185 files=$(find $base_dir \( -path "${src_dir}/*" -name "${target_name}.py" \) \
186 -or \( -path "${test_dir}/*" -name "${target_name}.py" \) )
189 echo "Found file ${f}"
190 if has_errors "${f}"; then
191 found+=( "${f}→BAD" )
193 found+=( "${f}→GOOD" )
197 # error if not found as utility either
198 if [ "${#found[@]}" -eq 0 ]; then
199 echo "Could not find ${target_name} in src/test dirs!"
206 check_to_branch "${target_name}"
210 printf "ERROR: internal error\n"
211 printf "ERROR: please run “git blame \"%s\"” to determine who" "$0"
212 printf "ERROR: is responsible for this mess\n\n"
216 declare -r wd=$(tput cols)
219 printf "·%.0s" $(seq 1 ${wd})
221 printf "$0 completed sucessfully\n"
224 for result in ${found[@]} ; do
228 read file verdict <<<"${result}"
230 if [ "${verdict}" = GOOD ]; then
233 printf "\t%d : %s\t→ %s\n" "$i" "${file}" "${verdict}"
236 printf "summary: %d files inspected, %d tested good, %d bad\n\n" \
237 $i ${good} $(( i - good ))
239 printf "·%.0s" $(seq 1 ${wd})
242 if [ ${good} -ne $i ]; then