Python has a number of linters and static analysis tools available, some of them are meta-tools that run multiple different kinds of checks over your code at once. Unfortunately the check that the meta-tools always seem to leave out is static type checking, using a tool like mypy. And so I want to run two linters on my Python code, mypy
for type-checking and flake8
for everything else.
Kakoune has only one lintcmd
option, which contains a shell-command that will be given the path to a file to check. That means we can run multiple linters by using a shell function to run both of them:
set-option window lintcmd %{ runlints() {
mypy --show-column-numbers "$1"
flake8 --max-complexity 10 "$1"
}; runlints }
The lintcmd
will be given to the shell with a filename added on; this causes the runlints
function to be defined, and then immediately called with the filename. Inside the function, each linter is called with $1
, the filename that was passed to the runlints
function.
But that’s not enough. I use my Kakoune config on different systems, and they don’t always have the same tools available. I’d like to gracefully ignore tools that don’t exist, perhaps with a warning if there’s no linters available at all.
This is what I came up with:
hook global WinSetOption filetype=python %{
evaluate-commands %sh{
# Clear existing arguments
# so we can use it as an array.
set --
# Add these linters to the array, if we have them.
for linter in \
"mypy --strict --show-column-numbers" \
"flake8 --max-complexity 10"
do
# The first whitespace-delimited word of the linter
tool_name=${linter%% *}
# If we have that tool...
if command -V "$tool_name" >/dev/null 2>/dev/null; then
# ...add it to the list
set -- "$@" "$linter"
fi
done
# If we found any linters...
if [ $# -gt 0 ]; then
# ...add them all to the lintcmd option,
# using a shell function to run each linter sequentially
# on the given file.
echo "set-option window lintcmd %{ l() {"
printf '%s "$1"\n' "$@"
echo "}; l }"
# ...and automatically lint the buffer after writing it.
echo hook window -group python-auto-lint BufWritePost '.*' lint-buffer
echo hook window WinSetOption filetype=.* %{ rmhooks window python-auto-lint; unset-option window lintcmd }
else
# Otherwise, put a warning in the *debug* buffer
echo "Could not find any supported linters" 1>&2
fi
}
}