Compare commits

...

43 Commits

Author SHA1 Message Date
Adrien Vergé
dca3a54e63 yamllint version 1.2.0 2016-03-06 17:04:05 +01:00
Adrien Vergé
2dcfbd7e0d Conf: relaxed: Remove unneeded lines 2016-03-06 17:04:05 +01:00
Adrien Vergé
73d7a608e8 Conf: relaxed: Re-enable hyphens (in warning) 2016-03-06 17:04:05 +01:00
Adrien Vergé
1c0f164fbf Conf: relaxed: Set indentation's indent-sequences=consistent 2016-03-06 17:01:18 +01:00
Adrien Vergé
46e9108419 Rules: indentation: Add 'consistent' option for 'indent-sequences'
Using `indent-sequences: consistent` allows block sequences to be
indented or not to be, as long as it remains the same within the file.
2016-03-06 15:42:16 +01:00
Adrien Vergé
2f9e3cc71b Conf: relaxed: Set indentation to warning level 2016-03-06 08:26:09 +01:00
Adrien Vergé
b13a03815a Conf: default: Use spaces: consistent for indentation 2016-03-06 08:26:09 +01:00
Adrien Vergé
9a7eec34b1 Rules: indentation: Fix spaces: consitent with broken flows 2016-03-06 08:26:09 +01:00
Adrien Vergé
5b62548ece Tests: indentation: Use 'spaces: consistent' by default 2016-03-06 08:26:09 +01:00
Adrien Vergé
8fca8a7a33 Config: Allow 'enable' keyword for rules
In the same manner as 'disable', 'enable' allows setting a rule on
without worrying about its options.
2016-03-06 08:00:25 +01:00
Adrien Vergé
69ef9a7272 Conf: relaxed: Set max line-length back to 80
Because 80 has been the default for years. But keep it as a warning, not
an error.
2016-03-06 07:42:49 +01:00
Adrien Vergé
d8d1d92545 yamllint version 1.1.0 2016-03-04 17:03:38 +01:00
Adrien Vergé
7688567faa cli: Add the -d option to provide inline conf 2016-03-04 16:53:26 +01:00
Adrien Vergé
4e188f8801 Conf: Add a new pre-defined conf 'relaxed'
It is more tolerant than 'default'.
2016-03-04 16:50:40 +01:00
Adrien Vergé
5693b1dddf Rules: indentation: Add 'consistent' option for 'spaces'
Using `spaces: consistent` allows any number of spaces, as long as it
remains the same within the file.
2016-03-04 16:03:53 +01:00
Adrien Vergé
fa420499c7 Config: Allow types in multiple choices
For instance, allow rules with:

    CONF = {'choice': (int, 'hardcoded-string'),
            'string-or-bool': (str, bool)}
2016-03-04 16:03:46 +01:00
Adrien Vergé
adefe38a0d yamllint version 1.0.4 2016-03-04 12:48:31 +01:00
Adrien Vergé
7e11082353 Distribution: Restore spec examples in package_data
Put `tests/yaml-1.2-spec-examples/*` back in `setup.py`'s `package_data`
because they need to be installed when running `python setup.py build`,
so Debian packaging script `dh_auto_test -O--buildsystem=pybuild`
doesn't fail.

See also commit e6dc67f.
2016-03-04 12:33:56 +01:00
Adrien Vergé
29c1c60143 Tests: Use absolute path to spec examples 2016-03-04 12:15:26 +01:00
Adrien Vergé
b879e9a98f Distribution: Add LICENSE and README to manifest 2016-02-26 09:57:06 +01:00
Adrien Vergé
5956b20545 yamllint version 1.0.3 2016-02-25 14:48:13 +01:00
Adrien Vergé
10ad302e2f Tests: Explicit encoding for spec examples
YAML specification examples contain unusual characters, let's explicit
`encoding='utf-8'` to prevent bugs.
2016-02-25 10:44:05 +01:00
Adrien Vergé
73d9322813 linter: Test run on str, unicode, bytes and stream
Previously it was not tested, and broke on Python 2 `unicode` inputs.
2016-02-25 10:41:17 +01:00
Adrien Vergé
ca0ebe4583 yamllint version 1.0.2 2016-02-24 21:21:02 +01:00
Adrien Vergé
e6dc67fd0a Distribution: Add MANIFEST.in
`yamllint/conf/*.yml` remains in `setup.py`'s `package_data` because it
needs to be installed when running `pip install .`.

