Skip to content

Commit 2f8ad70

Browse files
ndrwnaguibadrienverge
authored andcommitted
config: Implement for ignore-from-file option
Closes #360 Co-authored-by: Adrien Vergé <@adrienverge>
1 parent fb0c0a5 commit 2f8ad70

File tree

3 files changed

+229
-22
lines changed

3 files changed

+229
-22
lines changed

docs/configuration.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,20 @@ Here is a more complex example:
190190
*.ignore-trailing-spaces.yaml
191191
ascii-art/*
192192
193+
You can also use the ``.gitignore`` file (or any list of files) through:
194+
195+
.. code-block:: yaml
196+
197+
ignore-from-file: .gitignore
198+
199+
or:
200+
201+
.. code-block:: yaml
202+
203+
ignore-from-file: [.gitignore, .yamlignore]
204+
205+
.. note:: However, this is mutually exclusive with the ``ignore`` key.
206+
193207
Setting the locale
194208
------------------
195209

tests/test_config.py

Lines changed: 198 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from tests.common import build_temp_workspace
2424

25+
from yamllint.config import YamlLintConfigError
2526
from yamllint import cli
2627
from yamllint import config
2728

@@ -429,10 +430,10 @@ def test_extend_config_override_rule_partly(self):
429430
self.assertEqual(new.rules['empty-lines']['max-end'], 0)
430431

431432

432-
class IgnorePathConfigTestCase(unittest.TestCase):
433+
class IgnoreConfigTestCase(unittest.TestCase):
433434
@classmethod
434435
def setUpClass(cls):
435-
super(IgnorePathConfigTestCase, cls).setUpClass()
436+
super().setUpClass()
436437

437438
bad_yaml = ('---\n'
438439
'- key: val1\n'
@@ -452,36 +453,208 @@ def setUpClass(cls):
452453
's/s/ign-trail/file.yaml': bad_yaml,
453454
's/s/ign-trail/s/s/file.yaml': bad_yaml,
454455
's/s/ign-trail/s/s/file2.lint-me-anyway.yaml': bad_yaml,
455-
456-
'.yamllint': 'ignore: |\n'
457-
' *.dont-lint-me.yaml\n'
458-
' /bin/\n'
459-
' !/bin/*.lint-me-anyway.yaml\n'
460-
'\n'
461-
'extends: default\n'
462-
'\n'
463-
'rules:\n'
464-
' key-duplicates:\n'
465-
' ignore: |\n'
466-
' /ign-dup\n'
467-
' trailing-spaces:\n'
468-
' ignore: |\n'
469-
' ign-trail\n'
470-
' !*.lint-me-anyway.yaml\n',
471456
})
472457

473458
cls.backup_wd = os.getcwd()
474459
os.chdir(cls.wd)
475460

476461
@classmethod
477462
def tearDownClass(cls):
478-
super(IgnorePathConfigTestCase, cls).tearDownClass()
463+
super().tearDownClass()
479464

480465
os.chdir(cls.backup_wd)
481466

482467
shutil.rmtree(cls.wd)
483468

484-
def test_run_with_ignored_path(self):
469+
def test_mutually_exclusive_ignore_keys(self):
470+
self.assertRaises(
471+
YamlLintConfigError,
472+
config.YamlLintConfig, 'extends: default\n'
473+
'ignore-from-file: .gitignore\n'
474+
'ignore: |\n'
475+
' *.dont-lint-me.yaml\n'
476+
' /bin/\n')
477+
478+
def test_ignore_from_file_not_exist(self):
479+
self.assertRaises(
480+
FileNotFoundError,
481+
config.YamlLintConfig, 'extends: default\n'
482+
'ignore-from-file: not_found_file\n')
483+
484+
def test_ignore_from_file_incorrect_type(self):
485+
self.assertRaises(
486+
YamlLintConfigError,
487+
config.YamlLintConfig, 'extends: default\n'
488+
'ignore-from-file: 0\n')
489+
self.assertRaises(
490+
YamlLintConfigError,
491+
config.YamlLintConfig, 'extends: default\n'
492+
'ignore-from-file: [0]\n')
493+
494+
def test_no_ignore(self):
495+
sys.stdout = StringIO()
496+
with self.assertRaises(SystemExit):
497+
cli.run(('-f', 'parsable', '.'))
498+
499+
out = sys.stdout.getvalue()
500+
out = '\n'.join(sorted(out.splitlines()))
501+
502+
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
503+
trailing = '[error] trailing spaces (trailing-spaces)'
504+
hyphen = '[error] too many spaces after hyphen (hyphens)'
505+
506+
self.assertEqual(out, '\n'.join((
507+
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
508+
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
509+
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
510+
'./bin/file.yaml:3:3: ' + keydup,
511+
'./bin/file.yaml:4:17: ' + trailing,
512+
'./bin/file.yaml:5:5: ' + hyphen,
513+
'./file-at-root.yaml:3:3: ' + keydup,
514+
'./file-at-root.yaml:4:17: ' + trailing,
515+
'./file-at-root.yaml:5:5: ' + hyphen,
516+
'./file.dont-lint-me.yaml:3:3: ' + keydup,
517+
'./file.dont-lint-me.yaml:4:17: ' + trailing,
518+
'./file.dont-lint-me.yaml:5:5: ' + hyphen,
519+
'./ign-dup/file.yaml:3:3: ' + keydup,
520+
'./ign-dup/file.yaml:4:17: ' + trailing,
521+
'./ign-dup/file.yaml:5:5: ' + hyphen,
522+
'./ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
523+
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
524+
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
525+
'./ign-trail/file.yaml:3:3: ' + keydup,
526+
'./ign-trail/file.yaml:4:17: ' + trailing,
527+
'./ign-trail/file.yaml:5:5: ' + hyphen,
528+
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
529+
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
530+
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
531+
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
532+
'./s/s/ign-trail/file.yaml:4:17: ' + trailing,
533+
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
534+
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
535+
'./s/s/ign-trail/s/s/file.yaml:4:17: ' + trailing,
536+
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
537+
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
538+
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
539+
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
540+
)))
541+
542+
def test_run_with_ignore(self):
543+
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
544+
f.write('extends: default\n'
545+
'ignore: |\n'
546+
' *.dont-lint-me.yaml\n'
547+
' /bin/\n'
548+
' !/bin/*.lint-me-anyway.yaml\n'
549+
'rules:\n'
550+
' key-duplicates:\n'
551+
' ignore: |\n'
552+
' /ign-dup\n'
553+
' trailing-spaces:\n'
554+
' ignore: |\n'
555+
' ign-trail\n'
556+
' !*.lint-me-anyway.yaml\n')
557+
558+
sys.stdout = StringIO()
559+
with self.assertRaises(SystemExit):
560+
cli.run(('-f', 'parsable', '.'))
561+
562+
out = sys.stdout.getvalue()
563+
out = '\n'.join(sorted(out.splitlines()))
564+
565+
docstart = '[warning] missing document start "---" (document-start)'
566+
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
567+
trailing = '[error] trailing spaces (trailing-spaces)'
568+
hyphen = '[error] too many spaces after hyphen (hyphens)'
569+
570+
self.assertEqual(out, '\n'.join((
571+
'./.yamllint:1:1: ' + docstart,
572+
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
573+
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
574+
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
575+
'./file-at-root.yaml:3:3: ' + keydup,
576+
'./file-at-root.yaml:4:17: ' + trailing,
577+
'./file-at-root.yaml:5:5: ' + hyphen,
578+
'./ign-dup/file.yaml:4:17: ' + trailing,
579+
'./ign-dup/file.yaml:5:5: ' + hyphen,
580+
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
581+
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
582+
'./ign-trail/file.yaml:3:3: ' + keydup,
583+
'./ign-trail/file.yaml:5:5: ' + hyphen,
584+
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
585+
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
586+
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
587+
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
588+
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
589+
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
590+
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
591+
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
592+
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
593+
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
594+
)))
595+
596+
def test_run_with_ignore_from_file(self):
597+
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
598+
f.write('extends: default\n'
599+
'ignore-from-file: .gitignore\n')
600+
with open(os.path.join(self.wd, '.gitignore'), 'w') as f:
601+
f.write('*.dont-lint-me.yaml\n'
602+
'/bin/\n'
603+
'!/bin/*.lint-me-anyway.yaml\n')
604+
605+
sys.stdout = StringIO()
606+
with self.assertRaises(SystemExit):
607+
cli.run(('-f', 'parsable', '.'))
608+
609+
out = sys.stdout.getvalue()
610+
out = '\n'.join(sorted(out.splitlines()))
611+
612+
docstart = '[warning] missing document start "---" (document-start)'
613+
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
614+
trailing = '[error] trailing spaces (trailing-spaces)'
615+
hyphen = '[error] too many spaces after hyphen (hyphens)'
616+
617+
self.assertEqual(out, '\n'.join((
618+
'./.yamllint:1:1: ' + docstart,
619+
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
620+
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
621+
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
622+
'./file-at-root.yaml:3:3: ' + keydup,
623+
'./file-at-root.yaml:4:17: ' + trailing,
624+
'./file-at-root.yaml:5:5: ' + hyphen,
625+
'./ign-dup/file.yaml:3:3: ' + keydup,
626+
'./ign-dup/file.yaml:4:17: ' + trailing,
627+
'./ign-dup/file.yaml:5:5: ' + hyphen,
628+
'./ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
629+
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
630+
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
631+
'./ign-trail/file.yaml:3:3: ' + keydup,
632+
'./ign-trail/file.yaml:4:17: ' + trailing,
633+
'./ign-trail/file.yaml:5:5: ' + hyphen,
634+
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
635+
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
636+
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
637+
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
638+
'./s/s/ign-trail/file.yaml:4:17: ' + trailing,
639+
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
640+
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
641+
'./s/s/ign-trail/s/s/file.yaml:4:17: ' + trailing,
642+
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
643+
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
644+
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
645+
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
646+
)))
647+
648+
def test_run_with_ignored_from_file(self):
649+
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
650+
f.write('ignore-from-file: [.gitignore, .yamlignore]\n'
651+
'extends: default\n')
652+
with open(os.path.join(self.wd, '.gitignore'), 'w') as f:
653+
f.write('*.dont-lint-me.yaml\n'
654+
'/bin/\n')
655+
with open(os.path.join(self.wd, '.yamlignore'), 'w') as f:
656+
f.write('!/bin/*.lint-me-anyway.yaml\n')
657+
485658
sys.stdout = StringIO()
486659
with self.assertRaises(SystemExit):
487660
cli.run(('-f', 'parsable', '.'))
@@ -502,18 +675,23 @@ def test_run_with_ignored_path(self):
502675
'./file-at-root.yaml:3:3: ' + keydup,
503676
'./file-at-root.yaml:4:17: ' + trailing,
504677
'./file-at-root.yaml:5:5: ' + hyphen,
678+
'./ign-dup/file.yaml:3:3: ' + keydup,
505679
'./ign-dup/file.yaml:4:17: ' + trailing,
506680
'./ign-dup/file.yaml:5:5: ' + hyphen,
681+
'./ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
507682
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
508683
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
509684
'./ign-trail/file.yaml:3:3: ' + keydup,
685+
'./ign-trail/file.yaml:4:17: ' + trailing,
510686
'./ign-trail/file.yaml:5:5: ' + hyphen,
511687
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
512688
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
513689
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
514690
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
691+
'./s/s/ign-trail/file.yaml:4:17: ' + trailing,
515692
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
516693
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
694+
'./s/s/ign-trail/s/s/file.yaml:4:17: ' + trailing,
517695
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
518696
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
519697
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,

yamllint/config.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# You should have received a copy of the GNU General Public License
1414
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1515

16+
import fileinput
1617
import os.path
1718

1819
import pathspec
@@ -96,7 +97,21 @@ def parse(self, raw_content):
9697
except Exception as e:
9798
raise YamlLintConfigError('invalid config: %s' % e)
9899

99-
if 'ignore' in conf:
100+
if 'ignore' in conf and 'ignore-from-file' in conf:
101+
raise YamlLintConfigError(
102+
'invalid config: ignore and ignore-from-file keys cannot be '
103+
'used together')
104+
elif 'ignore-from-file' in conf:
105+
if isinstance(conf['ignore-from-file'], str):
106+
conf['ignore-from-file'] = [conf['ignore-from-file']]
107+
if not (isinstance(conf['ignore-from-file'], list) and all(
108+
isinstance(ln, str) for ln in conf['ignore-from-file'])):
109+
raise YamlLintConfigError(
110+
'invalid config: ignore-from-file should contain '
111+
'filename(s), either as a list or string')
112+
with fileinput.input(conf['ignore-from-file']) as f:
113+
self.ignore = pathspec.PathSpec.from_lines('gitwildmatch', f)
114+
elif 'ignore' in conf:
100115
if not isinstance(conf['ignore'], str):
101116
raise YamlLintConfigError(
102117
'invalid config: ignore should contain file patterns')
@@ -150,7 +165,7 @@ def validate_rule_conf(rule, conf):
150165
options = getattr(rule, 'CONF', {})
151166
options_default = getattr(rule, 'DEFAULT', {})
152167
for optkey in conf:
153-
if optkey in ('ignore', 'level'):
168+
if optkey in ('ignore', 'ignore-from-file', 'level'):
154169
continue
155170
if optkey not in options:
156171
raise YamlLintConfigError(

0 commit comments

Comments
 (0)