Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
asottile committed Mar 13, 2014
0 parents commit 27291ff
Show file tree
Hide file tree
Showing 10 changed files with 330 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[report]
exclude_lines =
# Don't complain about defensive assertions
raise NotImplementedError
raise AssertionError

# Don't complain about non-runnable code
if __name__ == .__main__.:

omit =
/usr/*
py_env/*
*/__init__.py

# Ignore test coverage
tests/*

# Don't complain about our pre-commit file
pre-commit.py
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
*.pyc
.pydevproject
.project
.coverage
/py_env
*.db
.idea
build
dist
*.egg-info
8 changes: 8 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
language: python

python:
- 2.6
- 2.7

install: pip install virtualenv
script: make
40 changes: 40 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

TEST_TARGETS =
ITEST_TARGETS = -m integration
UTEST_TARGETS = -m "not(integration)"

all: _tests

integration:
$(eval TEST_TARGETS := $(ITEST_TARGETS))

unit:
$(eval TEST_TARGETS := $(UTEST_TARGETS))

utests: test
utest: test
tests: test
test: unit _tests
itests: itest
itest: integration _tests

_tests: py_env
bash -c 'source py_env/bin/activate && py.test tests $(TEST_TARGETS)'

ucoverage: unit coverage
icoverage: integration coverage

coverage: py_env
bash -c 'source py_env/bin/activate && \
coverage erase && \
coverage run `which py.test` tests $(TEST_TARGETS) && \
coverage report -m'

py_env: requirements.txt
rm -rf py_env
virtualenv py_env
bash -c 'source py_env/bin/activate && \
pip install -r requirements.txt'

clean:
rm -rf py_env
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pre-commit-hooks
==========

Some out-of-the-box hooks for pre-commit.

See also: https://github.com/asottile/pre-commit
225 changes: 225 additions & 0 deletions pre-commit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
#!/usr/bin/env python

import collections
import optparse
import os
import os.path
import shutil
import subprocess
import sys

def __backport_check_output():
def check_output(*popenargs, **kwargs):
r"""Run command with arguments and return its output as a byte string.
Backported from Python 2.7 as it's implemented as pure python on stdlib.
>>> check_output(['/usr/bin/python', '--version'])
Python 2.6.2
"""
process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
output, unused_err = process.communicate()
retcode = process.poll()
if retcode:
cmd = kwargs.get("args")
if cmd is None:
cmd = popenargs[0]
error = subprocess.CalledProcessError(retcode, cmd)
error.output = output
raise error
return output

if not hasattr(subprocess, 'check_output'):
setattr(subprocess, 'check_output', check_output)

__backport_check_output()
del __backport_check_output


FILE_LIST = 'git ls-files | egrep "\.%s$"'

ALL_FILES = FILE_LIST % '(php|sql|py|js|htm|c|cpp|h|sh|css)'
JS_FILES = FILE_LIST % 'js'
PY_FILES = FILE_LIST % 'py'
CPP_FILES = FILE_LIST % '(cc|cpp|h)'
C_FILES = FILE_LIST % '(c|h)'
C_LIKE_FILES = FILE_LIST % '(c|cc|cpp|h)'
HEADER_FILES = FILE_LIST % 'h'

RED = '\033[41m'
GREEN = '\033[42m'
NORMAL = '\033[0m'
COLS = int(subprocess.check_output(['tput', 'cols']))

Test = collections.namedtuple('Test', ['command', 'name', 'nonzero', 'config'])

TESTS = [
Test(
"%s | xargs pyflakes" % PY_FILES,
'Py - Pyflakes',
False, 'testpyflakes',
),
Test(
"%s | xargs grep 'import\sipdb'" % PY_FILES,
'Py - ipdb',
True, 'testipdb',
),
Test(
"%s | grep 'tests' | grep -v '_test.py$' | grep -v '__init__.py' | grep -v '/conftest.py'" % PY_FILES,
'Py - Test files should end in _test.py',
True, 'testtestnames',
),
Test(
"%s | xargs egrep 'split\(.\\\\n.\)'" % PY_FILES,
'Py - Use s.splitlines over s.split',
True, 'testsplitlines',
),
Test(
"%s | xargs grep -H -n -P '\t'" % ALL_FILES,
"All - No tabs",
True, 'testtabs',
),
Test(
"make test",
"Py - Tests",
False, 'testtests',
),
]

def get_git_config(config_name):
config_result = ''
try:
config_result = subprocess.check_output([
'git', 'config', config_name
])
except subprocess.CalledProcessError: pass

return config_result.strip()

def get_pre_commit_path():
git_top = subprocess.check_output(
['git', 'rev-parse', '--show-toplevel']
).strip()
return os.path.join(git_top, '.git/hooks/pre-commit')

class FixAllBase(object):
name = None
matching_files_command = None

def get_all_files(self):
try:
files = subprocess.check_output(
self.matching_files_command,
shell=True,
)
files_split = files.splitlines()
return [file.strip() for file in files_split]
except subprocess.CalledProcessError:
return []

def fix_file(self, file):
'''Implement to fix the file.'''
raise NotImplementedError

def run(self):
'''Runs the process to fix the files. Returns True if nothign to fix.'''
print '%s...' % self.name
all_files = self.get_all_files()
for file in all_files:
print 'Fixing %s' % file
self.fix_file(file)
return not all_files

class FixTrailingWhitespace(FixAllBase):
name = 'Trimming trailing whitespace'
matching_files_command = '%s | xargs egrep -l "[[:space:]]$"' % ALL_FILES

def fix_file(self, file):
subprocess.check_call(['sed', '-i', '-e', 's/[[:space:]]*$//', file])

class FixLineEndings(FixAllBase):
name = 'Fixing line endings'
matching_files_command = "%s | xargs egrep -l $'\\r'\\$" % ALL_FILES

def fix_file(self, file):
subprocess.check_call(['dos2unix', file])
subprocess.check_call(['mac2unix', file])

FIXERS = [
FixTrailingWhitespace,
FixLineEndings,
]

def run_tests():
passed = True
for test in TESTS:
run_test = get_git_config('hooks.%s' % test.config)
if run_test == 'false':
print 'Skipping "%s" due to git config.' % test.name
continue

try:
retcode = 0
output = subprocess.check_output(
test.command, shell=True, stderr=subprocess.STDOUT
)
except subprocess.CalledProcessError as e:
retcode = e.returncode
output = e.output

pass_fail = '%sSuccess%s' % (GREEN, NORMAL)
failed_test = False
if (retcode and not test.nonzero) or (not retcode and test.nonzero):
pass_fail = '%sFailure(%s)%s' % (RED, retcode, NORMAL)
failed_test = True

dots = COLS - len(pass_fail) - len(test.name)
print '%s%s%s' % (test.name, '.' * dots, pass_fail)

if failed_test:
print
print output
passed = False

return passed

if __name__ == '__main__':
parser = optparse.OptionParser()
parser.add_option(
'-u', '--uninstall',
action='store_true', dest='uninstall', default=False,
help='Uninstall pre-commit script.'
)
parser.add_option(
'-i', '--install',
action='store_true', dest='install', default=False,
help='Install pre-commit script.'
)
opts, args = parser.parse_args()

if opts.install:
pre_commit_path = get_pre_commit_path()
shutil.copyfile(__file__, pre_commit_path)
os.chmod(pre_commit_path, 0755)
print 'Installed pre commit to %s' % pre_commit_path
sys.exit(0)
elif opts.uninstall:
pre_commit_path = get_pre_commit_path()
if os.path.exists(pre_commit_path):
os.remove(pre_commit_path)
print 'Removed pre-commit scripts.'

passed = True
for fixer in FIXERS:
passed &= fixer().run()
passed &= run_tests()

if not passed:
print '%sFailures / Fixes detected.%s' % (RED, NORMAL)
print 'Please fix and commit again.'
print "You could also pass --no-verify, but you probably shouldn't."
print
print "Here's git status for convenience: "
print
os.system('git status')
sys.exit(-1)
Empty file added pre_commit_hooks/__init__.py
Empty file.
10 changes: 10 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
argparse
pyyaml
simplejson

# Testing requirements
coverage
ipdb
mock
pyflakes
pytest
12 changes: 12 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from setuptools import find_packages
from setuptools import setup

setup(
name='pre_commit_hooks',
version='0.0.0',
packages=find_packages('.', exclude=('tests*', 'testing*')),
install_requires=[
'argparse',
'simplejson',
],
)
Empty file added tests/__init__.py
Empty file.

0 comments on commit 27291ff

Please sign in to comment.