140 lines
4.0 KiB
Python
Executable File

#!/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()