log_read: create/Fix docstrings
[pyi2ncommon] / pycheck.sh
CommitLineData
1ab5214e
CH
1#!/bin/bash
2set -u
3
4# pycheck.sh
5# check a python source code file using
6# pylint, pycodestyle (former pep8) and pydocstyle
7
8# debug mode:
9#set -x
10
11readonly base_dir="$(realpath -m $(dirname $0))"
12readonly src_dir=$base_dir/src
13readonly test_dir=$base_dir/test
14
15readonly pylint_init_hook="
16import sys;
17sys.path.insert(0, 'base_dir');
18"
19readonly codestyle_ignores="E501,W503,E226,E265"
20readonly docstyle_ignores="D212,D205,D400,D401,D203,D105,D301,D302"
21
22usage_print_msg ()
23{
24 printf "usage: %s --all { FILENAME | --branch NAME }\n" "$0"
25 printf "\n"
26 printf "\twhere\n"
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"
32 printf "\n"
33}
34
35usage () {
36 local out=1
37 case "${1:-stdout}" in
38 2|err|stderr) out=2 ;;
39 esac
40 usage_print_msg >&"${out}";
41}
42
43# check params
44pylint_Wall=no
45lookup_mode=file
46target_name=""
47while : ; do
48 arg="${1:-}"
49 case "${arg}" in
50 "") break ;;
51 --help)
52 usage
53 exit 0
54 ;;
55 --all)
56 echo "Enabling all pylint warnings"
57 pylint_Wall=yes
58 ;;
59 --branch)
60 shift
61 arg="${1:-}"
62 if [ -z "${arg}" ]; then
63 printf "ERROR: --branch specified but no name given\n\n" >&2
64 usage err
65 exit 1
66 fi
67 target_name="${arg}"
68 lookup_mode=branch
69 ;;
70 *)
71 if [ -z "${target_name}" ]; then
72 target_name="${arg%%.py}"
73 else
74 printf "ERROR: filename ${target_name} already specified\n" >&2
75 printf "ERROR: please specify a single file name to check\n\n" >&2
76 usage err
77 exit 1
78 fi
79 ;;
80 esac
81 shift
82done
83
84if [ -z "${target_name}" ]; then
85 printf "ERROR: no file name or branch specified\n\n" >&2
86 usage err
87 exit 1
88fi
89
90# check for presence of pylint-3
91pylint-3 --version
92if [ $? = 0 ]; then
93 pylint_bin=pylint-3
94else
95 pylint --version
96 pylint_bin=pylint
97fi
98
99function has_errors {
100 local found_errors=no
101 # append extra rules passed to the function
102 codestyle_all_ignores="$codestyle_ignores,${2:-}"
103
104 # run pylint
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)')
109 fi
110 echo "pylint:"
111 if ! ${pylint_bin} ${pylint_flags[@]} --reports=no --init-hook="$pylint_init_hook" "$1"; then
112 found_errors=yes
113 fi
114 echo
115
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
119
120 echo "pycodestyle|pep8:"
121 if ! $style_checker "$1" --ignore="$codestyle_all_ignores"; then
122 found_errors=yes
123 fi
124 echo
125
126 # run pydocstyle
127 echo "pydocstyle:"
128 if ! pydocstyle "$1" --ignore=$docstyle_ignores; then
129 found_errors=yes
130 fi
131 echo
132
133 [ "${found_errors}" = yes ]
134}
135
136declare -a found=()
137
138valid_branch ()
139{
140 local needle="${1:-master}"
141 git rev-parse --verify "${needle}" &>/dev/null
142}
143
144check_to_branch ()
145{
146 local br="${1:-master}"
147
148 if ! valid_branch ${br}; then
149 printf "ERROR: branch “%s” not known to Git\n\n" "${br}"
150 usage err
151 exit 1
152 fi
153
154 local allfiles=( $(git diff --name-only --diff-filter=d "${br}") )
155 local pyfiles=( )
156 for f in ${allfiles[@]}; do
157 if [[ "$f" =~ \.py$ ]]; then pyfiles+=( "$f" ); fi
158 done
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"
165 done
166 printf "\n"
167 usage err
168 exit 1
169 fi
170
171 printf "checking %d files for tiny and understandable mistakes\n" ${#pyfiles[@]}
172 local i=0
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" )
177 else
178 found+=( "${py}→GOOD" )
179 fi
180 done
181}
182
183case "${lookup_mode}" in
184 file)
185 files=$(find $base_dir \( -path "${src_dir}/*" -name "${target_name}.py" \) \
186 -or \( -path "${test_dir}/*" -name "${target_name}.py" \) )
187
188 for f in $files; do
189 echo "Found file ${f}"
190 if has_errors "${f}"; then
191 found+=( "${f}→BAD" )
192 else
193 found+=( "${f}→GOOD" )
194 fi
195 done
196
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!"
200 echo
201 usage stderr
202 exit 2
203 fi
204 ;;
205 branch)
206 check_to_branch "${target_name}"
207 ;;
208 *)
209 # can’t happen
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"
213 exit 1
214esac
215
216declare -r wd=$(tput cols)
217
218printf "\n"
219printf "·%.0s" $(seq 1 ${wd})
220printf "\n\n"
221printf "$0 completed sucessfully\n"
222i=0
223good=0
224for result in ${found[@]} ; do
225 (( ++i ))
226 old_IFS="${IFS}"
227 IFS=→
228 read file verdict <<<"${result}"
229 IFS="${old_IFS}"
230 if [ "${verdict}" = GOOD ]; then
231 (( ++good ))
232 fi
233 printf "\t%d : %s\t→ %s\n" "$i" "${file}" "${verdict}"
234done
235printf "\n"
236printf "summary: %d files inspected, %d tested good, %d bad\n\n" \
237 $i ${good} $(( i - good ))
238
239printf "·%.0s" $(seq 1 ${wd})
240printf "\n\n"
241
242if [ ${good} -ne $i ]; then
243 exit 1
244fi
245