Merge pull request #4 from pre-commit/better_project_setup
Better project setup
diff --git a/.coveragerc b/.coveragerc
index 418c1f1..82a7f6c 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,19 +1,29 @@
+[run]
+branch = True
+timid = True
+source =
+ .
+omit =
+ .tox/*
+ /usr/*
+ */tmp*
+ setup.py
+
[report]
exclude_lines =
- # Don't complain about defensive assertions
- raise NotImplementedError
- raise AssertionError
+ # Have to re-enable the standard pragma
+ \#\s*pragma: no cover
- # Don't complain about non-runnable code
- if __name__ == .__main__.:
+ # Don't complain if tests don't hit defensive assertion code:
+ ^\s*raise AssertionError\b
+ ^\s*raise NotImplementedError\b
+ ^\s*return NotImplemented\b
+ ^\s*raise$
-omit =
- /usr/*
- py_env/*
- */__init__.py
+ # Don't complain if non-runnable code isn't run:
+ ^if __name__ == ['"]__main__['"]:$
- # Ignore test coverage
- tests/*
+[html]
+directory = coverage-html
- # Don't complain about our pre-commit file
- pre-commit.py
+# vim:ft=dosini
diff --git a/.gitignore b/.gitignore
index abd9e46..a63d861 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,14 @@
-*.pyc
-.pydevproject
-.project
-.coverage
-/py_env
-*.db
-.idea
-build
-dist
*.egg-info
*.iml
+*.py[co]
+.*.sw[a-z]
+.coverage
+.idea
.pre-commit-files
+.project
+.pydevproject
+.tox
+.venv.touch
+/venv*
+coverage-html
+dist
diff --git a/.travis.yml b/.travis.yml
index 51f507a..0688c6d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,8 @@
language: python
-
-python:
- - 2.6
- - 2.7
-
-install: pip install virtualenv
-script: make coverage
+env: # These should match the tox env list
+ - TOXENV=py26
+ - TOXENV=py27
+ - TOXENV=py33
+ - TOXENV=pypy
+install: pip install tox --use-mirrors
+script: tox
diff --git a/Makefile b/Makefile
index 08bd8b3..7399eb3 100644
--- a/Makefile
+++ b/Makefile
@@ -1,41 +1,27 @@
-TEST_TARGETS =
-ITEST_TARGETS = -m integration
-UTEST_TARGETS = -m "not(integration)"
+REBUILD_FLAG =
-DEBUG=
+.PHONY: all
+all: venv test
-all: _tests
+.PHONY: venv
+venv: .venv.touch
+ tox -e venv $(REBUILD_FLAG)
-integration:
- $(eval TEST_TARGETS := $(ITEST_TARGETS))
-
-unit:
- $(eval TEST_TARGETS := $(UTEST_TARGETS))
-
-utests: test
-utest: test
+.PHONY: tests test
tests: test
-test: unit _tests
-itests: itest
-itest: integration _tests
+test: .venv.touch
+ tox $(REBUILD_FLAG)
-_tests: py_env
- bash -c 'source py_env/bin/activate && py.test tests $(TEST_TARGETS) $(DEBUG)'
-ucoverage: unit coverage
-icoverage: integration coverage
+.venv.touch: setup.py requirements.txt requirements_dev.txt
+ $(eval REBUILD_FLAG := --recreate)
+ touch .venv.touch
-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 setup.py
- rm -rf py_env
- virtualenv py_env
- bash -c 'source py_env/bin/activate && pip install -r requirements.txt'
-
+.PHONY: clean
clean:
- rm -rf py_env
+ find . -iname '*.pyc' | xargs rm -f
+ rm -rf .tox
+ rm -rf ./venv-*
+ rm -f .venv.touch
diff --git a/UNLICENSE b/UNLICENSE
new file mode 100644
index 0000000..68a49da
--- /dev/null
+++ b/UNLICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>
diff --git a/pre_commit_hooks/check_yaml.py b/pre_commit_hooks/check_yaml.py
index c297ccf..456f1f7 100644
--- a/pre_commit_hooks/check_yaml.py
+++ b/pre_commit_hooks/check_yaml.py
@@ -1,3 +1,4 @@
+from __future__ import print_function
import argparse
import sys
@@ -17,7 +18,7 @@
try:
yaml.load(open(filename))
except yaml.YAMLError as e:
- print e
+ print(e)
retval = 1
return retval
diff --git a/pre_commit_hooks/debug_statement_hook.py b/pre_commit_hooks/debug_statement_hook.py
index 565740e..92f7c07 100644
--- a/pre_commit_hooks/debug_statement_hook.py
+++ b/pre_commit_hooks/debug_statement_hook.py
@@ -1,3 +1,4 @@
+from __future__ import print_function
import argparse
import ast
@@ -39,7 +40,14 @@
visitor.visit(ast_obj)
if visitor.debug_import_statements:
for debug_statement in visitor.debug_import_statements:
- print '{0}:{2}:{3} - {1} imported'.format(filename, *debug_statement)
+ print(
+ '{0}:{1}:{2} - {3} imported'.format(
+ filename,
+ debug_statement.line,
+ debug_statement.col,
+ debug_statement.name,
+ )
+ )
return 1
else:
return 0
diff --git a/pre_commit_hooks/end_of_file_fixer.py b/pre_commit_hooks/end_of_file_fixer.py
index b585ce3..9fee9c1 100644
--- a/pre_commit_hooks/end_of_file_fixer.py
+++ b/pre_commit_hooks/end_of_file_fixer.py
@@ -1,4 +1,3 @@
-
from __future__ import print_function
from __future__ import unicode_literals
@@ -18,11 +17,11 @@
return 0
last_character = file_obj.read(1)
# last_character will be '' for an empty file
- if last_character != '\n' and last_character != '':
- file_obj.write('\n')
+ if last_character != b'\n' and last_character != b'':
+ file_obj.write(b'\n')
return 1
- while last_character == '\n':
+ while last_character == b'\n':
# Deal with the beginning of the file
if file_obj.tell() == 1:
# If we've reached the beginning of the file and it is all
diff --git a/pre_commit_hooks/tests_should_end_in_test.py b/pre_commit_hooks/tests_should_end_in_test.py
index 37db03c..92641f0 100644
--- a/pre_commit_hooks/tests_should_end_in_test.py
+++ b/pre_commit_hooks/tests_should_end_in_test.py
@@ -1,4 +1,3 @@
-
from __future__ import print_function
import sys
@@ -22,4 +21,4 @@
if __name__ == '__main__':
- sys.exit(entry())
+ sys.exit(validate_files())
diff --git a/pre_commit_hooks/trailing_whitespace_fixer.py b/pre_commit_hooks/trailing_whitespace_fixer.py
index bf016af..20b08fe 100644
--- a/pre_commit_hooks/trailing_whitespace_fixer.py
+++ b/pre_commit_hooks/trailing_whitespace_fixer.py
@@ -1,3 +1,4 @@
+from __future__ import print_function
import argparse
import sys
@@ -12,13 +13,13 @@
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
args = parser.parse_args(argv)
- bad_whitespace_files = filter(bool, local['grep'][
+ bad_whitespace_files = local['grep'][
('-l', '[[:space:]]$') + tuple(args.filenames)
- ](retcode=None).splitlines())
+ ](retcode=None).strip().splitlines()
if bad_whitespace_files:
for bad_whitespace_file in bad_whitespace_files:
- print 'Fixing {0}'.format(bad_whitespace_file)
+ print('Fixing {0}'.format(bad_whitespace_file))
local['sed']['-i', '-e', 's/[[:space:]]*$//', bad_whitespace_file]()
return 1
else:
diff --git a/pre_commit_hooks/util.py b/pre_commit_hooks/util.py
index 22d13c4..5b65794 100644
--- a/pre_commit_hooks/util.py
+++ b/pre_commit_hooks/util.py
@@ -1,4 +1,3 @@
-
import functools
import sys
diff --git a/pylintrc b/pylintrc
new file mode 100644
index 0000000..9895d8b
--- /dev/null
+++ b/pylintrc
@@ -0,0 +1,19 @@
+[MESSAGES CONTROL]
+disable=missing-docstring,abstract-method,redefined-builtin,invalid-name,no-value-for-parameter,redefined-outer-name,no-member,bad-open-mode
+
+[REPORTS]
+output-format=colorized
+reports=no
+
+[BASIC]
+const-rgx=(([A-Za-z_][A-Za-z0-9_]*)|(__.*__))$
+
+[FORMAT]
+max-line-length=131
+
+[TYPECHECK]
+ignored-classes=pytest
+
+[DESIGN]
+min-public-methods=0
+
diff --git a/requirements.txt b/requirements.txt
index b63c160..9c558e3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1 @@
--e .
-
-# Testing requirements
-coverage
-flake8
-mock
-git+git://github.com/pre-commit/pre-commit#egg=pre-commit
-pytest
+.
diff --git a/requirements_dev.txt b/requirements_dev.txt
new file mode 100644
index 0000000..9051674
--- /dev/null
+++ b/requirements_dev.txt
@@ -0,0 +1,8 @@
+-e .
+
+coverage
+flake8
+mock
+pylint
+pytest
+git+git://github.com/pre-commit/pre-commit#egg=pre_commit
diff --git a/setup.py b/setup.py
index 094a989..5454b48 100644
--- a/setup.py
+++ b/setup.py
@@ -1,9 +1,25 @@
from setuptools import find_packages
from setuptools import setup
+
setup(
name='pre_commit_hooks',
+ description='Some out-of-the-box hooks for pre-commit.',
+ url='https://github.com/pre-commit/pre-commit-hooks',
version='0.0.0',
+
+ author='Anthony Sottile',
+ author_email='asottile@umich.edu',
+
+ platforms='linux',
+ classifiers=[
+ 'License :: Public Domain',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ ],
+
packages=find_packages('.', exclude=('tests*', 'testing*')),
install_requires=[
'argparse',
diff --git a/testing/resources/file_with_debug.notpy b/testing/resources/file_with_debug.notpy
new file mode 100644
index 0000000..faa23a2
--- /dev/null
+++ b/testing/resources/file_with_debug.notpy
@@ -0,0 +1,5 @@
+
+def foo(obj):
+ import pdb; pdb.set_trace()
+
+ return 5
diff --git a/testing/util.py b/testing/util.py
index c52a8cf..8e468d6 100644
--- a/testing/util.py
+++ b/testing/util.py
@@ -1,4 +1,3 @@
-
import os.path
diff --git a/tests/check_yaml_test.py b/tests/check_yaml_test.py
index 8985e8c..c145fdc 100644
--- a/tests/check_yaml_test.py
+++ b/tests/check_yaml_test.py
@@ -1,4 +1,3 @@
-
import pytest
from pre_commit_hooks.check_yaml import check_yaml
diff --git a/tests/conftest.py b/tests/conftest.py
deleted file mode 100644
index 762b31d..0000000
--- a/tests/conftest.py
+++ /dev/null
@@ -1,11 +0,0 @@
-
-import __builtin__
-
-import mock
-import pytest
-
-
-@pytest.yield_fixture
-def print_mock():
- with mock.patch.object(__builtin__, 'print', autospec=True) as mock_print:
- yield mock_print
diff --git a/tests/debug_statement_hook_test.py b/tests/debug_statement_hook_test.py
index 01ecdc1..44c462f 100644
--- a/tests/debug_statement_hook_test.py
+++ b/tests/debug_statement_hook_test.py
@@ -1,9 +1,10 @@
-
import ast
import pytest
from pre_commit_hooks.debug_statement_hook import DebugStatement
+from pre_commit_hooks.debug_statement_hook import debug_statement_hook
from pre_commit_hooks.debug_statement_hook import ImportStatementParser
+from testing.util import get_resource_path
@pytest.fixture
@@ -54,3 +55,13 @@
assert visitor.debug_import_statements == [
DebugStatement('pudb', 3, 0)
]
+
+
+def test_returns_one_for_failing_file():
+ ret = debug_statement_hook([get_resource_path('file_with_debug.notpy')])
+ assert ret == 1
+
+
+def test_returns_zero_for_passing_file():
+ ret = debug_statement_hook([__file__])
+ assert ret == 0
diff --git a/tests/end_of_file_fixer_test.py b/tests/end_of_file_fixer_test.py
index 444836e..2e53246 100644
--- a/tests/end_of_file_fixer_test.py
+++ b/tests/end_of_file_fixer_test.py
@@ -1,5 +1,4 @@
-
-import cStringIO
+import io
import os.path
import pytest
@@ -9,19 +8,19 @@
# Input, expected return value, expected output
TESTS = (
- ('foo\n', 0, 'foo\n'),
- ('', 0, ''),
- ('\n\n', 1, ''),
- ('\n\n\n\n', 1, ''),
- ('foo', 1, 'foo\n'),
- ('foo\n\n\n', 1, 'foo\n'),
- ('\xe2\x98\x83', 1, '\xe2\x98\x83\n'),
+ (b'foo\n', 0, b'foo\n'),
+ (b'', 0, b''),
+ (b'\n\n', 1, b''),
+ (b'\n\n\n\n', 1, b''),
+ (b'foo', 1, b'foo\n'),
+ (b'foo\n\n\n', 1, b'foo\n'),
+ (b'\xe2\x98\x83', 1, b'\xe2\x98\x83\n'),
)
@pytest.mark.parametrize(('input', 'expected_retval', 'output'), TESTS)
def test_fix_file(input, expected_retval, output):
- file_obj = cStringIO.StringIO()
+ file_obj = io.BytesIO()
file_obj.write(input)
ret = fix_file(file_obj)
assert file_obj.getvalue() == output
@@ -32,11 +31,11 @@
def test_integration(input, expected_retval, output, tmpdir):
file_path = os.path.join(tmpdir.strpath, 'file.txt')
- with open(file_path, 'w') as file_obj:
+ with open(file_path, 'wb') as file_obj:
file_obj.write(input)
ret = end_of_file_fixer([file_path])
- file_output = open(file_path, 'r').read()
+ file_output = open(file_path, 'rb').read()
assert file_output == output
assert ret == expected_retval
diff --git a/tests/tests_should_end_in_test_test.py b/tests/tests_should_end_in_test_test.py
index e56b84e..3ba1617 100644
--- a/tests/tests_should_end_in_test_test.py
+++ b/tests/tests_should_end_in_test_test.py
@@ -1,14 +1,11 @@
-
from pre_commit_hooks.tests_should_end_in_test import validate_files
-def test_validate_files_all_pass(print_mock):
+def test_validate_files_all_pass():
ret = validate_files(['foo_test.py', 'bar_test.py'])
assert ret == 0
- assert print_mock.call_count == 0
-def test_validate_files_one_fails(print_mock):
+def test_validate_files_one_fails():
ret = validate_files(['not_test_ending.py', 'foo_test.py'])
assert ret == 1
- assert print_mock.call_count == 1
diff --git a/tests/trailing_whitespace_fixer_test.py b/tests/trailing_whitespace_fixer_test.py
new file mode 100644
index 0000000..3b6a9a4
--- /dev/null
+++ b/tests/trailing_whitespace_fixer_test.py
@@ -0,0 +1,26 @@
+from plumbum import local
+
+from pre_commit_hooks.trailing_whitespace_fixer import fix_trailing_whitespace
+
+
+def test_fixes_trailing_whitespace(tmpdir):
+ with local.cwd(tmpdir.strpath):
+ for filename, contents in (
+ ('foo.py', 'foo \nbar \n'),
+ ('bar.py', 'bar\t\nbaz\t\n'),
+ ):
+ with open(filename, 'w') as f:
+ f.write(contents) # pragma: no cover (python 2.6 coverage bug)
+
+ ret = fix_trailing_whitespace(['foo.py', 'bar.py'])
+ assert ret == 1
+
+ for filename, after_contents in (
+ ('foo.py', 'foo\nbar\n'),
+ ('bar.py', 'bar\nbaz\n'),
+ ):
+ assert open(filename).read() == after_contents
+
+
+def test_returns_zero_for_no_changes():
+ assert fix_trailing_whitespace([__file__]) == 0
diff --git a/tests/util_test.py b/tests/util_test.py
index 84fa6c5..28ebe81 100644
--- a/tests/util_test.py
+++ b/tests/util_test.py
@@ -1,4 +1,3 @@
-
import mock
import pytest
import sys
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..f99bbc5
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,28 @@
+[tox]
+project = pre_commit_hooks
+# These should match the travis env list
+envlist = py26,py27,py33,pypy
+
+[testenv]
+install_command = pip install --use-wheel {opts} {packages}
+deps = -rrequirements_dev.txt
+commands =
+ coverage erase
+ coverage run -m pytest {posargs:tests}
+ coverage report --show-missing --fail-under 100
+ flake8 {[tox]project} testing tests setup.py
+ pylint {[tox]project} testing tests setup.py
+
+[testenv:venv]
+envdir = venv-{[tox]project}
+commands =
+
+[testenv:docs]
+deps =
+ {[testenv]deps}
+ sphinx
+changedir = docs
+commands = sphinx-build -b html -d build/doctrees source build/html
+
+[flake8]
+max-line-length=131