Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d99bb9fec3 | ||
|
|
3c4013fda1 | ||
|
|
1a961bd4b0 | ||
|
|
7a8cfeed6d | ||
|
|
f9709bc6e6 | ||
|
|
5060917e40 | ||
|
|
a052cf7dba | ||
|
|
ae33716529 | ||
|
|
df26cc0438 | ||
|
|
342d7b49dd | ||
|
|
7d638d47b9 | ||
|
|
db116eaaaf | ||
|
|
30dfa78923 |
@@ -1,16 +1,19 @@
|
||||
---
|
||||
language: python
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.3
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
- nightly
|
||||
install:
|
||||
- pip install pyyaml flake8 coveralls
|
||||
- pip install pyyaml flake8 flake8-import-order coveralls
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi
|
||||
- pip install .
|
||||
script:
|
||||
- flake8 .
|
||||
- if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then flake8 .; fi
|
||||
- yamllint --strict $(git ls-files '*.yaml' '*.yml')
|
||||
- coverage run --source=yamllint setup.py test
|
||||
after_success:
|
||||
|
||||
15
CHANGELOG.rst
Normal file
15
CHANGELOG.rst
Normal file
@@ -0,0 +1,15 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
1.8.1 (2017-07-04)
|
||||
------------------
|
||||
|
||||
- Require pathspec >= 0.5.3
|
||||
- Support Python 2.6
|
||||
- Add a changelog
|
||||
|
||||
1.8.0 (2017-06-28)
|
||||
------------------
|
||||
|
||||
- Refactor argparse with mutually_exclusive_group
|
||||
- Add support to ignore paths in configuration
|
||||
21
README.rst
21
README.rst
@@ -119,6 +119,27 @@ or for a whole block:
|
||||
consectetur : adipiscing elit
|
||||
# yamllint enable
|
||||
|
||||
Specific files can be ignored (totally or for some rules only) using a
|
||||
``.gitignore``-style pattern:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
# 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/*
|
||||
|
||||
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`_
|
||||
|
||||
License
|
||||
|
||||
@@ -114,3 +114,57 @@ return code will be:
|
||||
* ``0`` if no errors or warnings occur
|
||||
* ``1`` if one or more errors occur
|
||||
* ``2`` if no errors occur, but one or more warnings occur
|
||||
|
||||
Ignoring paths
|
||||
--------------
|
||||
|
||||
It is possible to exclude specific files or directories, so that the linter
|
||||
doesn't process them.
|
||||
|
||||
You can either totally ignore files (they won't be looked at):
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
extends: default
|
||||
|
||||
ignore: |
|
||||
/this/specific/file.yaml
|
||||
/all/this/directory/
|
||||
*.template.yaml
|
||||
|
||||
or ignore paths only for specific rules:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
trailing-spaces:
|
||||
ignore: |
|
||||
/this-file-has-trailing-spaces-but-it-is-OK.yaml
|
||||
/generated/*.yaml
|
||||
|
||||
Note that this ``.gitignore``-style path pattern allows complex path
|
||||
exclusion/inclusion, see the `pathspec README file
|
||||
<https://pypi.python.org/pypi/pathspec>`_ for more details.
|
||||
Here is a more complex example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# For all rules
|
||||
ignore: |
|
||||
*.dont-lint-me.yaml
|
||||
/bin/
|
||||
!/bin/*.lint-me-anyway.yaml
|
||||
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
key-duplicates:
|
||||
ignore: |
|
||||
generated
|
||||
*.template.yaml
|
||||
trailing-spaces:
|
||||
ignore: |
|
||||
*.ignore-trailing-spaces.yaml
|
||||
/ascii-art/*
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[flake8]
|
||||
import-order-style = pep8
|
||||
|
||||
5
setup.py
5
setup.py
@@ -46,7 +46,6 @@ setup(
|
||||
entry_points={'console_scripts': ['yamllint=yamllint.cli:run']},
|
||||
package_data={'yamllint': ['conf/*.yaml'],
|
||||
'tests': ['yaml-1.2-spec-examples/*']},
|
||||
install_requires=['pyyaml'],
|
||||
tests_require=['nose'],
|
||||
test_suite='nose.collector',
|
||||
install_requires=['pathspec >=0.5.3', 'pyyaml'],
|
||||
test_suite='tests',
|
||||
)
|
||||
|
||||
@@ -14,7 +14,14 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import tempfile
|
||||
import sys
|
||||
try:
|
||||
assert sys.version_info >= (2, 7)
|
||||
import unittest
|
||||
except:
|
||||
import unittest2 as unittest
|
||||
|
||||
import yaml
|
||||
|
||||
@@ -49,3 +56,21 @@ class RuleTestCase(unittest.TestCase):
|
||||
|
||||
real_problems = list(linter.run(source, self.build_fake_config(conf)))
|
||||
self.assertEqual(real_problems, expected_problems)
|
||||
|
||||
|
||||
def build_temp_workspace(files):
|
||||
tempdir = tempfile.mkdtemp(prefix='yamllint-tests-')
|
||||
|
||||
for path, content in files.items():
|
||||
path = os.path.join(tempdir, path)
|
||||
if not os.path.exists(os.path.dirname(path)):
|
||||
os.makedirs(os.path.dirname(path))
|
||||
|
||||
if type(content) is list:
|
||||
os.mkdir(path)
|
||||
else:
|
||||
mode = 'wb' if isinstance(content, bytes) else 'w'
|
||||
with open(path, mode) as f:
|
||||
f.write(content)
|
||||
|
||||
return tempdir
|
||||
|
||||
@@ -23,65 +23,59 @@ import locale
|
||||
import os
|
||||
import pty
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
import sys
|
||||
try:
|
||||
assert sys.version_info >= (2, 7)
|
||||
import unittest
|
||||
except:
|
||||
import unittest2 as unittest
|
||||
|
||||
from yamllint import cli
|
||||
|
||||
from tests.common import build_temp_workspace
|
||||
|
||||
|
||||
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
|
||||
class CommandLineTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.wd = tempfile.mkdtemp(prefix='yamllint-tests-')
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(CommandLineTestCase, cls).setUpClass()
|
||||
|
||||
# .yaml file at root
|
||||
with open(os.path.join(self.wd, 'a.yaml'), 'w') as f:
|
||||
f.write('---\n'
|
||||
'- 1 \n'
|
||||
'- 2')
|
||||
cls.wd = build_temp_workspace({
|
||||
# .yaml file at root
|
||||
'a.yaml': '---\n'
|
||||
'- 1 \n'
|
||||
'- 2',
|
||||
# file with only one warning
|
||||
'warn.yaml': 'key: value\n',
|
||||
# .yml file at root
|
||||
'empty.yml': '',
|
||||
# file in dir
|
||||
'sub/ok.yaml': '---\n'
|
||||
'key: value\n',
|
||||
# file in very nested dir
|
||||
's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml': '---\n'
|
||||
'key: value\n'
|
||||
'key: other value\n',
|
||||
# empty dir
|
||||
'empty-dir': [],
|
||||
# non-YAML file
|
||||
'no-yaml.json': '---\n'
|
||||
'key: value\n',
|
||||
# non-ASCII chars
|
||||
'non-ascii/utf-8': (
|
||||
u'---\n'
|
||||
u'- hétérogénéité\n'
|
||||
u'# 19.99 €\n'
|
||||
u'- お早う御座います。\n'
|
||||
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
|
||||
})
|
||||
|
||||
# file with only one warning
|
||||
with open(os.path.join(self.wd, 'warn.yaml'), 'w') as f:
|
||||
f.write('key: value\n')
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super(CommandLineTestCase, cls).tearDownClass()
|
||||
|
||||
# .yml file at root
|
||||
open(os.path.join(self.wd, 'empty.yml'), 'w').close()
|
||||
|
||||
# file in dir
|
||||
os.mkdir(os.path.join(self.wd, 'sub'))
|
||||
with open(os.path.join(self.wd, 'sub', 'ok.yaml'), 'w') as f:
|
||||
f.write('---\n'
|
||||
'key: value\n')
|
||||
|
||||
# file in very nested dir
|
||||
dir = self.wd
|
||||
for i in range(15):
|
||||
dir = os.path.join(dir, 's')
|
||||
os.mkdir(dir)
|
||||
with open(os.path.join(dir, 'file.yaml'), 'w') as f:
|
||||
f.write('---\n'
|
||||
'key: value\n'
|
||||
'key: other value\n')
|
||||
|
||||
# empty dir
|
||||
os.mkdir(os.path.join(self.wd, 'empty-dir'))
|
||||
|
||||
# non-YAML file
|
||||
with open(os.path.join(self.wd, 'no-yaml.json'), 'w') as f:
|
||||
f.write('---\n'
|
||||
'key: value\n')
|
||||
|
||||
# non-ASCII chars
|
||||
os.mkdir(os.path.join(self.wd, 'non-ascii'))
|
||||
with open(os.path.join(self.wd, 'non-ascii', 'utf-8'), 'wb') as f:
|
||||
f.write((u'---\n'
|
||||
u'- hétérogénéité\n'
|
||||
u'# 19.99 €\n'
|
||||
u'- お早う御座います。\n'
|
||||
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'))
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.wd)
|
||||
shutil.rmtree(cls.wd)
|
||||
|
||||
def test_find_files_recursively(self):
|
||||
self.assertEqual(
|
||||
@@ -145,8 +139,11 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
|
||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
||||
self.assertEqual(out, '')
|
||||
self.assertRegexpMatches(err, r'^Options --config-file and '
|
||||
r'--config-data cannot be used')
|
||||
self.assertRegexpMatches(
|
||||
err.splitlines()[-1],
|
||||
r'^yamllint: error: argument -d\/--config-data: '
|
||||
r'not allowed with argument -c\/--config-file$'
|
||||
)
|
||||
|
||||
def test_run_with_bad_config(self):
|
||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||
|
||||
@@ -14,10 +14,24 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import unittest
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
try:
|
||||
assert sys.version_info >= (2, 7)
|
||||
import unittest
|
||||
except:
|
||||
import unittest2 as unittest
|
||||
|
||||
from yamllint import cli
|
||||
from yamllint import config
|
||||
|
||||
from tests.common import build_temp_workspace
|
||||
|
||||
|
||||
class SimpleConfigTestCase(unittest.TestCase):
|
||||
def test_parse_config(self):
|
||||
@@ -30,7 +44,7 @@ class SimpleConfigTestCase(unittest.TestCase):
|
||||
self.assertEqual(new.rules['colons']['max-spaces-before'], 0)
|
||||
self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
|
||||
|
||||
self.assertEqual(len(new.enabled_rules()), 1)
|
||||
self.assertEqual(len(new.enabled_rules(None)), 1)
|
||||
|
||||
def test_invalid_conf(self):
|
||||
with self.assertRaises(config.YamlLintConfigError):
|
||||
@@ -170,7 +184,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
|
||||
self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
|
||||
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
|
||||
|
||||
self.assertEqual(len(new.enabled_rules()), 2)
|
||||
self.assertEqual(len(new.enabled_rules(None)), 2)
|
||||
|
||||
def test_extend_remove_rule(self):
|
||||
old = config.YamlLintConfig('rules:\n'
|
||||
@@ -187,7 +201,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
|
||||
self.assertEqual(new.rules['colons'], False)
|
||||
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
|
||||
|
||||
self.assertEqual(len(new.enabled_rules()), 1)
|
||||
self.assertEqual(len(new.enabled_rules(None)), 1)
|
||||
|
||||
def test_extend_edit_rule(self):
|
||||
old = config.YamlLintConfig('rules:\n'
|
||||
@@ -207,7 +221,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
|
||||
self.assertEqual(new.rules['colons']['max-spaces-after'], 4)
|
||||
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
|
||||
|
||||
self.assertEqual(len(new.enabled_rules()), 2)
|
||||
self.assertEqual(len(new.enabled_rules(None)), 2)
|
||||
|
||||
def test_extend_reenable_rule(self):
|
||||
old = config.YamlLintConfig('rules:\n'
|
||||
@@ -225,7 +239,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
|
||||
self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
|
||||
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
|
||||
|
||||
self.assertEqual(len(new.enabled_rules()), 2)
|
||||
self.assertEqual(len(new.enabled_rules(None)), 2)
|
||||
|
||||
|
||||
class ExtendedLibraryConfigTestCase(unittest.TestCase):
|
||||
@@ -270,3 +284,94 @@ class ExtendedLibraryConfigTestCase(unittest.TestCase):
|
||||
self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys()))
|
||||
for rule in new.rules:
|
||||
self.assertEqual(new.rules[rule], old.rules[rule])
|
||||
|
||||
|
||||
class IgnorePathConfigTestCase(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(IgnorePathConfigTestCase, cls).setUpClass()
|
||||
|
||||
bad_yaml = ('---\n'
|
||||
'- key: val1\n'
|
||||
' key: val2\n'
|
||||
'- trailing space \n'
|
||||
'- lonely hyphen\n')
|
||||
|
||||
cls.wd = build_temp_workspace({
|
||||
'bin/file.lint-me-anyway.yaml': bad_yaml,
|
||||
'bin/file.yaml': bad_yaml,
|
||||
'file-at-root.yaml': bad_yaml,
|
||||
'file.dont-lint-me.yaml': bad_yaml,
|
||||
'ign-dup/file.yaml': bad_yaml,
|
||||
'ign-dup/sub/dir/file.yaml': bad_yaml,
|
||||
'ign-trail/file.yaml': bad_yaml,
|
||||
'include/ign-dup/sub/dir/file.yaml': bad_yaml,
|
||||
'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()
|
||||
os.chdir(cls.wd)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super(IgnorePathConfigTestCase, cls).tearDownClass()
|
||||
|
||||
os.chdir(cls.backup_wd)
|
||||
|
||||
shutil.rmtree(cls.wd)
|
||||
|
||||
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
|
||||
def test_run_with_ignored_path(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,
|
||||
'./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,
|
||||
)))
|
||||
|
||||
@@ -15,8 +15,12 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import io
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
try:
|
||||
assert sys.version_info >= (2, 7)
|
||||
import unittest
|
||||
except:
|
||||
import unittest2 as unittest
|
||||
|
||||
from yamllint.config import YamlLintConfig
|
||||
from yamllint import linter
|
||||
|
||||
@@ -18,9 +18,15 @@ import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
import sys
|
||||
try:
|
||||
assert sys.version_info >= (2, 7)
|
||||
import unittest
|
||||
except:
|
||||
import unittest2 as unittest
|
||||
|
||||
|
||||
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
|
||||
class ModuleTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.wd = tempfile.mkdtemp(prefix='yamllint-tests-')
|
||||
|
||||
@@ -14,7 +14,12 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
try:
|
||||
assert sys.version_info >= (2, 7)
|
||||
import unittest
|
||||
except:
|
||||
import unittest2 as unittest
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ indentation, etc."""
|
||||
|
||||
|
||||
APP_NAME = 'yamllint'
|
||||
APP_VERSION = '1.7.0'
|
||||
APP_VERSION = '1.8.1'
|
||||
APP_DESCRIPTION = __doc__
|
||||
|
||||
__author__ = u'Adrien Vergé'
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
@@ -78,11 +79,13 @@ def run(argv=None):
|
||||
description=APP_DESCRIPTION)
|
||||
parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+',
|
||||
help='files to check')
|
||||
parser.add_argument('-c', '--config-file', dest='config_file',
|
||||
action='store', help='path to a custom configuration')
|
||||
parser.add_argument('-d', '--config-data', dest='config_data',
|
||||
action='store',
|
||||
help='custom configuration (as YAML source)')
|
||||
config_group = parser.add_mutually_exclusive_group()
|
||||
config_group.add_argument('-c', '--config-file', dest='config_file',
|
||||
action='store',
|
||||
help='path to a custom configuration')
|
||||
config_group.add_argument('-d', '--config-data', dest='config_data',
|
||||
action='store',
|
||||
help='custom configuration (as YAML source)')
|
||||
parser.add_argument('-f', '--format',
|
||||
choices=('parsable', 'standard'), default='standard',
|
||||
help='format for parsing output')
|
||||
@@ -97,11 +100,6 @@ def run(argv=None):
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if args.config_file is not None and args.config_data is not None:
|
||||
print('Options --config-file and --config-data cannot be used '
|
||||
'simultaneously.', file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
# User-global config is supposed to be in ~/.config/yamllint/config
|
||||
if 'XDG_CONFIG_HOME' in os.environ:
|
||||
user_global_config = os.path.join(
|
||||
@@ -129,10 +127,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