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:
Adrien Vergé
2017-06-06 21:52:59 +02:00
parent 342d7b49dd
commit df26cc0438
7 changed files with 222 additions and 17 deletions

View File

@@ -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():

View File

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

View File

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