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 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 @@ def check_yaml(argv): 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 @@ def check_file_for_debug_statements(filename): 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 @@ def fix_file(file_obj): 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 @@ def validate_files(argv): 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 @@ def fix_trailing_whitespace(argv): 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/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..a054426 100644 --- a/tests/debug_statement_hook_test.py +++ b/tests/debug_statement_hook_test.py @@ -1,4 +1,3 @@ - import ast import pytest 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 @@ from pre_commit_hooks.end_of_file_fixer import fix_file # 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_fix_file(input, expected_retval, output): 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/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..7dccc5a --- /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 82 + 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