`docs/*` and `tests/yaml-1.2-spec-examples/*` just need to be packaged,
they can go in the manifest.
2016-02-24 21:18:48 +01:00
Adrien Vergé
611a560082 yamllint version 1.0.1 2016-02-19 19:39:52 +01:00
Adrien Vergé
83384fa4cf Doc: Fix man page redundant description 2016-02-19 19:34:20 +01:00
Adrien Vergé
3ab3784a75 cli: Remove shebang
A shebang is present at the beginning of file, it dates from the time
when `yamllint/cli.py` was `bin/yamllint`, i.e. an executable launcher.
Since this is not the case anymore (see `entry_points` section in
`setup.py`), let's remove it.
2016-02-19 19:17:49 +01:00
Adrien Vergé
2f75e92a66 Doc: Add a configuration example in README 2016-02-19 10:37:52 +01:00
Adrien Vergé
64caa95b6a yamllint version 1.0.0 2016-02-19 10:15:23 +01:00
Adrien Vergé
fff09fa2df Distribution: Ship example files from spec in sdist
Closes: #1
2016-02-19 10:14:59 +01:00
Adrien Vergé
316bee8c98 yamllint version 0.7.2 2016-02-05 11:28:15 +01:00
Adrien Vergé
6c8af97a40 Tests: unblacklist remaining spec examples
Since !!tags are now supported.
2016-02-05 11:14:37 +01:00
Adrien Vergé
647d84ff94 Rules: indentation: Handle tags 2016-02-05 11:13:44 +01:00
Adrien Vergé
8eb0d0ad74 Tests: unblacklist spec example 7.16
As is it supported -- it just lacks some indentation.
2016-02-05 09:52:09 +01:00
Adrien Vergé
4bc3d5a01c Rules: indentation: Handle anchors 2016-02-04 22:10:40 +01:00
Adrien Vergé
48c7d65c54 parser: Provide nextnext for token rules
Because the indentation rule sometimes needs to look two tokens forward
(in case of anchors for instance).
2016-02-04 22:10:40 +01:00
Adrien Vergé
62fa4cbe39 Tests: indentation: Test the indent stack
The "indentation stack" is iteratively built by the `check()` function
of the indentation rule. It is important, since everything in the rule
relies on it.

This patch adds tests to make sure the stack is correctly built for some
known structures.
2016-02-04 22:10:40 +01:00
Adrien Vergé
8d38d349ac Rules: indentation: Rewrite stack generation
"Indentation stack" generation was not done properly, hence did not work
in all cases. This commit does a cleaner rewriting.
2016-02-04 21:47:08 +01:00
Adrien Vergé
3f264806b9 yamllint version 0.7.1 2016-02-03 14:43:09 +01:00
Adrien Vergé
9a82b99d4b Rules: indentation: Fix multi-line flows
To detect this as correct indentations:

    top:
      rules: [
        {
          foo: 1
        },
        {
          foo: 2
          bar: [
            a, b, c
          ],
        },
      ]
2016-02-03 12:05:22 +01:00
Adrien Vergé
ba140ad42c Tests: Remove ghost character from YAML spec example 2016-02-01 23:27:49 +01:00
Adrien Vergé
0e04ee29e6 Doc: Update description 2016-02-01 23:03:25 +01:00
33 changed files with 1337 additions and 223 deletions

3
MANIFEST.in Normal file
View File

