Add scripting and Makefile infrastructure to create virtual envs and be ran by CI tooling.
This commit is contained in:
139
scripts/python-lint.py
Executable file
139
scripts/python-lint.py
Executable file
@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Enforces Python coding standards via pep8, pyflakes and pylint
|
||||
|
||||
|
||||
Installation:
|
||||
pip install pep8 - style guide
|
||||
pip install pep257 - for docstrings
|
||||
pip install pyflakes - unused imports and variable declarations
|
||||
pip install plumbum - used for executing shell commands
|
||||
|
||||
This script can be called from the git pre-commit hook with a
|
||||
--git-precommit option
|
||||
"""
|
||||
|
||||
import os
|
||||
import pep257
|
||||
import re
|
||||
import sys
|
||||
from plumbum import local, cli, commands
|
||||
|
||||
pep8_options = [
|
||||
'--max-line-length=105'
|
||||
]
|
||||
|
||||
|
||||
def lint(to_lint):
|
||||
"""
|
||||
Run all linters against a list of files.
|
||||
|
||||
:param to_lint: a list of files to lint.
|
||||
|
||||
"""
|
||||
exit_code = 0
|
||||
for linter, options in (('pyflakes', []), ('pep8', pep8_options)):
|
||||
try:
|
||||
output = local[linter](*(options + to_lint))
|
||||
except commands.ProcessExecutionError as e:
|
||||
output = e.stdout
|
||||
|
||||
if output:
|
||||
exit_code = 1
|
||||
print "{0} Errors:".format(linter)
|
||||
print output
|
||||
|
||||
output = hacked_pep257(to_lint)
|
||||
if output:
|
||||
exit_code = 1
|
||||
print "Docstring Errors:".format(linter.upper())
|
||||
print output
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
def hacked_pep257(to_lint):
|
||||
"""
|
||||
Check for the presence of docstrings, but ignore some of the options
|
||||
"""
|
||||
def ignore(*args, **kwargs):
|
||||
pass
|
||||
|
||||
pep257.check_blank_before_after_class = ignore
|
||||
pep257.check_blank_after_last_paragraph = ignore
|
||||
pep257.check_blank_after_summary = ignore
|
||||
pep257.check_ends_with_period = ignore
|
||||
pep257.check_one_liners = ignore
|
||||
pep257.check_imperative_mood = ignore
|
||||
|
||||
original_check_return_type = pep257.check_return_type
|
||||
|
||||
def better_check_return_type(def_docstring, context, is_script):
|
||||
"""
|
||||
Ignore private methods
|
||||
"""
|
||||
def_name = context.split()[1]
|
||||
if def_name.startswith('_') and not def_name.endswith('__'):
|
||||
original_check_return_type(def_docstring, context, is_script)
|
||||
|
||||
pep257.check_return_type = better_check_return_type
|
||||
|
||||
errors = []
|
||||
for filename in to_lint:
|
||||
with open(filename) as f:
|
||||
source = f.read()
|
||||
if source:
|
||||
errors.extend(pep257.check_source(source, filename))
|
||||
return '\n'.join([str(error) for error in sorted(errors)])
|
||||
|
||||
|
||||
class Lint(cli.Application):
|
||||
"""
|
||||
Command line app for VmrunWrapper
|
||||
"""
|
||||
|
||||
DESCRIPTION = "Lints python with pep8, pep257, and pyflakes"
|
||||
|
||||
git = cli.Flag("--git-precommit", help="Lint only modified git files",
|
||||
default=False)
|
||||
|
||||
def main(self, *directories):
|
||||
"""
|
||||
The actual logic that runs the linters
|
||||
"""
|
||||
if not self.git and len(directories) == 0:
|
||||
print ("ERROR: At least one directory must be provided (or the "
|
||||
"--git-precommit flag must be passed.\n")
|
||||
self.help()
|
||||
return
|
||||
|
||||
if len(directories) > 0:
|
||||
find = local['find']
|
||||
files = []
|
||||
for directory in directories:
|
||||
real = os.path.expanduser(directory)
|
||||
if not os.path.exists(real):
|
||||
raise ValueError("{0} does not exist".format(directory))
|
||||
files.extend(find(real, '-not', '-name', '._*', '-name', '*.py').strip().split('\n'))
|
||||
else:
|
||||
status = local['git']('status', '--porcelain', '-uno')
|
||||
root = local['git']('rev-parse', '--show-toplevel').strip()
|
||||
|
||||
# get all modified or added python files
|
||||
modified = re.findall(r"^\s[AM]\s+(\S+\.py)$", status, re.MULTILINE)
|
||||
|
||||
# now just get the path part, which all should be relative to the
|
||||
# root
|
||||
files = [os.path.join(root, line.split(' ', 1)[-1].strip())
|
||||
for line in modified]
|
||||
|
||||
if len(files) > 0:
|
||||
print "Linting {0} python files.\n".format(len(files))
|
||||
lint(files)
|
||||
else:
|
||||
print "No python files found to lint.\n"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Lint.run()
|
Reference in New Issue
Block a user