feat(config): Add support to ignore paths on per-rule basis
Example of configuration to use this feature:
# For all rules
ignore: |
*.dont-lint-me.yaml
/bin/
!/bin/*.lint-me-anyway.yaml
rules:
key-duplicates:
ignore: |
generated
*.template.yaml
trailing-spaces:
ignore: |
*.ignore-trailing-spaces.yaml
/ascii-art/*
Closes #43.
This commit is contained in:
@@ -126,10 +126,11 @@ def run(argv=None):
|
||||
max_level = 0
|
||||
|
||||
for file in find_files_recursively(args.files):
|
||||
filepath = file[2:] if file.startswith('./') else file
|
||||
try:
|
||||
first = True
|
||||
with open(file) as f:
|
||||
for problem in linter.run(f, conf):
|
||||
for problem in linter.run(f, conf, filepath):
|
||||
if args.format == 'parsable':
|
||||
print(Format.parsable(problem, file))
|
||||
elif sys.stdout.isatty():
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import os.path
|
||||
|
||||
import pathspec
|
||||
import yaml
|
||||
|
||||
import yamllint.rules
|
||||
@@ -29,6 +30,8 @@ class YamlLintConfig(object):
|
||||
def __init__(self, content=None, file=None):
|
||||
assert (content is None) ^ (file is None)
|
||||
|
||||
self.ignore = None
|
||||
|
||||
if file is not None:
|
||||
with open(file) as f:
|
||||
content = f.read()
|
||||
@@ -36,9 +39,14 @@ class YamlLintConfig(object):
|
||||
self.parse(content)
|
||||
self.validate()
|
||||
|
||||
def enabled_rules(self):
|
||||
def is_file_ignored(self, filepath):
|
||||
return self.ignore and self.ignore.match_file(filepath)
|
||||
|
||||
def enabled_rules(self, filepath):
|
||||
return [yamllint.rules.get(id) for id, val in self.rules.items()
|
||||
if val is not False]
|
||||
if val is not False and (
|
||||
filepath is None or 'ignore' not in val or
|
||||
not val['ignore'].match_file(filepath))]
|
||||
|
||||
def extend(self, base_config):
|
||||
assert isinstance(base_config, YamlLintConfig)
|
||||
@@ -53,6 +61,9 @@ class YamlLintConfig(object):
|
||||
|
||||
self.rules = base_config.rules
|
||||
|
||||
if base_config.ignore is not None:
|
||||
self.ignore = base_config.ignore
|
||||
|
||||
def parse(self, raw_content):
|
||||
try:
|
||||
conf = yaml.safe_load(raw_content)
|
||||
@@ -73,6 +84,13 @@ class YamlLintConfig(object):
|
||||
except Exception as e:
|
||||
raise YamlLintConfigError('invalid config: %s' % e)
|
||||
|
||||
if 'ignore' in conf:
|
||||
if type(conf['ignore']) != str:
|
||||
raise YamlLintConfigError(
|
||||
'invalid config: ignore should be a list of patterns')
|
||||
self.ignore = pathspec.PathSpec.from_lines(
|
||||
'gitwildmatch', conf['ignore'].splitlines())
|
||||
|
||||
def validate(self):
|
||||
for id in self.rules:
|
||||
try:
|
||||
@@ -90,6 +108,14 @@ def validate_rule_conf(rule, conf):
|
||||
conf = {}
|
||||
|
||||
if type(conf) == dict:
|
||||
if ('ignore' in conf and
|
||||
type(conf['ignore']) != pathspec.pathspec.PathSpec):
|
||||
if type(conf['ignore']) != str:
|
||||
raise YamlLintConfigError(
|
||||
'invalid config: ignore should be a list of patterns')
|
||||
conf['ignore'] = pathspec.PathSpec.from_lines(
|
||||
'gitwildmatch', conf['ignore'].splitlines())
|
||||
|
||||
if 'level' not in conf:
|
||||
conf['level'] = 'error'
|
||||
elif conf['level'] not in ('error', 'warning'):
|
||||
@@ -98,7 +124,7 @@ def validate_rule_conf(rule, conf):
|
||||
|
||||
options = getattr(rule, 'CONF', {})
|
||||
for optkey in conf:
|
||||
if optkey == 'level':
|
||||
if optkey in ('ignore', 'level'):
|
||||
continue
|
||||
if optkey not in options:
|
||||
raise YamlLintConfigError(
|
||||
|
||||
@@ -63,8 +63,8 @@ class LintProblem(object):
|
||||
return '%d:%d: %s' % (self.line, self.column, self.message)
|
||||
|
||||
|
||||
def get_cosmetic_problems(buffer, conf):
|
||||
rules = conf.enabled_rules()
|
||||
def get_cosmetic_problems(buffer, conf, filepath):
|
||||
rules = conf.enabled_rules(filepath)
|
||||
|
||||
# Split token rules from line rules
|
||||
token_rules = [r for r in rules if r.TYPE == 'token']
|
||||
@@ -185,7 +185,7 @@ def get_syntax_error(buffer):
|
||||
return problem
|
||||
|
||||
|
||||
def _run(buffer, conf):
|
||||
def _run(buffer, conf, filepath):
|
||||
assert hasattr(buffer, '__getitem__'), \
|
||||
'_run() argument must be a buffer, not a stream'
|
||||
|
||||
@@ -193,7 +193,7 @@ def _run(buffer, conf):
|
||||
# right line
|
||||
syntax_error = get_syntax_error(buffer)
|
||||
|
||||
for problem in get_cosmetic_problems(buffer, conf):
|
||||
for problem in get_cosmetic_problems(buffer, conf, filepath):
|
||||
# Insert the syntax error (if any) at the right place...
|
||||
if (syntax_error and syntax_error.line <= problem.line and
|
||||
syntax_error.column <= problem.column):
|
||||
@@ -215,7 +215,7 @@ def _run(buffer, conf):
|
||||
yield syntax_error
|
||||
|
||||
|
||||
def run(input, conf):
|
||||
def run(input, conf, filepath=None):
|
||||
"""Lints a YAML source.
|
||||
|
||||
Returns a generator of LintProblem objects.
|
||||
@@ -223,11 +223,14 @@ def run(input, conf):
|
||||
:param input: buffer, string or stream to read from
|
||||
:param conf: yamllint configuration object
|
||||
"""
|
||||
if conf.is_file_ignored(filepath):
|
||||
return ()
|
||||
|
||||
if type(input) in (type(b''), type(u'')): # compat with Python 2 & 3
|
||||
return _run(input, conf)
|
||||
return _run(input, conf, filepath)
|
||||
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
|
||||
# We need to have everything in memory to parse correctly
|
||||
content = input.read()
|
||||
return _run(content, conf)
|
||||
return _run(content, conf, filepath)
|
||||
else:
|
||||
raise TypeError('input should be a string or a stream')
|
||||
|
||||
Reference in New Issue
Block a user