From f21d01ab68e9ce01bd1dcb21b978123cfcc44967 Mon Sep 17 00:00:00 2001 From: Jiri Kucera Date: Wed, 31 Jul 2019 00:52:26 +0200 Subject: [PATCH] Add pylint wrapper Add run_pylint.py that probe working directory for python source files and pass them to pylint. This is needed because pylint expects module or package, so passing a directory with no __init__.py in it makes pylint angry. With this change, LSR_PYLINT_DIRS is no longer needed. Additionally, we no longer need: - init-hook in pylintrc - [pylintrc] section in tox.ini, because the configuration from this section is now the part of pylintrc - [pycodestyle] section in tox.ini, because pycodestyle linter is not used --- .travis/config.sh | 1 - pylintrc | 7 +-- run_pylint.py | 144 ++++++++++++++++++++++++++++++++++++++++++++++ tox.ini | 18 +----- 4 files changed, 149 insertions(+), 21 deletions(-) create mode 100644 run_pylint.py diff --git a/.travis/config.sh b/.travis/config.sh index f43fd10..2ca77d3 100644 --- a/.travis/config.sh +++ b/.travis/config.sh @@ -1,4 +1,3 @@ -export LSR_PYLINT_DIRS='library/network_connections.py module_utils/network_lsr tests/unit/test_network_connections.py' export LSR_MOLECULE_DEPS='-rmolecule_requirements.txt' case "x${TRAVIS_PYTHON_VERSION}" in diff --git a/pylintrc b/pylintrc index 2f07798..1cf76db 100644 --- a/pylintrc +++ b/pylintrc @@ -16,8 +16,7 @@ ignore-patterns= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). -init-hook="from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.dirname(find_pylintrc()) + '/library'); sys.path.append(os.path.dirname(find_pylintrc()) + '/module_utils'); sys.path.append(os.path.dirname(find_pylintrc()) + '/tests')" - +#init-hook= # Use multiple processes to speed up Pylint. jobs=1 @@ -56,7 +55,7 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable= +disable=wrong-import-position #disable=print-statement, # parameter-unpacking, # unpacking-in-except, @@ -246,7 +245,7 @@ indent-after-paren=4 indent-string=' ' # Maximum number of characters on a single line. -max-line-length=100 +max-line-length=88 # Maximum number of lines in a module max-module-lines=1000 diff --git a/run_pylint.py b/run_pylint.py new file mode 100644 index 0000000..5bc1254 --- /dev/null +++ b/run_pylint.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +# Probe directory tree for python files and pass them to pylint +# Copyright (C) 2019 Red Hat, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +""" +Usage: python run_pylint.py ARGUMENTS + +Run pylint with ARGUMENTS followed by the list of python files contained in the +working directory and its subdirectories. As a python file is recognized a +file that match the regular expression .*\\.py[iw]?$. Files and directories +that match the regular expression ^\\..* are skipped. Symbolic links are also +skipped. + +There are several cases when argument from ARGUMENTS is not passed to pylint +but it is handled by run_pylint.py instead: + + 1. if -h or --help is contained in ARGUMENTS, this help screen is printed to + the standard output and run_pylint.py exits with 0; + 2. if --include followed by a PATTERN is contained in ARGUMENTS, the PATTERN + is used to recognize whether the file is a python file or not, instead of + default .*\\.py[iw]?$; + 3. if --exclude followed by a PATTERN is contained in ARGUMENTS, the PATTERN + is used to recognize whether the file or directory should be skipped (i.e. + instead of default ^\\..*, the PATTERN is used). + +Exclusion takes a priority over inclusion, i.e. if a file or directory can be +both included and excluded, it is excluded. +""" + +import os +import re +import sys + +from colorama import Fore +from pylint.lint import Run + + +def blue(s): + """ + Return string `s` colorized to blue. + """ + + return "%s%s%s" % (Fore.BLUE, s, Fore.RESET) + + +def print_line(s): + """ + Write `s` followed by the line feed character to the standard output. + """ + + sys.stdout.write("%s\n" % s) + + +def probe_args(): + """ + Analyze the command line arguments and return a tuple containing a list of + pylint arguments, pattern string to recognize files to be included, and + pattern string to recognize files and directories to be skipped. + """ + + args = [] + include_pattern = r".*\.py[iw]?$" + exclude_pattern = r"^\..*" + i, nargs = 1, len(sys.argv) + while i < nargs: + arg = sys.argv[i] + if arg == "--include": + i += 1 + assert i < nargs, "--include: missing PATTERN" + include_pattern = sys.argv[i] + elif arg == "--exclude": + i += 1 + assert i < nargs, "--exclude: missing PATTERN" + exclude_pattern = sys.argv[i] + else: + args.append(arg) + i += 1 + return args, include_pattern, exclude_pattern + + +def probe_dir(path, include_re, exclude_re): + """ + Recursively go through directory structure starting at `path`, collect + files that match `include_re`, skip files and directories that are either + symbolic links or match `exclude_re`. Return the list of collected files. + """ + + files = [] + for direntry in os.listdir(path): + fullpath = os.path.join(path, direntry) + if os.path.islink(fullpath) or exclude_re.match(direntry): + continue + elif os.path.isdir(fullpath): + files.extend(probe_dir(fullpath, include_re, exclude_re)) + elif os.path.isfile(fullpath) and include_re.match(direntry): + files.append(fullpath) + return files + + +def show_files(files): + """ + Print `files` to the standard output, one item per line, in a blue color. + """ + + if not files: + return + print_line(blue("%s: files to be checked:" % sys.argv[0])) + for f in files: + print_line(blue(" %s" % f)) + + +def main(): + """ + Script entry point. Return exit code. + """ + + args, include_pattern, exclude_pattern = probe_args() + if "-h" in args or "--help" in args: + sys.stdout.write(__doc__) + return 0 + files = probe_dir( + os.getcwd(), re.compile(include_pattern), re.compile(exclude_pattern) + ) + show_files(files) + args.extend(files) + sys.argv[0] = "pylint" + return Run(args, None, False).linter.msg_status + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tox.ini b/tox.ini index 2079465..aca3582 100644 --- a/tox.ini +++ b/tox.ini @@ -106,23 +106,16 @@ deps = commands = black --check --diff --include "^[^.].*\.py$" --exclude "/(\.[^.].*|tests/roles)/" . -# Custom role settings may set LSR_PYLINT_DIRS environment variable if there is -# a reason to have other than default directories (library/ module_utils/ and -# tests/). For example, network role should have this set like -# -# LSR_PYLINT_DIRS="library/network_connections.py module_utils/network_lsr tests/unit/test_network_connections.py" -# [testenv:pylint] basepython = python2.7 setenv = {[base]setenv} deps = + colorama pylint>=1.8.4 ansible commands = - pylint --errors-only \ - {posargs} \ - {env:LSR_PYLINT_DIRS:library module_utils tests} + {envpython} ./run_pylint.py --errors-only {posargs} [testenv:flake8] basepython = python2.7 @@ -223,13 +216,6 @@ show_source = true max-line-length = 88 ignore = E402,W503 -[pylint] -max-line-length = 88 -disable = wrong-import-position - -[pycodestyle] -max-line-length = 88 - [travis] python = 2.6: py26,extra