Better project structure

This commit is contained in:
Anthony Sottile
2014-04-12 07:28:25 -07:00
parent f31f092f9b
commit 1746a97e24
52 changed files with 221 additions and 189 deletions

View File

@@ -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

21
.gitignore vendored
View File

@@ -1,12 +1,13 @@
*.pyc
.pydevproject
.pre-commit-files
.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*
coverage-html
dist

View File

@@ -1,13 +1,12 @@
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
install: pip install tox --use-mirrors
script: tox
# Special snowflake. Our tests depend on making real commits.
before_install:
- git config --global user.name "Travis CI"
- git config --global user.email "user@example.com"

View File

@@ -1,41 +1,17 @@
.PHONY: all
all: venv test
TEST_TARGETS =
ITEST_TARGETS = -m integration
UTEST_TARGETS = -m "not(integration)"
.PHONY: venv
venv:
tox -e venv
DEBUG=
all: _tests
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
_tests: py_env
bash -c 'source py_env/bin/activate && py.test tests $(TEST_TARGETS) $(DEBUG)'
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 setup.py
rm -rf py_env
virtualenv py_env
bash -c 'source py_env/bin/activate && pip install -r requirements.txt'
test:
tox
.PHONY: clean
clean:
rm -rf py_env
find . -iname '*.pyc' | xargs rm -f
rm -rf .tox
rm -rf ./venv-*

24
UNLICENSE Normal file
View File

@@ -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/>

View File

@@ -1,4 +1,3 @@
# Hooks are set up as follows
# - id: hook_id
# name: 'Readable name'

View File

@@ -1,4 +1,3 @@
- repo: git@github.com:pre-commit/pre-commit-hooks
sha: cd74dc150c142c3be70b24eaf0b02cae9d235f37
hooks:

View File

@@ -1,4 +1,3 @@
from __future__ import print_function
import argparse

View File

