Skip to content

Commit 3945f84

Browse files
committed
Started implementing manifest validating
1 parent e063921 commit 3945f84

11 files changed

Lines changed: 237 additions & 0 deletions

example_manifest.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
# Hooks are set up as follows
3+
# hooks:
4+
# -
5+
# id: hook_id
6+
# name: 'Readable name'
7+
# entry: my_hook_executable
8+
#
9+
# # Optional
10+
# description: 'Longer description of the hook'
11+
#
12+
# # Optional, for now 'python[optional version]', 'ruby #.#.#', 'node'
13+
# language: 'python'
14+
#
15+
# # Optional, defaults to zero
16+
# expected_return_value: 0
17+
18+
hooks:
19+
-
20+
id: my_hook
21+
name: My Simple Hook
22+
description: This is my simple hook that does blah
23+
entry: my-simple-hook.py
24+
language: python
25+
expected_return_value: 0
26+
-
27+
id: my_grep_based_hook
28+
name: My Bash Based Hook
29+
description: This is a hook that uses grep to validate some stuff
30+
entry: ./my_grep_based_hook.sh
31+
expected_return_value: 1

pre_commit/clientlib/__init__.py

Whitespace-only changes.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
2+
from __future__ import print_function
3+
4+
import argparse
5+
import jsonschema
6+
import jsonschema.exceptions
7+
import os.path
8+
import yaml
9+
10+
import pre_commit.constants as C
11+
12+
13+
class InvalidManifestError(ValueError): pass
14+
15+
16+
MANIFEST_JSON_SCHEMA = {
17+
'type': 'object',
18+
'properties': {
19+
'hooks': {
20+
'type': 'array',
21+
'minItems': 1,
22+
'items': {
23+
'type': 'object',
24+
'properties': {
25+
'id': {'type': 'string'},
26+
'name': {'type': 'string'},
27+
'description': {'type': 'string'},
28+
'entry': {'type': 'string'},
29+
'language': {'type': 'string'},
30+
'expected_return_value': {'type': 'number'},
31+
},
32+
'required': ['id', 'name', 'entry'],
33+
},
34+
},
35+
},
36+
'required': ['hooks'],
37+
}
38+
39+
40+
def check_is_valid_manifest(file_contents):
41+
file_objects = yaml.load(file_contents)
42+
43+
jsonschema.validate(file_objects, MANIFEST_JSON_SCHEMA)
44+
45+
for hook_config in file_objects['hooks']:
46+
language = hook_config.get('language')
47+
48+
if language is not None and not any(
49+
language.startswith(lang) for lang in C.SUPPORTED_LANGUAGES
50+
):
51+
raise InvalidManifestError(
52+
'Expected language {0} for {1} to start with one of {2!r}'.format(
53+
hook_config['id'],
54+
hook_config['language'],
55+
C.SUPPORTED_LANGUAGES,
56+
)
57+
)
58+
59+
60+
61+
def run(argv):
62+
parser = argparse.ArgumentParser()
63+
parser.add_argument(
64+
'--filename',
65+
required=False, default=None,
66+
help='Manifest filename. Defaults to {0} at root of git repo'.format(
67+
C.MANIFEST_FILE,
68+
)
69+
)
70+
args = parser.parse_args(argv)
71+
72+
if args.filename is None:
73+
# TODO: filename = git.get_root() + C.MANIFEST_FILE
74+
raise NotImplementedError
75+
else:
76+
filename = args.filename
77+
78+
if not os.path.exists(filename):
79+
print('File {0} does not exist'.format(filename))
80+
return 1
81+
82+
file_contents = open(filename, 'r').read()
83+
84+
try:
85+
yaml.load(file_contents)
86+
except Exception as e:
87+
print('File {0} is not a valid yaml file'.format(filename))
88+
print(str(e))
89+
return 1
90+
91+
try:
92+
check_is_valid_manifest(file_contents)
93+
except (jsonschema.exceptions.ValidationError, InvalidManifestError) as e:
94+
print('File {0} is not a valid manifest file'.format(filename))
95+
print(str(e))
96+
return 1
97+
98+
return 0