@@ -0,0 +1,3 @@
include LICENSE
include README.rst
include docs/*

View File

@@ -3,8 +3,9 @@ yamllint
A linter for YAML files.
yamllint does not only check for syntax validity, but for common cosmetic
conventions such as lines length, trailing spaces, indentation, etc.
yamllint does not only check for syntax validity, but for weirdnesses like key
repetition and cosmetic problems such as lines length, trailing spaces,
indentation, etc.
.. image::
https://travis-ci.org/adrienverge/yamllint.svg?branch=master
@@ -56,6 +57,9 @@ Usage
.. code:: bash
# Use a pre-defined lint configuration
yamllint -d relaxed file.yml
# Use a custom lint configuration
yamllint -c ~/myconfig file.yml
@@ -63,3 +67,19 @@ Usage
# Output a parsable format (for syntax checking in editors like Vim, emacs...)
yamllint -f parsable file.yml
Configuration example
^^^^^^^^^^^^^^^^^^^^^
.. code:: yaml
extends: default
rules:
# 80 chars should be enough, but don't fail if a line is longer
line-length:
max: 80
level: warning
# don't bother me with this rule
indentation: disable

View File

@@ -38,6 +38,5 @@ htmlhelp_basename = 'yamllintdoc'
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'yamllint', u'yamllint Documentation',
[u'Adrien Vergé'], 1)
('index', 'yamllint', '', [u'Adrien Vergé'], 1)
]

View File

@@ -8,7 +8,7 @@ settings can be gathered in a configuration file.
To use a custom configuration file, either name it ``.yamllint`` in your working
directory, or use the ``-c`` option:
::
.. code:: bash
yamllint -c ~/myconfig file.yml
@@ -22,6 +22,15 @@ Unless told otherwise, yamllint uses its ``default`` configuration:
Details on rules can be found on :doc:`the rules page <rules>`.
There is another pre-defined configuration named ``relaxed``. As its name
suggests, it is more tolerant.
It can be chosen using:
.. code:: bash
yamllint -d relaxed file.yml
Extending the default configuration
-----------------------------------
@@ -50,7 +59,7 @@ strict on block sequences indentation:
extends: default
rules:
# 80 should be enough, but don't fail if a line is longer
# 80 chars should be enough, but don't fail if a line is longer
line-length:
max: 80
level: warning
@@ -63,6 +72,21 @@ strict on block sequences indentation:
indentation:
indent-sequences: whatever
Custom configuration without a config file
------------------------------------------
It is possible -- although not recommended -- to pass custom configuration
options to yamllint with the ``-d`` (short for ``--config-data``) option.
Its content can either be the name of a pre-defined conf (example: ``default``
or ``relaxed``) or a serialized YAML object describing the configuration.
For instance:
.. code:: bash
yamllint -d "{extends: relaxed, rules: {line-length: {max: 120}}}" file.yml
Errors and warnings
-------------------

View File

@@ -1,10 +1,7 @@
yamllint documentation
======================
A linter for YAML files.
yamllint does not only check for syntax validity, but for common cosmetic
conventions such as lines length, trailing spaces, indentation, etc.
.. automodule:: yamllint
Screenshot
----------

View File

@@ -30,7 +30,7 @@ setup(
keywords=['yaml', 'lint', 'linter', 'syntax', 'checker'],
url='https://github.com/adrienverge/yamllint',
classifiers=[
'Development Status :: 4 - Beta',
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
@@ -44,7 +44,8 @@ setup(
packages=find_packages(),
entry_points={'console_scripts': ['yamllint=yamllint.cli:run']},
package_data={'yamllint': ['conf/*.yml']},
package_data={'yamllint': ['conf/*.yml'],
'tests': ['yaml-1.2-spec-examples/*']},
install_requires=['pyyaml'],
tests_require=['nose'],
test_suite='nose.collector',

View File

@@ -49,7 +49,7 @@ class CommentsIndentationTestCase(RuleTestCase):
'...\n', conf)
def test_enabled(self):
conf = 'comments-indentation: {}'
conf = 'comments-indentation: enable'
self.check('---\n'
'# line 1\n'
'# line 2\n', conf)
@@ -123,18 +123,18 @@ class CommentsIndentationTestCase(RuleTestCase):
'...\n', conf)
def test_first_line(self):
conf = 'comments-indentation: {}'
conf = 'comments-indentation: enable'
self.check('# comment\n', conf)
self.check(' # comment\n', conf, problem=(1, 3))
def test_no_newline_at_end(self):
conf = ('comments-indentation: {}\n'
conf = ('comments-indentation: enable\n'
'new-line-at-end-of-file: disable\n')
self.check('# comment', conf)
self.check(' # comment', conf, problem=(1, 3))
def test_empty_comment(self):
conf = 'comments-indentation: {}'
conf = 'comments-indentation: enable'
self.check('---\n'
'# hey\n'
'# normal\n'

File diff suppressed because it is too large Load Diff

View File

@@ -80,7 +80,7 @@ class KeyDuplicatesTestCase(RuleTestCase):
': 1\n', conf)
def test_enabled(self):
conf = 'key-duplicates: {}'
conf = 'key-duplicates: enable'
self.check('---\n'
'block mapping:\n'
' key: a\n'
@@ -149,7 +149,7 @@ class KeyDuplicatesTestCase(RuleTestCase):
problem4=(7, 3))
def test_key_tokens_in_flow_sequences(self):
conf = 'key-duplicates: {}'
conf = 'key-duplicates: enable'
self.check('---\n'
'[\n'
' flow: sequence, with, key: value, mappings\n'

View File

@@ -30,7 +30,7 @@ class NewLineAtEndOfFileTestCase(RuleTestCase):
self.check('Sentence.\n', conf)
def test_enabled(self):
conf = ('new-line-at-end-of-file: {}\n'
conf = ('new-line-at-end-of-file: enable\n'
'empty-lines: disable\n'
'document-start: disable\n')
self.check('', conf)

View File

@@ -29,7 +29,7 @@ class TrailingSpacesTestCase(RuleTestCase):
'some: text \n', conf)
def test_enabled(self):
conf = 'trailing-spaces: {}'
conf = 'trailing-spaces: enable'
self.check('', conf)
self.check('\n', conf)
self.check(' \n', conf, problem=(1, 1))
@@ -40,7 +40,7 @@ class TrailingSpacesTestCase(RuleTestCase):
'some: text\t\n', conf, problem=(2, 11, 'syntax'))
def test_with_dos_new_lines(self):
conf = ('trailing-spaces: {}\n'
conf = ('trailing-spaces: enable\n'
'new-lines: {type: dos}\n')
self.check('---\r\n'
'some: text\r\n', conf)

View File

@@ -37,7 +37,7 @@ class SimpleConfigTestCase(unittest.TestCase):
config.YamlLintConfigError,
'invalid config: no such rule: "this-one-does-not-exist"'):
config.YamlLintConfig('rules:\n'
' this-one-does-not-exist: {}\n')
' this-one-does-not-exist: enable\n')
def test_missing_option(self):
with self.assertRaisesRegexp(
@@ -58,6 +58,58 @@ class SimpleConfigTestCase(unittest.TestCase):
' max-spaces-after: 1\n'
' abcdef: yes\n')
def test_validate_rule_conf(self):
class Rule(object):
ID = 'fake'
self.assertEqual(config.validate_rule_conf(Rule, False), False)
self.assertEqual(config.validate_rule_conf(Rule, 'disable'), False)
self.assertEqual(config.validate_rule_conf(Rule, {}),
{'level': 'error'})
self.assertEqual(config.validate_rule_conf(Rule, 'enable'),
{'level': 'error'})
config.validate_rule_conf(Rule, {'level': 'error'})
config.validate_rule_conf(Rule, {'level': 'warning'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'level': 'warn'})
Rule.CONF = {'length': int}
config.validate_rule_conf(Rule, {'length': 8})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'height': 8})
Rule.CONF = {'a': bool, 'b': int}
config.validate_rule_conf(Rule, {'a': True, 'b': 0})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'a': True})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'b': 0})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'a': 1, 'b': 0})
Rule.CONF = {'choice': (True, 88, 'str')}
config.validate_rule_conf(Rule, {'choice': True})
config.validate_rule_conf(Rule, {'choice': 88})
config.validate_rule_conf(Rule, {'choice': 'str'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': False})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 99})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 'abc'})
Rule.CONF = {'choice': (int, 'hardcoded')}
config.validate_rule_conf(Rule, {'choice': 42})
config.validate_rule_conf(Rule, {'choice': 'hardcoded'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': False})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 'abc'})
class ExtendedConfigTestCase(unittest.TestCase):
def test_extend_add_rule(self):

46
tests/test_linter.py Normal file
View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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 io
import unittest
from yamllint.config import YamlLintConfig
from yamllint import linter
class LinterTestCase(unittest.TestCase):
def fake_config(self):
return YamlLintConfig('extends: default')
def test_run_on_string(self):
linter.run('test: document', self.fake_config())
def test_run_on_bytes(self):
linter.run(b'test: document', self.fake_config())
def test_run_on_unicode(self):
linter.run(u'test: document', self.fake_config())
def test_run_on_stream(self):
linter.run(io.StringIO(u'hello'), self.fake_config())
def test_run_on_int(self):
self.assertRaises(TypeError, linter.run, 42, self.fake_config())
def test_run_on_list(self):
self.assertRaises(TypeError, linter.run,
['h', 'e', 'l', 'l', 'o'], self.fake_config())

View File

@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from io import open
import os
from tests.common import RuleTestCase
@@ -35,6 +36,7 @@ from tests.common import RuleTestCase
# for br in span.find_all("br"):
# br.replace_with("\n")
# text = text.replace('\u2193', '') # downwards arrow
# text = text.replace('\u21d3', '') # double downwards arrow
# text = text.replace('\u00b7', ' ') # visible space
# text = text.replace('\u21d4', '') # byte order mark
# text = text.replace('\u2192', '\t') # right arrow
@@ -95,6 +97,7 @@ conf_overrides = {
'example-7.15': ('braces: {min-spaces-inside: 0, max-spaces-inside: 1}\n'
'commas: {max-spaces-before: 1, min-spaces-after: 0}\n'
'colons: {max-spaces-before: 1}\n'),
'example-7.16': ('indentation: disable\n'),
'example-7.17': ('indentation: disable\n'),
'example-7.18': ('indentation: disable\n'),
'example-7.19': ('indentation: disable\n'),
@@ -111,9 +114,15 @@ conf_overrides = {
'example-8.14': ('colons: {max-spaces-before: 1}\n'),
'example-8.16': ('indentation: {spaces: 1}\n'),
'example-8.17': ('indentation: disable\n'),
'example-8.20': ('indentation: {indent-sequences: no}\n'
'colons: {max-spaces-before: 1}\n'),
'example-8.22': ('indentation: disable\n'),
'example-10.1': ('colons: {max-spaces-before: 2}\n'),
'example-10.2': ('indentation: {indent-sequences: no}\n'),
}
files = os.listdir('tests/yaml-1.2-spec-examples')
files = os.listdir(os.path.join(os.path.dirname(os.path.realpath(__file__)),
'yaml-1.2-spec-examples'))
assert len(files) == 132
@@ -122,15 +131,6 @@ def _gen_test(buffer, conf):
self.check(buffer, conf)
return test
# TODO
# The following tests are blacklisted because they contain rarely-used formats
# that yamllint does not handle yet.
tmp_blacklist = (
'example-7.16',
'example-8.20',
'example-8.22',
'example-10.1',
)
# The following tests are blacklisted (i.e. will not be checked against
# yamllint), because pyyaml is currently not able to parse the contents
# (using yaml.parse()).
@@ -177,10 +177,10 @@ pyyaml_blacklist = (
)
for file in files:
if file in tmp_blacklist or file in pyyaml_blacklist:
if file in pyyaml_blacklist:
continue
with open('tests/yaml-1.2-spec-examples/' + file) as f:
with open('tests/yaml-1.2-spec-examples/' + file, encoding='utf-8') as f:
conf = conf_general + conf_overrides.get(file, '')
setattr(SpecificationTestCase, 'test_' + file,
_gen_test(f.read(), conf))

View File

@@ -2,7 +2,7 @@
# Comments:
strip: |-
# text
# Clip
# comments:

View File

@@ -14,12 +14,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
APP_NAME = 'yamllint'
APP_VERSION = '0.7.0'
APP_DESCRIPTION = """A linter for YAML files.
"""A linter for YAML files.
yamllint does not only check for syntax validity, but for common cosmetic
conventions such as lines length, trailing spaces, indentation, etc."""
yamllint does not only check for syntax validity, but for weirdnesses like key
repetition and cosmetic problems such as lines length, trailing spaces,
indentation, etc."""
APP_NAME = 'yamllint'
APP_VERSION = '1.2.0'
APP_DESCRIPTION = __doc__
__author__ = u'Adrien Vergé'
__copyright__ = u'Copyright 2016, Adrien Vergé'

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
@@ -67,8 +66,11 @@ 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', dest='config_file', action='store',
help='path to a custom configuration')
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)')
parser.add_argument('-f', '--format',
choices=('parsable', 'standard'), default='standard',
help='format for parsing output')
@@ -79,8 +81,17 @@ 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)
try:
if args.config_file is not None:
if args.config_data is not None:
if ':' not in args.config_data:
args.config_data = 'extends: ' + args.config_data
conf = YamlLintConfig(content=args.config_data)
elif args.config_file is not None:
conf = YamlLintConfig(file=args.config_file)
elif os.path.isfile('.yamllint'):
conf = YamlLintConfig(file='.yamllint')

View File

@@ -31,14 +31,14 @@ rules:
hyphens:
max-spaces-after: 1
indentation:
spaces: 2
spaces: consistent
indent-sequences: yes
check-multi-line-strings: no
key-duplicates: {}
key-duplicates: enable
line-length:
max: 80
allow-non-breakable-words: yes
new-line-at-end-of-file: {level: error}
new-line-at-end-of-file: enable
new-lines:
type: unix
trailing-spaces: {}
trailing-spaces: enable

27
yamllint/conf/relaxed.yml Normal file
View File

@@ -0,0 +1,27 @@
---
extends: default
rules:
braces:
level: warning
max-spaces-inside: 1
brackets:
level: warning
max-spaces-inside: 1
colons:
level: warning
commas:
level: warning
comments: disable
comments-indentation: disable
document-start: disable
empty-lines:
level: warning
hyphens:
level: warning
indentation:
level: warning
indent-sequences: consistent
line-length:
level: warning

View File

@@ -83,6 +83,8 @@ class YamlLintConfig(object):
def validate_rule_conf(rule, conf):
if conf is False or conf == 'disable':
return False
elif conf == 'enable':
conf = {}
if type(conf) == dict:
if 'level' not in conf:
@@ -100,7 +102,8 @@ def validate_rule_conf(rule, conf):
'invalid config: unknown option "%s" for rule "%s"' %
(optkey, rule.ID))
if type(options[optkey]) == tuple:
if conf[optkey] not in options[optkey]:
if (conf[optkey] not in options[optkey] and
type(conf[optkey]) not in options[optkey]):
raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be in %s'
% (optkey, rule.ID, options[optkey]))
@@ -116,7 +119,8 @@ def validate_rule_conf(rule, conf):
(optkey, rule.ID))
else:
raise YamlLintConfigError(('invalid config: rule "%s": should be '
'either "disable" or a dict') % rule.ID)
'either "enable", "disable" or a dict')
% rule.ID)
return conf

View File

@@ -68,6 +68,7 @@ def get_costemic_problems(buffer, conf):
rule_conf = conf.rules[rule.ID]
for problem in rule.check(rule_conf,
elem.curr, elem.prev, elem.next,
elem.nextnext,
context[rule.ID]):
problem.rule = rule.ID
problem.level = rule_conf['level']
@@ -127,7 +128,7 @@ def run(input, conf):
:param input: buffer, string or stream to read from
:param conf: yamllint configuration object
"""
if type(input) == str:
if type(input) in (type(b''), type(u'')): # compat with Python 2 & 3
return _run(input, conf)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
# We need to have everything in memory to parse correctly

View File

@@ -30,11 +30,12 @@ class Line(object):
class Token(object):
def __init__(self, line_no, curr, prev, next):
def __init__(self, line_no, curr, prev, next, nextnext):
self.line_no = line_no
self.curr = curr
self.prev = prev
self.next = next
self.nextnext = nextnext
def line_generator(buffer):
@@ -55,14 +56,16 @@ def token_generator(buffer):
try:
prev = None
next = yaml_loader.peek_token()
while next is not None:
curr = yaml_loader.get_token()
next = yaml_loader.peek_token()
curr = yaml_loader.get_token()
while curr is not None:
next = yaml_loader.get_token()
nextnext = yaml_loader.peek_token()
yield Token(curr.start_mark.line + 1, curr, prev, next)
yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext)
prev = curr
curr = next
except yaml.scanner.ScannerError:
pass

View File

@@ -73,7 +73,7 @@ CONF = {'min-spaces-inside': int,
'max-spaces-inside': int}
def check(conf, token, prev, next, context):
def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.FlowMappingStartToken):
problem = spaces_after(token, prev, next,
min=conf['min-spaces-inside'],

View File

@@ -74,7 +74,7 @@ CONF = {'min-spaces-inside': int,
'max-spaces-inside': int}
def check(conf, token, prev, next, context):
def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.FlowSequenceStartToken):
problem = spaces_after(token, prev, next,
min=conf['min-spaces-inside'],

View File

@@ -81,7 +81,7 @@ CONF = {'max-spaces-before': int,
'max-spaces-after': int}
def check(conf, token, prev, next, context):
def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.ValueToken):
problem = spaces_before(token, prev, next,
max=conf['max-spaces-before'],

View File

@@ -105,7 +105,7 @@ CONF = {'max-spaces-before': int,
'max-spaces-after': int}
def check(conf, token, prev, next, context):
def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.FlowEntryToken):
if (prev is not None and conf['max-spaces-before'] != -1 and
prev.end_mark.line < token.start_mark.line):

View File

@@ -67,7 +67,7 @@ CONF = {'require-starting-space': bool,
'min-spaces-from-content': int}
def check(conf, token, prev, next, context):
def check(conf, token, prev, next, nextnext, context):
for comment in get_comments_between_tokens(token, next):
if (conf['min-spaces-from-content'] != -1 and
not isinstance(token, yaml.StreamStartToken) and

View File

@@ -98,7 +98,7 @@ TYPE = 'token'
# # commented line 2
# current: line
def check(conf, token, prev, next, context):
def check(conf, token, prev, next, nextnext, context):
if prev is None:
return

View File

@@ -84,7 +84,7 @@ TYPE = 'token'
CONF = {'present': bool}
def check(conf, token, prev, next, context):
def check(conf, token, prev, next, nextnext, context):
if conf['present']:
if (isinstance(token, yaml.StreamEndToken) and
not (isinstance(prev, yaml.DocumentEndToken) or

View File

@@ -74,7 +74,7 @@ TYPE = 'token'
CONF = {'present': bool}
def check(conf, token, prev, next, context):
def check(conf, token, prev, next, nextnext, context):
if conf['present']:
if (isinstance(prev, (yaml.StreamStartToken,
yaml.DocumentEndToken,

View File

@@ -78,7 +78,7 @@ TYPE = 'token'
CONF = {'max-spaces-after': int}
def check(conf, token, prev, next, context):
def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.BlockEntryToken):
problem = spaces_after(token, prev, next,
max=conf['max-spaces-after'],

View File

@@ -19,12 +19,16 @@ Use this rule to control the indentation.
.. rubric:: Options
* ``spaces`` defines the number of spaces that represent an indentation level.
* ``spaces`` defines the indentation width, in spaces. Set either to an integer
(e.g. ``2`` or ``4``, representing the number of spaces in an indentation
level) or to ``consistent`` to allow any number, as long as it remains the
same within the file.
* ``indent-sequences`` defines whether block sequences should be indented or
not (when in a mapping, this indentation is not mandatory -- some people
perceive the ``-`` as part of the indentation). Possible values: ``yes``,
``no`` and ``whatever`` (the latter means either indenting or not indenting
block sequences is OK.
``no``, ``whatever`` and ``consistent``. ``consistent`` requires either all
block sequences to be indented, or none to be. ``whatever`` means either
indenting or not indenting individual block sequences is OK.
* ``check-multi-line-strings`` defines whether to lint indentation in
multi-line strings. Set to ``yes`` to enable, ``no`` to disable.
@@ -73,6 +77,28 @@ Use this rule to control the indentation.
- haystack:
needle
#. With ``indentation: {spaces: consistent}``
the following code snippet would **PASS**:
::
history:
- name: Unix
date: 1969
- name: Linux
date: 1991
nest:
recurse:
- haystack:
needle
the following code snippet would **FAIL**:
::
some:
Russian:
dolls
#. With ``indentation: {spaces: 2, indent-sequences: no}``
the following code snippet would **PASS**:
@@ -104,6 +130,28 @@ Use this rule to control the indentation.
- spaghetti
- sauce
#. With ``indentation: {spaces: 2, indent-sequences: consistent}``
the following code snippet would **PASS**:
::
- flying:
- spaghetti
- monster
- not flying:
- spaghetti
- sauce
the following code snippet would **FAIL**:
::
- flying:
- spaghetti
- monster
- not flying:
- spaghetti
- sauce
#. With ``indentation: {spaces: 4, check-multi-line-strings: yes}``
the following code snippet would **PASS**:
@@ -150,63 +198,77 @@ from yamllint.rules.common import is_explicit_key, get_real_end_line
ID = 'indentation'
TYPE = 'token'
CONF = {'spaces': int,
'indent-sequences': (True, False, 'whatever'),
CONF = {'spaces': (int, 'consistent'),
'indent-sequences': (bool, 'whatever', 'consistent'),
'check-multi-line-strings': bool}
ROOT, MAP, B_SEQ, F_SEQ, B_ENT, KEY, VAL = range(7)
ROOT, B_MAP, F_MAP, B_SEQ, F_SEQ, B_ENT, KEY, VAL = range(8)
labels = ('ROOT', 'B_MAP', 'F_MAP', 'B_SEQ', 'F_SEQ', 'B_ENT', 'KEY', 'VAL')
class Parent(object):
def __init__(self, type, indent):
def __init__(self, type, indent, line_indent=None):
self.type = type
self.indent = indent
self.line_indent = line_indent
self.explicit_key = False
self.implicit_block_seq = False
def __repr__(self):
return '%s:%d' % (labels[self.type], self.indent)
def check_scalar_indentation(conf, token, context):
if token.start_mark.line == token.end_mark.line:
return
if token.plain:
expected_indent = token.start_mark.column
elif token.style in ('"', "'"):
expected_indent = token.start_mark.column + 1
elif token.style in ('>', '|'):
if context['stack'][-1].type == B_SEQ:
# - >
# multi
# line
expected_indent = token.start_mark.column + conf['spaces']
elif context['stack'][-1].type == KEY:
assert context['stack'][-1].explicit_key
# - ? >
# multi-line
# key
# : >
# multi-line
# value
expected_indent = token.start_mark.column + conf['spaces']
elif context['stack'][-1].type == VAL:
if token.start_mark.line + 1 > context['cur_line']:
# - key:
# >
# multi
# line
expected_indent = context['stack'][-1].indent + conf['spaces']
elif context['stack'][-2].explicit_key:
# - ? key
def compute_expected_indent(found_indent):
def detect_indent(base_indent):
if type(context['spaces']) is not int:
context['spaces'] = found_indent - base_indent
return base_indent + context['spaces']
if token.plain:
return token.start_mark.column
elif token.style in ('"', "'"):
return token.start_mark.column + 1
elif token.style in ('>', '|'):
if context['stack'][-1].type == B_SEQ:
# - >
# multi
# line
return detect_indent(token.start_mark.column)
elif context['stack'][-1].type == KEY:
assert context['stack'][-1].explicit_key
# - ? >
# multi-line
# key
# : >
# multi-line
# value
expected_indent = token.start_mark.column + conf['spaces']
return detect_indent(token.start_mark.column)
elif context['stack'][-1].type == VAL:
if token.start_mark.line + 1 > context['cur_line']:
# - key:
# >
# multi
# line
return detect_indent(context['stack'][-1].indent)
elif context['stack'][-2].explicit_key:
# - ? key
# : >
# multi-line
# value
return detect_indent(token.start_mark.column)
else:
# - key: >
# multi
# line
return detect_indent(context['stack'][-2].indent)
else:
# - key: >
# multi
# line
expected_indent = context['stack'][-2].indent + conf['spaces']
else:
expected_indent = context['stack'][-1].indent + conf['spaces']
return detect_indent(context['stack'][-1].indent)
expected_indent = None
line_no = token.start_mark.line + 1
@@ -224,16 +286,21 @@ def check_scalar_indentation(conf, token, context):
if token.start_mark.buffer[line_start + indent] == '\n':
continue
if expected_indent is None:
expected_indent = compute_expected_indent(indent)
if indent != expected_indent:
yield LintProblem(line_no, indent + 1,
'wrong indentation: expected %d but found %d' %
(expected_indent, indent))
def check(conf, token, prev, next, context):
def check(conf, token, prev, next, nextnext, context):
if 'stack' not in context:
context['stack'] = [Parent(ROOT, 0)]
context['cur_line'] = -1
context['spaces'] = conf['spaces']
context['indent-sequences'] = conf['indent-sequences']
# Step 1: Lint
@@ -244,17 +311,22 @@ def check(conf, token, prev, next, context):
first_in_line = (is_visible and
token.start_mark.line + 1 > context['cur_line'])
def detect_indent(base_indent, next):
if type(context['spaces']) is not int:
context['spaces'] = next.start_mark.column - base_indent
return base_indent + context['spaces']
if first_in_line:
found_indentation = token.start_mark.column
expected = context['stack'][-1].indent
if isinstance(token, (yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)):
expected = 0
expected = context['stack'][-1].line_indent
elif (context['stack'][-1].type == KEY and
context['stack'][-1].explicit_key and
not isinstance(token, yaml.ValueToken)):
expected += conf['spaces']
expected = detect_indent(expected, token)
if found_indentation != expected:
yield LintProblem(token.start_mark.line + 1, found_indentation + 1,
@@ -275,9 +347,6 @@ def check(conf, token, prev, next, context):
# Step 2.b: Update state
if context['stack'][-1].type == B_ENT:
context['stack'].pop()
if isinstance(token, yaml.BlockMappingStartToken):
assert isinstance(next, yaml.KeyToken)
if next.start_mark.line == token.start_mark.line:
@@ -291,9 +360,9 @@ def check(conf, token, prev, next, context):
# - ?
# a
# : 1
indent = token.start_mark.column + conf['spaces']
indent = detect_indent(token.start_mark.column, next)
context['stack'].append(Parent(MAP, indent))
context['stack'].append(Parent(B_MAP, indent))
elif isinstance(token, yaml.FlowMappingStartToken):
if next.start_mark.line == token.start_mark.line:
@@ -301,11 +370,12 @@ def check(conf, token, prev, next, context):
indent = next.start_mark.column
else:
# - {
# a: 1, b: 2
# }
indent = context['cur_line_indent'] + conf['spaces']
# a: 1, b: 2
# }
indent = detect_indent(context['cur_line_indent'], next)
context['stack'].append(Parent(MAP, indent))
context['stack'].append(Parent(F_MAP, indent,
line_indent=context['cur_line_indent']))
elif isinstance(token, yaml.BlockSequenceStartToken):
# - - a
@@ -320,6 +390,12 @@ def check(conf, token, prev, next, context):
elif (isinstance(token, yaml.BlockEntryToken) and
# in case of an empty entry
not isinstance(next, (yaml.BlockEntryToken, yaml.BlockEndToken))):
# It looks like pyyaml doesn't issue BlockSequenceStartTokens when the
# list is not indented. We need to compensate that.
if context['stack'][-1].type != B_SEQ:
context['stack'].append(Parent(B_SEQ, token.start_mark.column))
context['stack'][-1].implicit_block_seq = True
if next.start_mark.line == token.end_mark.line:
# - item 1
# - item 2
@@ -330,7 +406,7 @@ def check(conf, token, prev, next, context):
# -
# key:
# value
indent = token.start_mark.column + conf['spaces']
indent = detect_indent(token.start_mark.column, next)
context['stack'].append(Parent(B_ENT, indent))
@@ -342,15 +418,10 @@ def check(conf, token, prev, next, context):
# - [
# a, b
# ]
indent = context['cur_line_indent'] + conf['spaces']
indent = detect_indent(context['cur_line_indent'], next)
context['stack'].append(Parent(F_SEQ, indent))
elif isinstance(token, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)):
assert context['stack'][-1].type in (MAP, B_SEQ, F_SEQ)
context['stack'].pop()
context['stack'].append(Parent(F_SEQ, indent,
line_indent=context['cur_line_indent']))
elif isinstance(token, yaml.KeyToken):
indent = context['stack'][-1].indent
@@ -359,21 +430,25 @@ def check(conf, token, prev, next, context):
context['stack'][-1].explicit_key = is_explicit_key(token)
if context['stack'][-1].type == VAL:
context['stack'].pop()
assert context['stack'][-1].type == KEY
context['stack'].pop()
elif isinstance(token, yaml.ValueToken):
assert context['stack'][-1].type == KEY
# Discard empty values
if isinstance(next, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken,
yaml.KeyToken)):
context['stack'].pop()
else:
# Special cases:
# key: &anchor
# value
# and:
# key: !!tag
# value
if isinstance(next, (yaml.AnchorToken, yaml.TagToken)):
if (next.start_mark.line == prev.start_mark.line and
next.start_mark.line < nextnext.start_mark.line):
next = nextnext
# Only if value is not empty
if not isinstance(next, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken,
yaml.KeyToken)):
if context['stack'][-1].explicit_key:
# ? k
# : value
@@ -381,7 +456,7 @@ def check(conf, token, prev, next, context):
# ? k
# :
# value
indent = context['stack'][-1].indent + conf['spaces']
indent = detect_indent(context['stack'][-1].indent, next)
elif next.start_mark.line == prev.start_mark.line:
# k: value
indent = next.start_mark.column
@@ -392,33 +467,77 @@ def check(conf, token, prev, next, context):
# yaml.scan()ning this:
# '- lib:\n'
# ' - var\n'
if conf['indent-sequences'] is False:
if context['indent-sequences'] is False:
indent = context['stack'][-1].indent
elif conf['indent-sequences'] is True:
indent = context['stack'][-1].indent + conf['spaces']
else: # 'whatever'
elif context['indent-sequences'] is True:
indent = detect_indent(context['stack'][-1].indent, next)
else: # 'whatever' or 'consistent'
if next.start_mark.column == context['stack'][-1].indent:
# key:
# - e1
# - e2
if context['indent-sequences'] == 'consistent':
context['indent-sequences'] = False
indent = context['stack'][-1].indent
else:
if context['indent-sequences'] == 'consistent':
context['indent-sequences'] = True
# key:
# - e1
# - e2
indent = context['stack'][-1].indent + conf['spaces']
indent = detect_indent(context['stack'][-1].indent,
next)
else:
# k:
# value
indent = context['stack'][-1].indent + conf['spaces']
indent = detect_indent(context['stack'][-1].indent, next)
context['stack'].append(Parent(VAL, indent))
if (context['stack'][-1].type == KEY and
isinstance(next, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken,
yaml.KeyToken))):
# A key without a value: it's part of a set. Let's drop this key
# and leave room for the next one.
context['stack'].pop()
consumed_current_token = False
while True:
if (context['stack'][-1].type == F_SEQ and
isinstance(token, yaml.FlowSequenceEndToken)):
context['stack'].pop()
elif (context['stack'][-1].type == F_MAP and
isinstance(token, yaml.FlowMappingEndToken)):
context['stack'].pop()
elif (context['stack'][-1].type in (B_MAP, B_SEQ) and
isinstance(token, yaml.BlockEndToken) and
not context['stack'][-1].implicit_block_seq and
not consumed_current_token):
context['stack'].pop()
consumed_current_token = True
elif (context['stack'][-1].type == B_ENT and
not isinstance(token, yaml.BlockEntryToken) and
context['stack'][-2].implicit_block_seq and
not isinstance(token, (yaml.AnchorToken, yaml.TagToken)) and
not isinstance(next, yaml.BlockEntryToken)):
context['stack'].pop()
context['stack'].pop()
elif (context['stack'][-1].type == B_ENT and
isinstance(next, (yaml.BlockEntryToken, yaml.BlockEndToken))):
context['stack'].pop()
elif (context['stack'][-1].type == VAL and
not isinstance(token, yaml.ValueToken) and
not isinstance(token, (yaml.AnchorToken, yaml.TagToken))):
assert context['stack'][-2].type == KEY
context['stack'].pop()
context['stack'].pop()
elif (context['stack'][-1].type == KEY and
isinstance(next, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken,
yaml.KeyToken))):
# A key without a value: it's part of a set. Let's drop this key
# and leave room for the next one.
context['stack'].pop()
else:
break

View File

@@ -72,7 +72,7 @@ class Parent(object):
self.keys = []
def check(conf, token, prev, next, context):
def check(conf, token, prev, next, nextnext, context):
if 'stack' not in context:
context['stack'] = []