@@ -1,4 +1,3 @@
import re
import sys
@@ -6,7 +5,8 @@ from pre_commit.clientlib.validate_base import get_run_function
from pre_commit.clientlib.validate_base import get_validator
class InvalidConfigError(ValueError): pass
class InvalidConfigError(ValueError):
pass
CONFIG_JSON_SCHEMA = {

View File

@@ -1,4 +1,3 @@
import sys
from pre_commit.clientlib.validate_base import get_run_function
@@ -6,7 +5,8 @@ from pre_commit.clientlib.validate_base import get_validator
from pre_commit.languages.all import all_languages
class InvalidManifestError(ValueError): pass
class InvalidManifestError(ValueError):
pass
MANIFEST_JSON_SCHEMA = {

View File

@@ -1,4 +1,3 @@
import sys
RED = '\033[41m'
@@ -8,18 +7,19 @@ TURQUOISE = '\033[46;30m'
NORMAL = '\033[0m'
class InvalidColorSetting(ValueError): pass
class InvalidColorSetting(ValueError):
pass
def format_color(text, color, use_color):
def format_color(text, color, use_color_setting):
"""Format text with color.
Args:
text - Text to be formatted with color if `use_color`
color - The color start string
use_color - Whether or not to color
use_color_setting - Whether or not to color
"""
if not use_color:
if not use_color_setting:
return text
else:
return u'{0}{1}{2}'.format(color, text, NORMAL)

View File

@@ -1,4 +1,3 @@
from __future__ import print_function
import os
@@ -42,7 +41,8 @@ def uninstall(runner):
return 0
class RepositoryCannotBeUpdatedError(RuntimeError): pass
class RepositoryCannotBeUpdatedError(RuntimeError):
pass
def _update_repository(repo_config):
@@ -95,8 +95,8 @@ def autoupdate(runner):
print('Updating {0}...'.format(repo_config['repo']), end='')
try:
new_repo_config = _update_repository(repo_config)
except RepositoryCannotBeUpdatedError as e:
print(e.args[0])
except RepositoryCannotBeUpdatedError as error:
print(error.args[0])
output_configs.append(repo_config)
retv = 1
continue

View File

@@ -1,4 +1,3 @@
CONFIG_FILE = '.pre-commit-config.yaml'
HOOKS_WORKSPACE = '.pre-commit-files'

View File

@@ -43,14 +43,15 @@ def get_files_matching(all_file_list_strategy):
def wrapper(include_expr, exclude_expr):
include_regex = re.compile(include_expr)
exclude_regex = re.compile(exclude_expr)
return set(filter(os.path.exists, (
return set(
filename
for filename in all_file_list_strategy()
if (
include_regex.search(filename) and
not exclude_regex.search(filename)
not exclude_regex.search(filename) and
os.path.exists(filename)
)
)))
)
return wrapper

View File

@@ -1,4 +1,3 @@
import contextlib
import os.path
from plumbum import local

View File

@@ -1,4 +1,3 @@
import copy
import jsonschema
import jsonschema.validators
@@ -21,7 +20,6 @@ def extend_validator_cls(validator_cls, modify):
)
def default_values(properties, instance):
for property, subschema in properties.iteritems():
if 'default' in subschema:

View File

@@ -1,4 +1,3 @@
from pre_commit.languages import node
from pre_commit.languages import python
from pre_commit.languages import ruby

View File

@@ -1,4 +1,3 @@
import contextlib
from pre_commit.languages import helpers

View File

@@ -1,4 +1,3 @@
import contextlib
from pre_commit.languages import helpers
@@ -11,7 +10,7 @@ ENVIRONMENT_DIR = 'rvm_env'
class RubyEnv(helpers.Environment):
@property
def env_prefix(self):
return '. {{prefix}}{0}/bin/activate &&'.format(ENVIRONMENT_DIR)
raise NotImplementedError
@contextlib.contextmanager

View File

@@ -1,4 +1,3 @@
ENVIRONMENT_DIR = None

View File

@@ -1,4 +1,3 @@
ENVIRONMENT_DIR = None

View File

@@ -1,4 +1,3 @@
from __future__ import print_function
import logging

View File

@@ -1,4 +1,3 @@
import os
import os.path
import subprocess

View File

@@ -1,4 +1,3 @@
import contextlib
import logging
from plumbum import local

View File

@@ -1,4 +1,3 @@
from __future__ import print_function
import argparse
@@ -56,7 +55,6 @@ def _run_single_hook(runner, repository, hook_id, args):
print_color = color.GREEN
pass_fail = 'Passed'
print(color.format_color(pass_fail, print_color, args.color))
if output and (retcode or args.verbose):
@@ -111,14 +109,14 @@ def run(argv):
subparsers.add_parser('autoupdate', help='Auto-update hooks config.')
run = subparsers.add_parser('run', help='Run hooks.')
run.add_argument('hook', nargs='?', help='A single hook-id to run'),
run.add_argument(
run_parser = subparsers.add_parser('run', help='Run hooks.')
run_parser.add_argument('hook', nargs='?', help='A single hook-id to run')
run_parser.add_argument(
'--all-files', '-a', action='store_true', default=False,
help='Run on all the files in the repo.',
)
run.add_argument('--verbose', '-v', action='store_true', default=False)
run.add_argument(
run_parser.add_argument('--verbose', '-v', action='store_true', default=False)
run_parser.add_argument(
'--color', default='auto', type=color.use_color,
help='Whether to use color in output. Defaults to `auto`',
)

View File

@@ -1,4 +1,3 @@
import os
import os.path
@@ -40,7 +39,7 @@ class Runner(object):
def repositories(self):
"""Returns a tuple of the configured repositories."""
config = load_config(self.config_file_path)
return tuple(map(Repository, config))
return tuple(Repository(x) for x in config)
@cached_property
def pre_commit_path(self):

View File

@@ -1,4 +1,3 @@
import contextlib
import logging
import time

View File

@@ -1,4 +1,3 @@
import contextlib
import functools
import os

View File

@@ -1,4 +1,3 @@
import yaml
from pre_commit.ordereddict import OrderedDict
@@ -6,23 +5,27 @@ from pre_commit.ordereddict import OrderedDict
# Adapted from http://stackoverflow.com/a/21912744/812183
def ordered_load(s):
class OrderedLoader(yaml.loader.Loader): pass
def ordered_load(stream):
class OrderedLoader(yaml.loader.Loader):
pass
def constructor(loader, node):
return OrderedDict(loader.construct_pairs(node))
OrderedLoader.add_constructor(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
constructor,
)
return yaml.load(s, Loader=OrderedLoader)
return yaml.load(stream, Loader=OrderedLoader)
def ordered_dump(s, **kwargs):
class OrderedDumper(yaml.dumper.SafeDumper): pass
def ordered_dump(obj, **kwargs):
class OrderedDumper(yaml.dumper.SafeDumper):
pass
def dict_representer(dumper, data):
return dumper.represent_mapping(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
data.items(),
)
OrderedDumper.add_representer(OrderedDict, dict_representer)
return yaml.dump(s, Dumper=OrderedDumper, **kwargs)
return yaml.dump(obj, Dumper=OrderedDumper, **kwargs)

19
pylintrc Normal file
View File

@@ -0,0 +1,19 @@
[MESSAGES CONTROL]
disable=missing-docstring,abstract-method,redefined-builtin,useless-else-on-loop,redefined-outer-name,invalid-name
[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

View File

@@ -1,10 +1 @@
-e .
# Testing requirements
coverage
# Fuck you ipython
ipython<2.0.0
ipdb
mock
pyflakes
pytest
.

7
requirements_dev.txt Normal file
View File

@@ -0,0 +1,7 @@
-e .
coverage
flake8
mock
pylint
pytest

View File

@@ -18,7 +18,20 @@ if sys.version_info < (2, 7):
setup(
name='pre_commit',
description='A framework for managing and maintaining multi-language pre-commit hooks.',
url='http://github.com/pre-commit/pre-commit',
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',
],
packages=find_packages('.', exclude=('tests*', 'testing*')),
package_data={
'pre_commit': [

View File

@@ -1,4 +1,3 @@
import collections
def auto_namedtuple(classname='auto_namedtuple', **kwargs):

View File

@@ -1,4 +1,3 @@
- repo: git@github.com:pre-commit/pre-commit-hooks
hooks:
- id: pyflakes

View File

@@ -1,4 +1,4 @@
import jsonschema
import os
import os.path
import shutil
@@ -27,3 +27,10 @@ def copy_tree_to_path(src_dir, dest_dir):
shutil.copytree(srcname, destname)
else:
shutil.copy(srcname, destname)
def is_valid_according_to_schema(obj, schema):
try:
jsonschema.validate(obj, schema)
return True
except jsonschema.exceptions.ValidationError:
return False

View File

@@ -1,4 +1,3 @@
import pytest
from pre_commit.clientlib.validate_base import get_validator
@@ -7,7 +6,8 @@ from pre_commit.yaml_extensions import ordered_load
from testing.util import get_resource_path
class AdditionalValidatorError(ValueError): pass
class AdditionalValidatorError(ValueError):
pass
@pytest.fixture
@@ -22,7 +22,7 @@ def array_validator():
@pytest.fixture
def additional_validator():
def raises_always(obj):
def raises_always(_):
raise AdditionalValidatorError
return get_validator(

View File

@@ -1,6 +1,3 @@
import jsonschema
import jsonschema.exceptions
import pytest
from pre_commit.clientlib.validate_config import CONFIG_JSON_SCHEMA
@@ -8,6 +5,7 @@ from pre_commit.clientlib.validate_config import InvalidConfigError
from pre_commit.clientlib.validate_config import run
from pre_commit.clientlib.validate_config import validate_config_extra
from pre_commit.jsonschema_extensions import apply_defaults
from testing.util import is_valid_according_to_schema
def test_returns_0_for_valid_config():
@@ -22,21 +20,13 @@ def test_returns_1_for_failing():
assert run(['tests/data/valid_yaml_but_invalid_config.yaml']) == 1
def is_valid_according_to_schema(obj, schema):
try:
jsonschema.validate(obj, schema)
return True
except jsonschema.exceptions.ValidationError:
return False
@pytest.mark.parametrize(('manifest_obj', 'expected'), (
([], False),
(
[{
'repo': 'git@github.com:pre-commit/pre-commit-hooks',
'sha': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37',
'hooks': [{'id': 'pyflakes', 'files': '\.py$'}],
'repo': 'git@github.com:pre-commit/pre-commit-hooks',
'sha': 'cd74dc150c142c3be70b24eaf0b02cae9d235f37',
'hooks': [{'id': 'pyflakes', 'files': '\\.py$'}],
}],
True,
),
@@ -47,7 +37,7 @@ def is_valid_according_to_schema(obj, schema):
'hooks': [
{
'id': 'pyflakes',
'files': '\.py$',
'files': '\\.py$',
'args': ['foo', 'bar', 'baz'],
},
],
@@ -61,7 +51,7 @@ def is_valid_according_to_schema(obj, schema):
'hooks': [
{
'id': 'pyflakes',
'files': '\.py$',
'files': '\\.py$',
# Exclude pattern must be a string
'exclude': 0,
'args': ['foo', 'bar', 'baz'],
@@ -95,7 +85,7 @@ def test_config_with_ok_regexes_passes():
[{
'repo': 'foo',
'sha': 'foo',
'hooks': [{'id': 'hook_id', 'files': '\.py$'}],
'hooks': [{'id': 'hook_id', 'files': '\\.py$'}],
}],
CONFIG_JSON_SCHEMA,
)

View File

@@ -1,12 +1,10 @@
import jsonschema
import jsonschema.exceptions
import pytest
from pre_commit.clientlib.validate_manifest import additional_manifest_check
from pre_commit.clientlib.validate_manifest import InvalidManifestError
from pre_commit.clientlib.validate_manifest import MANIFEST_JSON_SCHEMA
from pre_commit.clientlib.validate_manifest import run
from testing.util import is_valid_according_to_schema
def test_returns_0_for_valid_manifest():
@@ -34,24 +32,16 @@ def test_additional_manifest_check_languages(obj):
additional_manifest_check(obj)
def is_valid_according_to_schema(obj, schema):
try:
jsonschema.validate(obj, schema)
return True
except jsonschema.exceptions.ValidationError:
return False
@pytest.mark.parametrize(('manifest_obj', 'expected'), (
([], False),
([{'id': 'a', 'name': 'b', 'entry': 'c', 'language': 'python'}], True),
(
[{
'id': 'a',
'name': 'b',
'entry': 'c',
'language': 'python',
'expected_return_value': 0,
'id': 'a',
'name': 'b',
'entry': 'c',
'language': 'python',
'expected_return_value': 0,
}],
True,
),

View File

@@ -1,4 +1,3 @@
import mock
import pytest
import sys

View File

@@ -1,4 +1,3 @@
import os
import os.path
import pkg_resources

View File

@@ -81,12 +81,12 @@ def _make_config(path, hook_id, file_regex):
@pytest.yield_fixture
def config_for_node_hooks_repo(node_hooks_repo):
yield _make_config(node_hooks_repo, 'foo', '\.js$')
yield _make_config(node_hooks_repo, 'foo', '\\.js$')
@pytest.yield_fixture
def config_for_python_hooks_repo(python_hooks_repo):
yield _make_config(python_hooks_repo, 'foo', '\.py$')
yield _make_config(python_hooks_repo, 'foo', '\\.py$')
@pytest.yield_fixture

View File

@@ -1,4 +1,3 @@
import pytest
from plumbum import local
@@ -38,7 +37,7 @@ def test_get_files_matching_base(get_files_matching_func):
def test_get_files_matching_total_match(get_files_matching_func):
ret = get_files_matching_func('^.*\.py$', '^$')
ret = get_files_matching_func('^.*\\.py$', '^$')
assert ret == set([
'pre_commit/run.py',
'pre_commit/git.py',
@@ -46,7 +45,7 @@ def test_get_files_matching_total_match(get_files_matching_func):
def test_does_search_instead_of_match(get_files_matching_func):
ret = get_files_matching_func('\.yaml$', '^$')
ret = get_files_matching_func('\\.yaml$', '^$')
assert ret == set(['hooks.yaml'])
@@ -56,5 +55,5 @@ def test_does_not_include_deleted_fileS(get_files_matching_func):
def test_exclude_removes_files(get_files_matching_func):
ret = get_files_matching_func('', '\.py$')
ret = get_files_matching_func('', '\\.py$')
assert ret == set(['hooks.yaml'])

View File

@@ -1,4 +1,3 @@
from pre_commit.jsonschema_extensions import apply_defaults
from pre_commit.jsonschema_extensions import remove_defaults

View File

@@ -1,4 +1,3 @@
import pytest
from pre_commit.languages.all import all_languages

View File

@@ -1,4 +1,3 @@
import os
import mock
import pytest
@@ -94,7 +93,8 @@ def test_path_multiple_args():
assert ret == 'foo/bar/baz'
@pytest.mark.parametrize(('prefix', 'path_end', 'expected_output'),
@pytest.mark.parametrize(
('prefix', 'path_end', 'expected_output'),
tuple(
(prefix, path_end, expected_output + os.sep)
for prefix, path_end, expected_output in PATH_TESTS

View File

@@ -31,6 +31,7 @@ def test_create_repo_in_env(dummy_repo_config, dummy_git_repo):
os.path.join(dummy_git_repo, C.HOOKS_WORKSPACE, repo.sha),
)
@pytest.mark.integration
def test_install_python_repo_in_env(config_for_python_hooks_repo):
repo = Repository(config_for_python_hooks_repo)
@@ -110,7 +111,7 @@ def mock_repo_config():
'sha': '5e713f8878b7d100c0e059f8cc34be4fc2e8f897',
'hooks': [{
'id': 'pyflakes',
'files': '\.py$',
'files': '\\.py$',
}],
}
config_wrapped = apply_defaults([config], CONFIG_JSON_SCHEMA)

View File

@@ -1,4 +1,3 @@
import os
import os.path
import pytest

View File

@@ -1,4 +1,3 @@
import os.path
import pytest
import shutil

View File

@@ -1,4 +1,3 @@
import mock
import pytest
import os
@@ -17,7 +16,7 @@ from pre_commit.util import memoize_by_cwd
def class_with_cached_property():
class Foo(object):
@cached_property
def foo(self):
def my_property(self):
return "Foo" + str(random.getrandbits(64))
return Foo
@@ -25,14 +24,14 @@ def class_with_cached_property():
def test_cached_property(class_with_cached_property):
instance = class_with_cached_property()
val = instance.foo
val2 = instance.foo
val = instance.my_property
val2 = instance.my_property
assert val is val2
def test_unbound_cached_property(class_with_cached_property):
# Make sure we don't blow up when accessing the property unbound
prop = class_with_cached_property.foo
prop = class_with_cached_property.my_property
assert isinstance(prop, cached_property)
@@ -90,7 +89,8 @@ def test_no_arguments_passed_uses_argv(entry_func):
def test_clean_on_failure_noop(in_tmpdir):
with clean_path_on_failure('foo'): pass
with clean_path_on_failure('foo'):
pass
def test_clean_path_on_failure_does_nothing_when_not_raising(in_tmpdir):
@@ -100,7 +100,8 @@ def test_clean_path_on_failure_does_nothing_when_not_raising(in_tmpdir):
def test_clean_path_on_failure_cleans_for_normal_exception(in_tmpdir):
class MyException(Exception): pass
class MyException(Exception):
pass
with pytest.raises(MyException):
with clean_path_on_failure('foo'):
@@ -111,7 +112,8 @@ def test_clean_path_on_failure_cleans_for_normal_exception(in_tmpdir):
def test_clean_path_on_failure_cleans_for_system_exit(in_tmpdir):
class MySystemExit(SystemExit): pass
class MySystemExit(SystemExit):
pass
with pytest.raises(MySystemExit):
with clean_path_on_failure('foo'):

View File

@@ -1,4 +1,3 @@
import pre_commit.constants as C
from pre_commit.ordereddict import OrderedDict
from pre_commit.yaml_extensions import ordered_dump

28
tox.ini Normal file
View File

@@ -0,0 +1,28 @@
[tox]
project = pre_commit
# These should match the travis env list
envlist = py26,py27
[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 90
flake8 {[tox]project} tests setup.py
# pylint {[tox]project} 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