pre_commit/constants.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,11 @@
22
PRE_COMMIT_FILE = '.pre-commit-config.yaml'
33

44
PRE_COMMIT_DIR = '.pre-commit-files'
5+
6+
MANIFEST_FILE = 'manifest.yaml'
7+
8+
SUPPORTED_LANGUAGES = [
9+
'python',
10+
'ruby',
11+
'node',
12+
]

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
argparse
2+
jsonschema
23
pyyaml
34
simplejson
45

scripts/validate-manifest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env python
2+
3+
if __name__ == '__main__':
4+
import sys
5+
6+
from pre_commit.clientlib.validate_manifest import run
7+
8+
sys.exit(run(sys.argv[1:]))

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
packages=find_packages('.', exclude=('tests*', 'testing*')),
88
install_requires=[
99
'argparse',
10+
'jsonschema',
11+
'pyyaml',
1012
'simplejson',
1113
],
1214
scripts=[
1315
'scripts/pre-commit.py',
16+
'scripts/validate-manifest.py',
1417
],
1518
)

tests/clientlib/__init__.py

Whitespace-only changes.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
2+
import __builtin__
3+
import jsonschema
4+
import pytest
5+
import mock
6+
7+
from pre_commit.clientlib.validate_manifest import check_is_valid_manifest
8+
from pre_commit.clientlib.validate_manifest import InvalidManifestError
9+
from pre_commit.clientlib.validate_manifest import run
10+
11+
12+
@pytest.yield_fixture
13+
def print_mock():
14+
with mock.patch.object(__builtin__, 'print', autospec=True) as print_mock_obj:
15+
yield print_mock_obj
16+
17+
18+
def test_run_returns_1_for_non_existent_module(print_mock):
19+
non_existent_filename = 'file_that_does_not_exist'
20+
ret = run(['--filename', non_existent_filename])
21+
assert ret == 1
22+
print_mock.assert_called_once_with(
23+
'File {0} does not exist'.format(non_existent_filename),
24+
)
25+
26+
27+
def test_run_returns_1_for_non_yaml_file(print_mock):
28+
non_parseable_filename = 'tests/data/non_parseable_yaml_file.yaml'
29+
ret = run(['--filename', non_parseable_filename])
30+
assert ret == 1
31+
print_mock.assert_any_call(
32+
'File {0} is not a valid yaml file'.format(non_parseable_filename),
33+
)
34+
35+
36+
def test_returns_1_for_valid_yaml_file_but_invalid_manifest(print_mock):
37+
invalid_manifest = 'tests/data/valid_yaml_but_invalid_manifest.yaml'
38+
ret = run(['--filename', invalid_manifest])
39+
assert ret == 1
40+
print_mock.assert_any_call(
41+
'File {0} is not a valid manifest file'.format(invalid_manifest)
42+
)
43+
44+
45+
def test_returns_0_for_valid_manifest():
46+
valid_manifest = 'example_manifest.yaml'
47+
ret = run(['--filename', valid_manifest])
48+
assert ret == 0
49+
50+
51+
@pytest.mark.parametrize(('manifest', 'expected_exception_type'), (
52+
(
53+
"""
54+
hooks:
55+
-
56+
id: foo
57+
entry: foo
58+
""",
59+
jsonschema.exceptions.ValidationError,
60+
),
61+
(
62+
"""
63+
hooks:
64+
-
65+
id: foo
66+
name: Foo
67+
language: Not a Language lol
68+
entry: foo
69+
""",
70+
InvalidManifestError,
71+
),
72+
))
73+
def test_check_invalid_manifests(manifest, expected_exception_type):
74+
with pytest.raises(expected_exception_type):
75+
check_is_valid_manifest(manifest)
76+
77+
78+
def test_valid_manifest_is_valid():
79+
check_is_valid_manifest("""
80+
hooks:
81+
-
82+
id: foo
83+
name: Foo
84+
entry: foo
85+
language: python>2.6
86+
""")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
foo: "

0 commit comments

Comments
 (0)