diff --git a/docs/configuration.rst b/docs/configuration.rst index 3c1e0c8..c1d979c 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -190,6 +190,20 @@ Here is a more complex example: *.ignore-trailing-spaces.yaml ascii-art/* +You can also use the ``.gitignore`` file (or any list of files) through: + +.. code-block:: yaml + + ignore-from-file: .gitignore + +or: + +.. code-block:: yaml + + ignore-from-file: [.gitignore, .yamlignore] + +.. note:: However, this is mutually exclusive with the ``ignore`` key. + Setting the locale ------------------ diff --git a/tests/test_config.py b/tests/test_config.py index c84be1e..1e6c1c7 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -22,6 +22,7 @@ import unittest from tests.common import build_temp_workspace +from yamllint.config import YamlLintConfigError from yamllint import cli from yamllint import config @@ -429,10 +430,10 @@ class ExtendedLibraryConfigTestCase(unittest.TestCase): self.assertEqual(new.rules['empty-lines']['max-end'], 0) -class IgnorePathConfigTestCase(unittest.TestCase): +class IgnoreConfigTestCase(unittest.TestCase): @classmethod def setUpClass(cls): - super(IgnorePathConfigTestCase, cls).setUpClass() + super().setUpClass() bad_yaml = ('---\n' '- key: val1\n' @@ -452,22 +453,6 @@ class IgnorePathConfigTestCase(unittest.TestCase): 's/s/ign-trail/file.yaml': bad_yaml, 's/s/ign-trail/s/s/file.yaml': bad_yaml, 's/s/ign-trail/s/s/file2.lint-me-anyway.yaml': bad_yaml, - - '.yamllint': 'ignore: |\n' - ' *.dont-lint-me.yaml\n' - ' /bin/\n' - ' !/bin/*.lint-me-anyway.yaml\n' - '\n' - 'extends: default\n' - '\n' - 'rules:\n' - ' key-duplicates:\n' - ' ignore: |\n' - ' /ign-dup\n' - ' trailing-spaces:\n' - ' ignore: |\n' - ' ign-trail\n' - ' !*.lint-me-anyway.yaml\n', }) cls.backup_wd = os.getcwd() @@ -475,13 +460,201 @@ class IgnorePathConfigTestCase(unittest.TestCase): @classmethod def tearDownClass(cls): - super(IgnorePathConfigTestCase, cls).tearDownClass() + super().tearDownClass() os.chdir(cls.backup_wd) shutil.rmtree(cls.wd) - def test_run_with_ignored_path(self): + def test_mutually_exclusive_ignore_keys(self): + self.assertRaises( + YamlLintConfigError, + config.YamlLintConfig, 'extends: default\n' + 'ignore-from-file: .gitignore\n' + 'ignore: |\n' + ' *.dont-lint-me.yaml\n' + ' /bin/\n') + + def test_ignore_from_file_not_exist(self): + self.assertRaises( + FileNotFoundError, + config.YamlLintConfig, 'extends: default\n' + 'ignore-from-file: not_found_file\n') + + def test_ignore_from_file_incorrect_type(self): + self.assertRaises( + YamlLintConfigError, + config.YamlLintConfig, 'extends: default\n' + 'ignore-from-file: 0\n') + self.assertRaises( + YamlLintConfigError, + config.YamlLintConfig, 'extends: default\n' + 'ignore-from-file: [0]\n') + + def test_no_ignore(self): + sys.stdout = StringIO() + with self.assertRaises(SystemExit): + cli.run(('-f', 'parsable', '.')) + + out = sys.stdout.getvalue() + out = '\n'.join(sorted(out.splitlines())) + + keydup = '[error] duplication of key "key" in mapping (key-duplicates)' + trailing = '[error] trailing spaces (trailing-spaces)' + hyphen = '[error] too many spaces after hyphen (hyphens)' + + self.assertEqual(out, '\n'.join(( + './bin/file.lint-me-anyway.yaml:3:3: ' + keydup, + './bin/file.lint-me-anyway.yaml:4:17: ' + trailing, + './bin/file.lint-me-anyway.yaml:5:5: ' + hyphen, + './bin/file.yaml:3:3: ' + keydup, + './bin/file.yaml:4:17: ' + trailing, + './bin/file.yaml:5:5: ' + hyphen, + './file-at-root.yaml:3:3: ' + keydup, + './file-at-root.yaml:4:17: ' + trailing, + './file-at-root.yaml:5:5: ' + hyphen, + './file.dont-lint-me.yaml:3:3: ' + keydup, + './file.dont-lint-me.yaml:4:17: ' + trailing, + './file.dont-lint-me.yaml:5:5: ' + hyphen, + './ign-dup/file.yaml:3:3: ' + keydup, + './ign-dup/file.yaml:4:17: ' + trailing, + './ign-dup/file.yaml:5:5: ' + hyphen, + './ign-dup/sub/dir/file.yaml:3:3: ' + keydup, + './ign-dup/sub/dir/file.yaml:4:17: ' + trailing, + './ign-dup/sub/dir/file.yaml:5:5: ' + hyphen, + './ign-trail/file.yaml:3:3: ' + keydup, + './ign-trail/file.yaml:4:17: ' + trailing, + './ign-trail/file.yaml:5:5: ' + hyphen, + './include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup, + './include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing, + './include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen, + './s/s/ign-trail/file.yaml:3:3: ' + keydup, + './s/s/ign-trail/file.yaml:4:17: ' + trailing, + './s/s/ign-trail/file.yaml:5:5: ' + hyphen, + './s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup, + './s/s/ign-trail/s/s/file.yaml:4:17: ' + trailing, + './s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen, + './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup, + './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing, + './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen, + ))) + + def test_run_with_ignore(self): + with open(os.path.join(self.wd, '.yamllint'), 'w') as f: + f.write('extends: default\n' + 'ignore: |\n' + ' *.dont-lint-me.yaml\n' + ' /bin/\n' + ' !/bin/*.lint-me-anyway.yaml\n' + 'rules:\n' + ' key-duplicates:\n' + ' ignore: |\n' + ' /ign-dup\n' + ' trailing-spaces:\n' + ' ignore: |\n' + ' ign-trail\n' + ' !*.lint-me-anyway.yaml\n') + + sys.stdout = StringIO() + with self.assertRaises(SystemExit): + cli.run(('-f', 'parsable', '.')) + + out = sys.stdout.getvalue() + out = '\n'.join(sorted(out.splitlines())) + + docstart = '[warning] missing document start "---" (document-start)' + keydup = '[error] duplication of key "key" in mapping (key-duplicates)' + trailing = '[error] trailing spaces (trailing-spaces)' + hyphen = '[error] too many spaces after hyphen (hyphens)' + + self.assertEqual(out, '\n'.join(( + './.yamllint:1:1: ' + docstart, + './bin/file.lint-me-anyway.yaml:3:3: ' + keydup, + './bin/file.lint-me-anyway.yaml:4:17: ' + trailing, + './bin/file.lint-me-anyway.yaml:5:5: ' + hyphen, + './file-at-root.yaml:3:3: ' + keydup, + './file-at-root.yaml:4:17: ' + trailing, + './file-at-root.yaml:5:5: ' + hyphen, + './ign-dup/file.yaml:4:17: ' + trailing, + './ign-dup/file.yaml:5:5: ' + hyphen, + './ign-dup/sub/dir/file.yaml:4:17: ' + trailing, + './ign-dup/sub/dir/file.yaml:5:5: ' + hyphen, + './ign-trail/file.yaml:3:3: ' + keydup, + './ign-trail/file.yaml:5:5: ' + hyphen, + './include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup, + './include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing, + './include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen, + './s/s/ign-trail/file.yaml:3:3: ' + keydup, + './s/s/ign-trail/file.yaml:5:5: ' + hyphen, + './s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup, + './s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen, + './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup, + './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing, + './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen, + ))) + + def test_run_with_ignore_from_file(self): + with open(os.path.join(self.wd, '.yamllint'), 'w') as f: + f.write('extends: default\n' + 'ignore-from-file: .gitignore\n') + with open(os.path.join(self.wd, '.gitignore'), 'w') as f: + f.write('*.dont-lint-me.yaml\n' + '/bin/\n' + '!/bin/*.lint-me-anyway.yaml\n') + + sys.stdout = StringIO() + with self.assertRaises(SystemExit): + cli.run(('-f', 'parsable', '.')) + + out = sys.stdout.getvalue() + out = '\n'.join(sorted(out.splitlines())) + + docstart = '[warning] missing document start "---" (document-start)' + keydup = '[error] duplication of key "key" in mapping (key-duplicates)' + trailing = '[error] trailing spaces (trailing-spaces)' + hyphen = '[error] too many spaces after hyphen (hyphens)' + + self.assertEqual(out, '\n'.join(( + './.yamllint:1:1: ' + docstart, + './bin/file.lint-me-anyway.yaml:3:3: ' + keydup, + './bin/file.lint-me-anyway.yaml:4:17: ' + trailing, + './bin/file.lint-me-anyway.yaml:5:5: ' + hyphen, + './file-at-root.yaml:3:3: ' + keydup, + './file-at-root.yaml:4:17: ' + trailing, + './file-at-root.yaml:5:5: ' + hyphen, + './ign-dup/file.yaml:3:3: ' + keydup, + './ign-dup/file.yaml:4:17: ' + trailing, + './ign-dup/file.yaml:5:5: ' + hyphen, + './ign-dup/sub/dir/file.yaml:3:3: ' + keydup, + './ign-dup/sub/dir/file.yaml:4:17: ' + trailing, + './ign-dup/sub/dir/file.yaml:5:5: ' + hyphen, + './ign-trail/file.yaml:3:3: ' + keydup, + './ign-trail/file.yaml:4:17: ' + trailing, + './ign-trail/file.yaml:5:5: ' + hyphen, + './include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup, + './include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing, + './include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen, + './s/s/ign-trail/file.yaml:3:3: ' + keydup, + './s/s/ign-trail/file.yaml:4:17: ' + trailing, + './s/s/ign-trail/file.yaml:5:5: ' + hyphen, + './s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup, + './s/s/ign-trail/s/s/file.yaml:4:17: ' + trailing, + './s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen, + './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup, + './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing, + './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen, + ))) + + def test_run_with_ignored_from_file(self): + with open(os.path.join(self.wd, '.yamllint'), 'w') as f: + f.write('ignore-from-file: [.gitignore, .yamlignore]\n' + 'extends: default\n') + with open(os.path.join(self.wd, '.gitignore'), 'w') as f: + f.write('*.dont-lint-me.yaml\n' + '/bin/\n') + with open(os.path.join(self.wd, '.yamlignore'), 'w') as f: + f.write('!/bin/*.lint-me-anyway.yaml\n') + sys.stdout = StringIO() with self.assertRaises(SystemExit): cli.run(('-f', 'parsable', '.')) @@ -502,18 +675,23 @@ class IgnorePathConfigTestCase(unittest.TestCase): './file-at-root.yaml:3:3: ' + keydup, './file-at-root.yaml:4:17: ' + trailing, './file-at-root.yaml:5:5: ' + hyphen, + './ign-dup/file.yaml:3:3: ' + keydup, './ign-dup/file.yaml:4:17: ' + trailing, './ign-dup/file.yaml:5:5: ' + hyphen, + './ign-dup/sub/dir/file.yaml:3:3: ' + keydup, './ign-dup/sub/dir/file.yaml:4:17: ' + trailing, './ign-dup/sub/dir/file.yaml:5:5: ' + hyphen, './ign-trail/file.yaml:3:3: ' + keydup, + './ign-trail/file.yaml:4:17: ' + trailing, './ign-trail/file.yaml:5:5: ' + hyphen, './include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup, './include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing, './include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen, './s/s/ign-trail/file.yaml:3:3: ' + keydup, + './s/s/ign-trail/file.yaml:4:17: ' + trailing, './s/s/ign-trail/file.yaml:5:5: ' + hyphen, './s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup, + './s/s/ign-trail/s/s/file.yaml:4:17: ' + trailing, './s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen, './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup, './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing, diff --git a/yamllint/config.py b/yamllint/config.py index 939fe4a..3bee791 100644 --- a/yamllint/config.py +++ b/yamllint/config.py @@ -13,6 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import fileinput import os.path import pathspec @@ -96,7 +97,21 @@ class YamlLintConfig: except Exception as e: raise YamlLintConfigError('invalid config: %s' % e) - if 'ignore' in conf: + if 'ignore' in conf and 'ignore-from-file' in conf: + raise YamlLintConfigError( + 'invalid config: ignore and ignore-from-file keys cannot be ' + 'used together') + elif 'ignore-from-file' in conf: + if isinstance(conf['ignore-from-file'], str): + conf['ignore-from-file'] = [conf['ignore-from-file']] + if not (isinstance(conf['ignore-from-file'], list) and all( + isinstance(ln, str) for ln in conf['ignore-from-file'])): + raise YamlLintConfigError( + 'invalid config: ignore-from-file should contain ' + 'filename(s), either as a list or string') + with fileinput.input(conf['ignore-from-file']) as f: + self.ignore = pathspec.PathSpec.from_lines('gitwildmatch', f) + elif 'ignore' in conf: if not isinstance(conf['ignore'], str): raise YamlLintConfigError( 'invalid config: ignore should contain file patterns') @@ -150,7 +165,7 @@ def validate_rule_conf(rule, conf): options = getattr(rule, 'CONF', {}) options_default = getattr(rule, 'DEFAULT', {}) for optkey in conf: - if optkey in ('ignore', 'level'): + if optkey in ('ignore', 'ignore-from-file', 'level'): continue if optkey not in options: raise YamlLintConfigError(