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. A linter for YAML files.
yamllint does not only check for syntax validity, but for common cosmetic yamllint does not only check for syntax validity, but for weirdnesses like key
conventions such as lines length, trailing spaces, indentation, etc. repetition and cosmetic problems such as lines length, trailing spaces,
indentation, etc.
.. image:: .. image::
https://travis-ci.org/adrienverge/yamllint.svg?branch=master https://travis-ci.org/adrienverge/yamllint.svg?branch=master
@@ -56,6 +57,9 @@ Usage
.. code:: bash .. code:: bash
# Use a pre-defined lint configuration
yamllint -d relaxed file.yml
# Use a custom lint configuration # Use a custom lint configuration
yamllint -c ~/myconfig file.yml yamllint -c ~/myconfig file.yml
@@ -63,3 +67,19 @@ Usage
# Output a parsable format (for syntax checking in editors like Vim, emacs...) # Output a parsable format (for syntax checking in editors like Vim, emacs...)
yamllint -f parsable file.yml 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 # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
('index', 'yamllint', u'yamllint Documentation', ('index', 'yamllint', '', [u'Adrien Vergé'], 1)
[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 To use a custom configuration file, either name it ``.yamllint`` in your working
directory, or use the ``-c`` option: directory, or use the ``-c`` option:
:: .. code:: bash
yamllint -c ~/myconfig file.yml 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>`. 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 Extending the default configuration
----------------------------------- -----------------------------------
@@ -50,7 +59,7 @@ strict on block sequences indentation:
extends: default extends: default
rules: 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: line-length:
max: 80 max: 80
level: warning level: warning
@@ -63,6 +72,21 @@ strict on block sequences indentation:
indentation: indentation:
indent-sequences: whatever 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 Errors and warnings
------------------- -------------------

View File

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

View File

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

View File

@@ -49,7 +49,7 @@ class CommentsIndentationTestCase(RuleTestCase):
'...\n', conf) '...\n', conf)
def test_enabled(self): def test_enabled(self):
conf = 'comments-indentation: {}' conf = 'comments-indentation: enable'
self.check('---\n' self.check('---\n'
'# line 1\n' '# line 1\n'
'# line 2\n', conf) '# line 2\n', conf)
@@ -123,18 +123,18 @@ class CommentsIndentationTestCase(RuleTestCase):
'...\n', conf) '...\n', conf)
def test_first_line(self): def test_first_line(self):
conf = 'comments-indentation: {}' conf = 'comments-indentation: enable'
self.check('# comment\n', conf) self.check('# comment\n', conf)
self.check(' # comment\n', conf, problem=(1, 3)) self.check(' # comment\n', conf, problem=(1, 3))
def test_no_newline_at_end(self): def test_no_newline_at_end(self):
conf = ('comments-indentation: {}\n' conf = ('comments-indentation: enable\n'
'new-line-at-end-of-file: disable\n') 'new-line-at-end-of-file: disable\n')
self.check('# comment', conf) self.check('# comment', conf)
self.check(' # comment', conf, problem=(1, 3)) self.check(' # comment', conf, problem=(1, 3))
def test_empty_comment(self): def test_empty_comment(self):
conf = 'comments-indentation: {}' conf = 'comments-indentation: enable'
self.check('---\n' self.check('---\n'
'# hey\n' '# hey\n'
'# normal\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) ': 1\n', conf)
def test_enabled(self): def test_enabled(self):
conf = 'key-duplicates: {}' conf = 'key-duplicates: enable'
self.check('---\n' self.check('---\n'
'block mapping:\n' 'block mapping:\n'
' key: a\n' ' key: a\n'
@@ -149,7 +149,7 @@ class KeyDuplicatesTestCase(RuleTestCase):
problem4=(7, 3)) problem4=(7, 3))
def test_key_tokens_in_flow_sequences(self): def test_key_tokens_in_flow_sequences(self):
conf = 'key-duplicates: {}' conf = 'key-duplicates: enable'
self.check('---\n' self.check('---\n'
'[\n' '[\n'
' flow: sequence, with, key: value, mappings\n' ' flow: sequence, with, key: value, mappings\n'

View File

@@ -30,7 +30,7 @@ class NewLineAtEndOfFileTestCase(RuleTestCase):
self.check('Sentence.\n', conf) self.check('Sentence.\n', conf)
def test_enabled(self): 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' 'empty-lines: disable\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('', conf) self.check('', conf)

View File

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

View File

@@ -37,7 +37,7 @@ class SimpleConfigTestCase(unittest.TestCase):
config.YamlLintConfigError, config.YamlLintConfigError,
'invalid config: no such rule: "this-one-does-not-exist"'): 'invalid config: no such rule: "this-one-does-not-exist"'):
config.YamlLintConfig('rules:\n' config.YamlLintConfig('rules:\n'
' this-one-does-not-exist: {}\n') ' this-one-does-not-exist: enable\n')
def test_missing_option(self): def test_missing_option(self):
with self.assertRaisesRegexp( with self.assertRaisesRegexp(
@@ -58,6 +58,58 @@ class SimpleConfigTestCase(unittest.TestCase):
' max-spaces-after: 1\n' ' max-spaces-after: 1\n'
' abcdef: yes\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): class ExtendedConfigTestCase(unittest.TestCase):
def test_extend_add_rule(self): 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 # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from io import open
import os import os
from tests.common import RuleTestCase from tests.common import RuleTestCase
@@ -35,6 +36,7 @@ from tests.common import RuleTestCase
# for br in span.find_all("br"): # for br in span.find_all("br"):
# br.replace_with("\n") # br.replace_with("\n")
# text = text.replace('\u2193', '') # downwards arrow # text = text.replace('\u2193', '') # downwards arrow
# text = text.replace('\u21d3', '') # double downwards arrow
# text = text.replace('\u00b7', ' ') # visible space # text = text.replace('\u00b7', ' ') # visible space
# text = text.replace('\u21d4', '') # byte order mark # text = text.replace('\u21d4', '') # byte order mark
# text = text.replace('\u2192', '\t') # right arrow # 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' 'example-7.15': ('braces: {min-spaces-inside: 0, max-spaces-inside: 1}\n'
'commas: {max-spaces-before: 1, min-spaces-after: 0}\n' 'commas: {max-spaces-before: 1, min-spaces-after: 0}\n'
'colons: {max-spaces-before: 1}\n'), 'colons: {max-spaces-before: 1}\n'),
'example-7.16': ('indentation: disable\n'),
'example-7.17': ('indentation: disable\n'), 'example-7.17': ('indentation: disable\n'),
'example-7.18': ('indentation: disable\n'), 'example-7.18': ('indentation: disable\n'),
'example-7.19': ('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.14': ('colons: {max-spaces-before: 1}\n'),
'example-8.16': ('indentation: {spaces: 1}\n'), 'example-8.16': ('indentation: {spaces: 1}\n'),
'example-8.17': ('indentation: disable\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 assert len(files) == 132
@@ -122,15 +131,6 @@ def _gen_test(buffer, conf):
self.check(buffer, conf) self.check(buffer, conf)
return test 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 # The following tests are blacklisted (i.e. will not be checked against
# yamllint), because pyyaml is currently not able to parse the contents # yamllint), because pyyaml is currently not able to parse the contents
# (using yaml.parse()). # (using yaml.parse()).
@@ -177,10 +177,10 @@ pyyaml_blacklist = (
) )
for file in files: for file in files:
if file in tmp_blacklist or file in pyyaml_blacklist: if file in pyyaml_blacklist:
continue 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, '') conf = conf_general + conf_overrides.get(file, '')
setattr(SpecificationTestCase, 'test_' + file, setattr(SpecificationTestCase, 'test_' + file,
_gen_test(f.read(), conf)) _gen_test(f.read(), conf))

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé # Copyright (C) 2016 Adrien Vergé
# #
@@ -67,8 +66,11 @@ def run(argv=None):
description=APP_DESCRIPTION) description=APP_DESCRIPTION)
parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+', parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+',
help='files to check') help='files to check')
parser.add_argument('-c', '--config', dest='config_file', action='store', parser.add_argument('-c', '--config-file', dest='config_file',
help='path to a custom configuration') 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', parser.add_argument('-f', '--format',
choices=('parsable', 'standard'), default='standard', choices=('parsable', 'standard'), default='standard',
help='format for parsing output') help='format for parsing output')
@@ -79,8 +81,17 @@ def run(argv=None):
args = parser.parse_args(argv) 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: 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) conf = YamlLintConfig(file=args.config_file)
elif os.path.isfile('.yamllint'): elif os.path.isfile('.yamllint'):
conf = YamlLintConfig(file='.yamllint') conf = YamlLintConfig(file='.yamllint')

View File

@@ -31,14 +31,14 @@ rules:
hyphens: hyphens:
max-spaces-after: 1 max-spaces-after: 1
indentation: indentation:
spaces: 2 spaces: consistent
indent-sequences: yes indent-sequences: yes
check-multi-line-strings: no check-multi-line-strings: no
key-duplicates: {} key-duplicates: enable
line-length: line-length:
max: 80 max: 80
allow-non-breakable-words: yes allow-non-breakable-words: yes
new-line-at-end-of-file: {level: error} new-line-at-end-of-file: enable
new-lines: new-lines:
type: unix 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): def validate_rule_conf(rule, conf):
if conf is False or conf == 'disable': if conf is False or conf == 'disable':
return False return False
elif conf == 'enable':
conf = {}
if type(conf) == dict: if type(conf) == dict:
if 'level' not in conf: if 'level' not in conf:
@@ -100,7 +102,8 @@ def validate_rule_conf(rule, conf):
'invalid config: unknown option "%s" for rule "%s"' % 'invalid config: unknown option "%s" for rule "%s"' %
(optkey, rule.ID)) (optkey, rule.ID))
if type(options[optkey]) == tuple: 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( raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be in %s' 'invalid config: option "%s" of "%s" should be in %s'
% (optkey, rule.ID, options[optkey])) % (optkey, rule.ID, options[optkey]))
@@ -116,7 +119,8 @@ def validate_rule_conf(rule, conf):
(optkey, rule.ID)) (optkey, rule.ID))
else: else:
raise YamlLintConfigError(('invalid config: rule "%s": should be ' 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 return conf

View File

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

View File

@@ -30,11 +30,12 @@ class Line(object):
class Token(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.line_no = line_no
self.curr = curr self.curr = curr
self.prev = prev self.prev = prev
self.next = next self.next = next
self.nextnext = nextnext
def line_generator(buffer): def line_generator(buffer):
@@ -55,14 +56,16 @@ def token_generator(buffer):
try: try:
prev = None prev = None
next = yaml_loader.peek_token() curr = yaml_loader.get_token()
while next is not None: while curr is not None:
curr = yaml_loader.get_token() next = yaml_loader.get_token()
next = yaml_loader.peek_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 prev = curr
curr = next
except yaml.scanner.ScannerError: except yaml.scanner.ScannerError:
pass pass

View File

@@ -73,7 +73,7 @@ CONF = {'min-spaces-inside': int,
'max-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): if isinstance(token, yaml.FlowMappingStartToken):
problem = spaces_after(token, prev, next, problem = spaces_after(token, prev, next,
min=conf['min-spaces-inside'], min=conf['min-spaces-inside'],

View File

@@ -74,7 +74,7 @@ CONF = {'min-spaces-inside': int,
'max-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): if isinstance(token, yaml.FlowSequenceStartToken):
problem = spaces_after(token, prev, next, problem = spaces_after(token, prev, next,
min=conf['min-spaces-inside'], min=conf['min-spaces-inside'],

View File

@@ -81,7 +81,7 @@ CONF = {'max-spaces-before': int,
'max-spaces-after': 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): if isinstance(token, yaml.ValueToken):
problem = spaces_before(token, prev, next, problem = spaces_before(token, prev, next,
max=conf['max-spaces-before'], max=conf['max-spaces-before'],

View File

@@ -105,7 +105,7 @@ CONF = {'max-spaces-before': int,
'max-spaces-after': 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 isinstance(token, yaml.FlowEntryToken):
if (prev is not None and conf['max-spaces-before'] != -1 and if (prev is not None and conf['max-spaces-before'] != -1 and
prev.end_mark.line < token.start_mark.line): prev.end_mark.line < token.start_mark.line):

View File

@@ -67,7 +67,7 @@ CONF = {'require-starting-space': bool,
'min-spaces-from-content': int} '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): for comment in get_comments_between_tokens(token, next):
if (conf['min-spaces-from-content'] != -1 and if (conf['min-spaces-from-content'] != -1 and
not isinstance(token, yaml.StreamStartToken) and not isinstance(token, yaml.StreamStartToken) and

View File

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

View File

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

View File

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

View File

@@ -78,7 +78,7 @@ TYPE = 'token'
CONF = {'max-spaces-after': int} 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): if isinstance(token, yaml.BlockEntryToken):
problem = spaces_after(token, prev, next, problem = spaces_after(token, prev, next,
max=conf['max-spaces-after'], max=conf['max-spaces-after'],

View File

@@ -19,12 +19,16 @@ Use this rule to control the indentation.
.. rubric:: Options .. 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 * ``indent-sequences`` defines whether block sequences should be indented or
not (when in a mapping, this indentation is not mandatory -- some people not (when in a mapping, this indentation is not mandatory -- some people
perceive the ``-`` as part of the indentation). Possible values: ``yes``, perceive the ``-`` as part of the indentation). Possible values: ``yes``,
``no`` and ``whatever`` (the latter means either indenting or not indenting ``no``, ``whatever`` and ``consistent``. ``consistent`` requires either all
block sequences is OK. 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 * ``check-multi-line-strings`` defines whether to lint indentation in
multi-line strings. Set to ``yes`` to enable, ``no`` to disable. multi-line strings. Set to ``yes`` to enable, ``no`` to disable.
@@ -73,6 +77,28 @@ Use this rule to control the indentation.
- haystack: - haystack:
needle 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}`` #. With ``indentation: {spaces: 2, indent-sequences: no}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
@@ -104,6 +130,28 @@ Use this rule to control the indentation.
- spaghetti - spaghetti
- sauce - 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}`` #. With ``indentation: {spaces: 4, check-multi-line-strings: yes}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
@@ -150,63 +198,77 @@ from yamllint.rules.common import is_explicit_key, get_real_end_line
ID = 'indentation' ID = 'indentation'
TYPE = 'token' TYPE = 'token'
CONF = {'spaces': int, CONF = {'spaces': (int, 'consistent'),
'indent-sequences': (True, False, 'whatever'), 'indent-sequences': (bool, 'whatever', 'consistent'),
'check-multi-line-strings': bool} '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): class Parent(object):
def __init__(self, type, indent): def __init__(self, type, indent, line_indent=None):
self.type = type self.type = type
self.indent = indent self.indent = indent
self.line_indent = line_indent
self.explicit_key = False 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): def check_scalar_indentation(conf, token, context):
if token.start_mark.line == token.end_mark.line: if token.start_mark.line == token.end_mark.line:
return return
if token.plain: def compute_expected_indent(found_indent):
expected_indent = token.start_mark.column def detect_indent(base_indent):
elif token.style in ('"', "'"): if type(context['spaces']) is not int:
expected_indent = token.start_mark.column + 1 context['spaces'] = found_indent - base_indent
elif token.style in ('>', '|'): return base_indent + context['spaces']
if context['stack'][-1].type == B_SEQ:
# - > if token.plain:
# multi return token.start_mark.column
# line elif token.style in ('"', "'"):
expected_indent = token.start_mark.column + conf['spaces'] return token.start_mark.column + 1
elif context['stack'][-1].type == KEY: elif token.style in ('>', '|'):
assert context['stack'][-1].explicit_key if context['stack'][-1].type == B_SEQ:
# - ? > # - >
# multi-line # multi
# key # line
# : > return detect_indent(token.start_mark.column)
# multi-line elif context['stack'][-1].type == KEY:
# value assert context['stack'][-1].explicit_key
expected_indent = token.start_mark.column + conf['spaces'] # - ? >
elif context['stack'][-1].type == VAL: # multi-line
if token.start_mark.line + 1 > context['cur_line']: # key
# - key:
# >
# multi
# line
expected_indent = context['stack'][-1].indent + conf['spaces']
elif context['stack'][-2].explicit_key:
# - ? key
# : > # : >
# multi-line # multi-line
# value # 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: else:
# - key: > return detect_indent(context['stack'][-1].indent)
# multi
# line expected_indent = None
expected_indent = context['stack'][-2].indent + conf['spaces']
else:
expected_indent = context['stack'][-1].indent + conf['spaces']
line_no = token.start_mark.line + 1 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': if token.start_mark.buffer[line_start + indent] == '\n':
continue continue
if expected_indent is None:
expected_indent = compute_expected_indent(indent)
if indent != expected_indent: if indent != expected_indent:
yield LintProblem(line_no, indent + 1, yield LintProblem(line_no, indent + 1,
'wrong indentation: expected %d but found %d' % 'wrong indentation: expected %d but found %d' %
(expected_indent, indent)) (expected_indent, indent))
def check(conf, token, prev, next, context): def check(conf, token, prev, next, nextnext, context):
if 'stack' not in context: if 'stack' not in context:
context['stack'] = [Parent(ROOT, 0)] context['stack'] = [Parent(ROOT, 0)]
context['cur_line'] = -1 context['cur_line'] = -1
context['spaces'] = conf['spaces']
context['indent-sequences'] = conf['indent-sequences']
# Step 1: Lint # Step 1: Lint
@@ -244,17 +311,22 @@ def check(conf, token, prev, next, context):
first_in_line = (is_visible and first_in_line = (is_visible and
token.start_mark.line + 1 > context['cur_line']) 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: if first_in_line:
found_indentation = token.start_mark.column found_indentation = token.start_mark.column
expected = context['stack'][-1].indent expected = context['stack'][-1].indent
if isinstance(token, (yaml.FlowMappingEndToken, if isinstance(token, (yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)): yaml.FlowSequenceEndToken)):
expected = 0 expected = context['stack'][-1].line_indent
elif (context['stack'][-1].type == KEY and elif (context['stack'][-1].type == KEY and
context['stack'][-1].explicit_key and context['stack'][-1].explicit_key and
not isinstance(token, yaml.ValueToken)): not isinstance(token, yaml.ValueToken)):
expected += conf['spaces'] expected = detect_indent(expected, token)
if found_indentation != expected: if found_indentation != expected:
yield LintProblem(token.start_mark.line + 1, found_indentation + 1, 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 # Step 2.b: Update state
if context['stack'][-1].type == B_ENT:
context['stack'].pop()
if isinstance(token, yaml.BlockMappingStartToken): if isinstance(token, yaml.BlockMappingStartToken):
assert isinstance(next, yaml.KeyToken) assert isinstance(next, yaml.KeyToken)
if next.start_mark.line == token.start_mark.line: if next.start_mark.line == token.start_mark.line:
@@ -291,9 +360,9 @@ def check(conf, token, prev, next, context):
# - ? # - ?
# a # a
# : 1 # : 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): elif isinstance(token, yaml.FlowMappingStartToken):
if next.start_mark.line == token.start_mark.line: 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 indent = next.start_mark.column
else: else:
# - { # - {
# a: 1, b: 2 # a: 1, b: 2
# } # }
indent = context['cur_line_indent'] + conf['spaces'] 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): elif isinstance(token, yaml.BlockSequenceStartToken):
# - - a # - - a
@@ -320,6 +390,12 @@ def check(conf, token, prev, next, context):
elif (isinstance(token, yaml.BlockEntryToken) and elif (isinstance(token, yaml.BlockEntryToken) and
# in case of an empty entry # in case of an empty entry
not isinstance(next, (yaml.BlockEntryToken, yaml.BlockEndToken))): 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: if next.start_mark.line == token.end_mark.line:
# - item 1 # - item 1
# - item 2 # - item 2
@@ -330,7 +406,7 @@ def check(conf, token, prev, next, context):
# - # -
# key: # key:
# value # value
indent = token.start_mark.column + conf['spaces'] indent = detect_indent(token.start_mark.column, next)
context['stack'].append(Parent(B_ENT, indent)) context['stack'].append(Parent(B_ENT, indent))
@@ -342,15 +418,10 @@ def check(conf, token, prev, next, context):
# - [ # - [
# a, b # a, b
# ] # ]
indent = context['cur_line_indent'] + conf['spaces'] indent = detect_indent(context['cur_line_indent'], next)
context['stack'].append(Parent(F_SEQ, indent)) context['stack'].append(Parent(F_SEQ, indent,
line_indent=context['cur_line_indent']))
elif isinstance(token, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)):
assert context['stack'][-1].type in (MAP, B_SEQ, F_SEQ)
context['stack'].pop()
elif isinstance(token, yaml.KeyToken): elif isinstance(token, yaml.KeyToken):
indent = context['stack'][-1].indent 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) 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): elif isinstance(token, yaml.ValueToken):
assert context['stack'][-1].type == KEY assert context['stack'][-1].type == KEY
# Discard empty values # Special cases:
if isinstance(next, (yaml.BlockEndToken, # key: &anchor
yaml.FlowMappingEndToken, # value
yaml.FlowSequenceEndToken, # and:
yaml.KeyToken)): # key: !!tag
context['stack'].pop() # value
else: 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: if context['stack'][-1].explicit_key:
# ? k # ? k
# : value # : value
@@ -381,7 +456,7 @@ def check(conf, token, prev, next, context):
# ? k # ? k
# : # :
# value # 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: elif next.start_mark.line == prev.start_mark.line:
# k: value # k: value
indent = next.start_mark.column indent = next.start_mark.column
@@ -392,33 +467,77 @@ def check(conf, token, prev, next, context):
# yaml.scan()ning this: # yaml.scan()ning this:
# '- lib:\n' # '- lib:\n'
# ' - var\n' # ' - var\n'
if conf['indent-sequences'] is False: if context['indent-sequences'] is False:
indent = context['stack'][-1].indent indent = context['stack'][-1].indent
elif conf['indent-sequences'] is True: elif context['indent-sequences'] is True:
indent = context['stack'][-1].indent + conf['spaces'] indent = detect_indent(context['stack'][-1].indent, next)
else: # 'whatever' else: # 'whatever' or 'consistent'
if next.start_mark.column == context['stack'][-1].indent: if next.start_mark.column == context['stack'][-1].indent:
# key: # key:
# - e1 # - e1
# - e2 # - e2
if context['indent-sequences'] == 'consistent':
context['indent-sequences'] = False
indent = context['stack'][-1].indent indent = context['stack'][-1].indent
else: else:
if context['indent-sequences'] == 'consistent':
context['indent-sequences'] = True
# key: # key:
# - e1 # - e1
# - e2 # - e2
indent = context['stack'][-1].indent + conf['spaces'] indent = detect_indent(context['stack'][-1].indent,
next)
else: else:
# k: # k:
# value # value
indent = context['stack'][-1].indent + conf['spaces'] indent = detect_indent(context['stack'][-1].indent, next)
context['stack'].append(Parent(VAL, indent)) context['stack'].append(Parent(VAL, indent))
if (context['stack'][-1].type == KEY and consumed_current_token = False
isinstance(next, (yaml.BlockEndToken, while True:
yaml.FlowMappingEndToken, if (context['stack'][-1].type == F_SEQ and
yaml.FlowSequenceEndToken, isinstance(token, yaml.FlowSequenceEndToken)):
yaml.KeyToken))): context['stack'].pop()
# A key without a value: it's part of a set. Let's drop this key
# and leave room for the next one. elif (context['stack'][-1].type == F_MAP and
context['stack'].pop() 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 = [] self.keys = []
def check(conf, token, prev, next, context): def check(conf, token, prev, next, nextnext, context):
if 'stack' not in context: if 'stack' not in context:
context['stack'] = [] context['stack'] = []