| #!/usr/bin/env python |
| |
| # Copyright (c) 2009 Giampaolo Rodola'. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """ |
| This gets executed on 'git commit' and rejects the commit in case the |
| submitted code does not pass validation. Validation is run only against |
| the *.py files which were modified in the commit. Checks: |
| |
| - assert no space at EOLs |
| - assert not pdb.set_trace in code |
| - assert no bare except clause ("except:") in code |
| - assert "flake8" returns no warnings |
| |
| Install this with "make install-git-hooks". |
| """ |
| |
| from __future__ import print_function |
| import os |
| import subprocess |
| import sys |
| |
| |
| def term_supports_colors(): |
| try: |
| import curses |
| assert sys.stderr.isatty() |
| curses.setupterm() |
| assert curses.tigetnum("colors") > 0 |
| except Exception: |
| return False |
| else: |
| return True |
| |
| |
| def hilite(s, ok=True, bold=False): |
| """Return an highlighted version of 'string'.""" |
| if not term_supports_colors(): |
| return s |
| attr = [] |
| if ok is None: # no color |
| pass |
| elif ok: # green |
| attr.append('32') |
| else: # red |
| attr.append('31') |
| if bold: |
| attr.append('1') |
| return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s) |
| |
| |
| def exit(msg): |
| msg = hilite(msg, ok=False) |
| print(msg, file=sys.stderr) |
| sys.exit(1) |
| |
| |
| def sh(cmd): |
| """run cmd in a subprocess and return its output. |
| raises RuntimeError on error. |
| """ |
| p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, universal_newlines=True) |
| stdout, stderr = p.communicate() |
| if p.returncode != 0: |
| raise RuntimeError(stderr) |
| if stderr: |
| print(stderr, file=sys.stderr) |
| if stdout.endswith('\n'): |
| stdout = stdout[:-1] |
| return stdout |
| |
| |
| def main(): |
| out = sh("git diff --cached --name-only") |
| py_files = [x for x in out.split(b'\n') if x.endswith(b'.py') and |
| os.path.exists(x)] |
| |
| lineno = 0 |
| for path in py_files: |
| with open(path) as f: |
| for line in f: |
| lineno += 1 |
| # space at end of line |
| if line.endswith(' '): |
| print("%s:%s %r" % (path, lineno, line)) |
| return exit( |
| "commit aborted: space at end of line") |
| line = line.rstrip() |
| # pdb |
| if "pdb.set_trace" in line: |
| print("%s:%s %s" % (path, lineno, line)) |
| return exit( |
| "commit aborted: you forgot a pdb in your python code") |
| # bare except clause |
| if "except:" in line and not line.endswith("# NOQA"): |
| print("%s:%s %s" % (path, lineno, line)) |
| return exit("commit aborted: bare except clause") |
| |
| # flake8 |
| if py_files: |
| try: |
| import flake8 # NOQA |
| except ImportError: |
| return exit("commit aborted: flake8 is not installed; " |
| "run 'make setup-dev-env'") |
| |
| # XXX: we should scape spaces and possibly other amenities here |
| ret = subprocess.call( |
| "%s -m flake8 %s" % (sys.executable, " ".join(py_files)), |
| shell=True) |
| if ret != 0: |
| return exit("commit aborted: python code is not flake8 compliant") |
| |
| |
| main() |