Compare commits

..

16 Commits

Author SHA1 Message Date
Adrien Vergé
a7d39b5492 yamllint version 0.6.0 2016-01-25 11:03:00 +01:00
Adrien Vergé
4410bc3e23 Rules: indentation: Fix check-multi-line-strings
For strings that continue on next line at a lower indentation level:

    Blaise Pascal: Je vous écris une longue lettre parce que
      je n'ai pas le temps d'en écrire une courte.
2016-01-25 11:01:42 +01:00
Adrien Vergé
97c446907c Rules: line-length: Add option allow-non-breakable-words 2016-01-24 22:46:10 +01:00
Adrien Vergé
376a6ed484 Doc: Enhance short description 2016-01-24 18:40:48 +01:00
Adrien Vergé
a1eb9d7d2f yamllint version 0.5.2 2016-01-24 18:07:36 +01:00
Adrien Vergé
45538fb08a Doc: Explicit installation by adding sudo in README 2016-01-24 18:05:27 +01:00
Adrien Vergé
be998593dd Distribution: Create script with setup.py 2016-01-24 18:02:42 +01:00
Adrien Vergé
5ed496f471 Distribution: Remove unneeded setup_requires
With the new project layout, `pyyaml` is not needed anymore for parsing
setup.py.
2016-01-24 17:57:11 +01:00
Adrien Vergé
dbbecb5875 Refactor project layout to import yamllint alone
Currently importing yamllint recursively imports its submodules, which
finally requires having pyyaml installed. This is a problem when you
just want to import APP_VERSION from yamllint. For instance, setup.py
imports yamllint to know the version, but doesn't know yet that pyyaml
is to be installed, because it is stated in setup.py itself.

To solve this, yamllint/__init__.py will only contain constants. The
linting functions will be in yamllint/linter.py.
2016-01-24 17:48:20 +01:00
Adrien Vergé
7b147cb411 Tests: Remove Python 2.6 from CI tests
Because:

1. It is old. VERY old.

2. Some useful methods (`assertRaisesRegexp`, `assertIsInstance`) are
   only available from Python 2.7.
2016-01-24 17:39:36 +01:00
Adrien Vergé
fc108e7cee Config: Refactor to use YamlLintConfig objects 2016-01-24 17:39:27 +01:00
Adrien Vergé
792bdf99b4 yamllint version 0.5.1 2016-01-24 15:03:38 +01:00
Adrien Vergé
92798dbda9 Distribution: Add new keywords 2016-01-24 15:03:38 +01:00
Adrien Vergé
e3ebea6033 Distribution: Fix broken setup_requires
The `pyyaml` dependency is needed in `install_requires` but also in
`setup_requires`, because running `setup.py` requires importing
`yamllint`, which itself imports `yaml`.
2016-01-24 14:59:32 +01:00
Adrien Vergé
7983c66093 Doc: Clarify Python compatibility in README 2016-01-23 14:32:02 +01:00
Adrien Vergé
fee72d484e Doc: Add a screenshot 2016-01-23 14:30:24 +01:00
31 changed files with 576 additions and 373 deletions

View File

@@ -1,7 +1,6 @@
--- ---
language: python language: python
python: python:
- 2.6
- 2.7 - 2.7
- 3.3 - 3.3
- 3.4 - 3.4

View File

@@ -3,6 +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
conventions 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
:target: https://travis-ci.org/adrienverge/yamllint :target: https://travis-ci.org/adrienverge/yamllint
@@ -15,7 +18,7 @@ A linter for YAML files.
:target: http://yamllint.readthedocs.org/en/latest/?badge=latest :target: http://yamllint.readthedocs.org/en/latest/?badge=latest
:alt: Documentation status :alt: Documentation status
Compatible with Python 2 & 3. Written in Python (compatible with Python 2 & 3).
Documentation Documentation
------------- -------------
@@ -25,12 +28,18 @@ http://yamllint.readthedocs.org/
Short overview Short overview
-------------- --------------
Screenshot
^^^^^^^^^^
.. image:: docs/screenshot.png
:alt: yamllint screenshot
Installation Installation
^^^^^^^^^^^^ ^^^^^^^^^^^^
.. code:: bash .. code:: bash
pip install yamllint sudo pip install yamllint
Usage Usage
^^^^^ ^^^^^

View File

@@ -1,24 +0,0 @@
#!/usr/bin/env python
# -*- 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 sys
from yamllint import cli
if __name__ == '__main__':
cli.run(sys.argv[1:])

View File

@@ -4,8 +4,5 @@ Development
yamllint provides both a script and a Python module. The latter can be used to yamllint provides both a script and a Python module. The latter can be used to
write your own linting tools: write your own linting tools:
.. autoclass:: yamllint.errors.LintProblem .. automodule:: yamllint.linter
:members:
.. automodule:: yamllint
:members: :members:

View File

@@ -3,6 +3,23 @@ yamllint documentation
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.
Screenshot
----------
.. image:: screenshot.png
:alt: yamllint screenshot
.. note::
The default output format is inspired by `eslint <http://eslint.org/>`_, a
great linting tool for Javascript.
Table of contents
-----------------
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2

View File

@@ -33,7 +33,7 @@ You can also lint all YAML files in a whole directory:
yamllint . yamllint .
The output will look like (colors are not displayed here [#colored-output]_): The output will look like (colors are not displayed here):
:: ::
@@ -70,8 +70,3 @@ option:
If you have a ``.yamllint`` file in your working directory, it will be If you have a ``.yamllint`` file in your working directory, it will be
automatically loaded as configuration by yamllint. automatically loaded as configuration by yamllint.
.. rubric:: Footnotes
.. [#colored-output] The default output format is colored and inspired by
`eslint <http://eslint.org/>`_, a great linting tool for Javascript.

BIN
docs/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -24,9 +24,10 @@ setup(
name=APP_NAME, name=APP_NAME,
version=APP_VERSION, version=APP_VERSION,
author=__author__, author=__author__,
description=APP_DESCRIPTION, description=APP_DESCRIPTION.split('\n')[0],
long_description=APP_DESCRIPTION,
license=__license__, license=__license__,
keywords=['yaml', 'lint', 'linter'], 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 :: 4 - Beta',
@@ -42,7 +43,7 @@ setup(
], ],
packages=find_packages(), packages=find_packages(),
scripts=['bin/yamllint'], entry_points={'console_scripts': ['yamllint=yamllint.cli:run']},
package_data={'yamllint': ['conf/*.yml']}, package_data={'yamllint': ['conf/*.yml']},
install_requires=['pyyaml'], install_requires=['pyyaml'],
tests_require=['nose'], tests_require=['nose'],

View File

@@ -18,9 +18,8 @@ import unittest
import yaml import yaml
from yamllint.config import parse_config from yamllint.config import YamlLintConfig
from yamllint.errors import LintProblem from yamllint import linter
from yamllint import lint
class RuleTestCase(unittest.TestCase): class RuleTestCase(unittest.TestCase):
@@ -31,7 +30,7 @@ class RuleTestCase(unittest.TestCase):
conf = yaml.safe_load(conf) conf = yaml.safe_load(conf)
conf = {'extends': 'default', conf = {'extends': 'default',
'rules': conf} 'rules': conf}
return parse_config(yaml.safe_dump(conf)) return YamlLintConfig(yaml.safe_dump(conf))
def check(self, source, conf, **kwargs): def check(self, source, conf, **kwargs):
expected_problems = [] expected_problems = []
@@ -44,9 +43,9 @@ class RuleTestCase(unittest.TestCase):
rule_id = kwargs[key][2] rule_id = kwargs[key][2]
else: else:
rule_id = self.rule_id rule_id = self.rule_id
expected_problems.append( expected_problems.append(linter.LintProblem(
LintProblem(kwargs[key][0], kwargs[key][1], rule=rule_id)) kwargs[key][0], kwargs[key][1], rule=rule_id))
expected_problems.sort() expected_problems.sort()
real_problems = list(lint(source, self.build_fake_config(conf))) real_problems = list(linter.run(source, self.build_fake_config(conf)))
self.assertEqual(real_problems, expected_problems) self.assertEqual(real_problems, expected_problems)

View File

@@ -507,7 +507,7 @@ class ScalarIndentationTestCase(RuleTestCase):
self.check('a key: multi\n' self.check('a key: multi\n'
' line\n', conf) ' line\n', conf)
self.check('a key: multi\n' self.check('a key: multi\n'
' line\n', conf, problem=(2, 3)) ' line\n', conf)
self.check('a key: multi\n' self.check('a key: multi\n'
' line\n', conf) ' line\n', conf)
self.check('a key:\n' self.check('a key:\n'
@@ -528,6 +528,8 @@ class ScalarIndentationTestCase(RuleTestCase):
' line\n', conf, problem=(2, 2)) ' line\n', conf, problem=(2, 2))
self.check('- multi\n' self.check('- multi\n'
' line\n', conf, problem=(2, 4)) ' line\n', conf, problem=(2, 4))
self.check('a key: multi\n'
' line\n', conf, problem=(2, 3))
self.check('a key: multi\n' self.check('a key: multi\n'
' line\n', conf, problem=(2, 9)) ' line\n', conf, problem=(2, 9))
self.check('a key:\n' self.check('a key:\n'
@@ -546,24 +548,13 @@ class ScalarIndentationTestCase(RuleTestCase):
'document-start: disable\n') 'document-start: disable\n')
self.check('"multi\n' self.check('"multi\n'
' line"\n', conf) ' line"\n', conf)
self.check('"multi\n'
'line"\n', conf, problem=(2, 1))
self.check('- "multi\n' self.check('- "multi\n'
' line"\n', conf) ' line"\n', conf)
self.check('- "multi\n'
' line"\n', conf, problem=(2, 3))
self.check('a key: "multi\n' self.check('a key: "multi\n'
' line"\n', conf) ' line"\n', conf)
self.check('a key: "multi\n'
' line"\n', conf, problem=(2, 3))
self.check('a key: "multi\n'
' line"\n', conf, problem=(2, 8))
self.check('a key:\n' self.check('a key:\n'
' "multi\n' ' "multi\n'
' line"\n', conf) ' line"\n', conf)
self.check('a key:\n'
' "multi\n'
' line"\n', conf, problem=(3, 3))
self.check('- jinja2: "{% if ansible is defined %}\n' self.check('- jinja2: "{% if ansible is defined %}\n'
' {{ ansible }}\n' ' {{ ansible }}\n'
' {% else %}\n' ' {% else %}\n'
@@ -580,11 +571,22 @@ class ScalarIndentationTestCase(RuleTestCase):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n' conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('"multi\n' self.check('"multi\n'
'line"\n', conf, problem=(2, 1))
self.check('"multi\n'
' line"\n', conf, problem=(2, 3))
self.check('- "multi\n'
' line"\n', conf, problem=(2, 3)) ' line"\n', conf, problem=(2, 3))
self.check('- "multi\n' self.check('- "multi\n'
' line"\n', conf, problem=(2, 5)) ' line"\n', conf, problem=(2, 5))
self.check('a key: "multi\n'
' line"\n', conf, problem=(2, 3))
self.check('a key: "multi\n'
' line"\n', conf, problem=(2, 8))
self.check('a key: "multi\n' self.check('a key: "multi\n'
' line"\n', conf, problem=(2, 10)) ' line"\n', conf, problem=(2, 10))
self.check('a key:\n'
' "multi\n'
' line"\n', conf, problem=(3, 3))
self.check('a key:\n' self.check('a key:\n'
' "multi\n' ' "multi\n'
' line"\n', conf, problem=(3, 5)) ' line"\n', conf, problem=(3, 5))

View File

@@ -43,17 +43,17 @@ class LineLengthTestCase(RuleTestCase):
self.check('---\n', conf) self.check('---\n', conf)
self.check(80 * 'a', conf) self.check(80 * 'a', conf)
self.check('---\n' + 80 * 'a' + '\n', conf) self.check('---\n' + 80 * 'a' + '\n', conf)
self.check(81 * 'a', conf, problem=(1, 81)) self.check(16 * 'aaaa ' + 'z', conf, problem=(1, 81))
self.check('---\n' + 81 * 'a' + '\n', conf, problem=(2, 81)) self.check('---\n' + 16 * 'aaaa ' + 'z' + '\n', conf, problem=(2, 81))
self.check(1000 * 'b', conf, problem=(1, 81)) self.check(1000 * 'word ' + 'end', conf, problem=(1, 81))
self.check('---\n' + 1000 * 'b' + '\n', conf, problem=(2, 81)) self.check('---\n' + 1000 * 'word ' + 'end\n', conf, problem=(2, 81))
def test_max_length_10(self): def test_max_length_10(self):
conf = ('line-length: {max: 10}\n' conf = ('line-length: {max: 10}\n'
'new-line-at-end-of-file: disable\n') 'new-line-at-end-of-file: disable\n')
self.check('---\nABCDEFGHIJ', conf) self.check('---\nABCD EFGHI', conf)
self.check('---\nABCDEFGHIJK', conf, problem=(2, 11)) self.check('---\nABCD EFGHIJ', conf, problem=(2, 11))
self.check('---\nABCDEFGHIJK\n', conf, problem=(2, 11)) self.check('---\nABCD EFGHIJ\n', conf, problem=(2, 11))
def test_spaces(self): def test_spaces(self):
conf = ('line-length: {max: 80}\n' conf = ('line-length: {max: 80}\n'
@@ -61,3 +61,36 @@ class LineLengthTestCase(RuleTestCase):
'trailing-spaces: disable\n') 'trailing-spaces: disable\n')
self.check('---\n' + 81 * ' ', conf, problem=(2, 81)) self.check('---\n' + 81 * ' ', conf, problem=(2, 81))
self.check('---\n' + 81 * ' ' + '\n', conf, problem=(2, 81)) self.check('---\n' + 81 * ' ' + '\n', conf, problem=(2, 81))
def test_non_breakable_word(self):
conf = 'line-length: {max: 20, allow-non-breakable-words: yes}'
self.check('---\n' + 30 * 'A' + '\n', conf)
self.check('---\n'
'this:\n'
' is:\n'
' - a:\n'
' http://localhost/very/long/url\n'
'...\n', conf)
self.check('---\n'
'this:\n'
' is:\n'
' - a:\n'
' # http://localhost/very/long/url\n'
' comment\n'
'...\n', conf)
conf = 'line-length: {max: 20, allow-non-breakable-words: no}'
self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21))
self.check('---\n'
'this:\n'
' is:\n'
' - a:\n'
' http://localhost/very/long/url\n'
'...\n', conf, problem=(5, 21))
self.check('---\n'
'this:\n'
' is:\n'
' - a:\n'
' # http://localhost/very/long/url\n'
' comment\n'
'...\n', conf, problem=(5, 21))

View File

@@ -14,56 +14,164 @@
# 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/>.
import os
import unittest import unittest
from yamllint import config from yamllint import config
class ConfigTestCase(unittest.TestCase): class SimpleConfigTestCase(unittest.TestCase):
def setUp(self): def test_parse_config(self):
self.base = config.parse_config_from_file(os.path.join( new = config.YamlLintConfig('rules:\n'
os.path.dirname(os.path.dirname(os.path.realpath(__file__))), ' colons:\n'
'yamllint', 'conf', 'default.yml')) ' max-spaces-before: 0\n'
' max-spaces-after: 1\n')
self.assertEqual(list(new.rules.keys()), ['colons'])
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)
def test_unknown_rule(self):
with self.assertRaisesRegexp(
config.YamlLintConfigError,
'invalid config: no such rule: "this-one-does-not-exist"'):
config.YamlLintConfig('rules:\n'
' this-one-does-not-exist: {}\n')
def test_missing_option(self):
with self.assertRaisesRegexp(
config.YamlLintConfigError,
'invalid config: missing option "max-spaces-before" '
'for rule "colons"'):
config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-after: 1\n')
def test_unknown_option(self):
with self.assertRaisesRegexp(
config.YamlLintConfigError,
'invalid config: unknown option "abcdef" for rule "colons"'):
config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' abcdef: yes\n')
class ExtendedConfigTestCase(unittest.TestCase):
def test_extend_add_rule(self):
old = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n')
new = config.YamlLintConfig('rules:\n'
' hyphens:\n'
' max-spaces-after: 2\n')
new.extend(old)
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons']['max-spaces-before'], 0)
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)
def test_extend_remove_rule(self):
old = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' hyphens:\n'
' max-spaces-after: 2\n')
new = config.YamlLintConfig('rules:\n'
' colons: disable\n')
new.extend(old)
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons'], False)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 1)
def test_extend_edit_rule(self):
old = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' hyphens:\n'
' max-spaces-after: 2\n')
new = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 3\n'
' max-spaces-after: 4\n')
new.extend(old)
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons']['max-spaces-before'], 3)
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)
def test_extend_reenable_rule(self):
old = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' hyphens: disable\n')
new = config.YamlLintConfig('rules:\n'
' hyphens:\n'
' max-spaces-after: 2\n')
new.extend(old)
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons']['max-spaces-before'], 0)
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)
class ExtendedLibraryConfigTestCase(unittest.TestCase):
def test_extend_config_disable_rule(self): def test_extend_config_disable_rule(self):
new = config.parse_config('extends: default\n' old = config.YamlLintConfig('extends: default')
'rules:\n' new = config.YamlLintConfig('extends: default\n'
' trailing-spaces: disable\n') 'rules:\n'
' trailing-spaces: disable\n')
base = self.base.copy() old.rules['trailing-spaces'] = False
del base['trailing-spaces']
self.assertEqual(sorted(new.keys()), sorted(base.keys())) self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys()))
for rule in new: for rule in new.rules:
self.assertEqual(new[rule], base[rule]) self.assertEqual(new.rules[rule], old.rules[rule])
def test_extend_config_override_whole_rule(self): def test_extend_config_override_whole_rule(self):
new = config.parse_config('extends: default\n' old = config.YamlLintConfig('extends: default')
'rules:\n' new = config.YamlLintConfig('extends: default\n'
' empty-lines:\n' 'rules:\n'
' max: 42\n' ' empty-lines:\n'
' max-start: 43\n' ' max: 42\n'
' max-end: 44\n') ' max-start: 43\n'
' max-end: 44\n')
base = self.base.copy() old.rules['empty-lines']['max'] = 42
base['empty-lines']['max'] = 42 old.rules['empty-lines']['max-start'] = 43
base['empty-lines']['max-start'] = 43 old.rules['empty-lines']['max-end'] = 44
base['empty-lines']['max-end'] = 44
self.assertEqual(sorted(new.keys()), sorted(base.keys())) self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys()))
for rule in new: for rule in new.rules:
self.assertEqual(new[rule], base[rule]) self.assertEqual(new.rules[rule], old.rules[rule])
def test_extend_config_override_rule_partly(self): def test_extend_config_override_rule_partly(self):
new = config.parse_config('extends: default\n' old = config.YamlLintConfig('extends: default')
'rules:\n' new = config.YamlLintConfig('extends: default\n'
' empty-lines:\n' 'rules:\n'
' max-start: 42\n') ' empty-lines:\n'
' max-start: 42\n')
base = self.base.copy() old.rules['empty-lines']['max-start'] = 42
base['empty-lines']['max-start'] = 42
self.assertEqual(sorted(new.keys()), sorted(base.keys())) self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys()))
for rule in new: for rule in new.rules:
self.assertEqual(new[rule], base[rule]) self.assertEqual(new.rules[rule], old.rules[rule])

View File

@@ -65,8 +65,8 @@ class ParserTestCase(unittest.TestCase):
e = list(token_generator('')) e = list(token_generator(''))
self.assertEqual(len(e), 2) self.assertEqual(len(e), 2)
self.assertEqual(e[0].prev, None) self.assertEqual(e[0].prev, None)
self.assertTrue(isinstance(e[0].curr, yaml.Token)) self.assertIsInstance(e[0].curr, yaml.Token)
self.assertTrue(isinstance(e[0].next, yaml.Token)) self.assertIsInstance(e[0].next, yaml.Token)
self.assertEqual(e[1].prev, e[0].curr) self.assertEqual(e[1].prev, e[0].curr)
self.assertEqual(e[1].curr, e[0].next) self.assertEqual(e[1].curr, e[0].next)
self.assertEqual(e[1].next, None) self.assertEqual(e[1].next, None)
@@ -74,20 +74,20 @@ class ParserTestCase(unittest.TestCase):
e = list(token_generator('---\n' e = list(token_generator('---\n'
'k: v\n')) 'k: v\n'))
self.assertEqual(len(e), 9) self.assertEqual(len(e), 9)
self.assertTrue(isinstance(e[3].curr, yaml.KeyToken)) self.assertIsInstance(e[3].curr, yaml.KeyToken)
self.assertTrue(isinstance(e[5].curr, yaml.ValueToken)) self.assertIsInstance(e[5].curr, yaml.ValueToken)
def test_token_or_line_generator(self): def test_token_or_line_generator(self):
e = list(token_or_line_generator('---\n' e = list(token_or_line_generator('---\n'
'k: v\n')) 'k: v\n'))
self.assertEqual(len(e), 12) self.assertEqual(len(e), 12)
self.assertTrue(isinstance(e[0], Token)) self.assertIsInstance(e[0], Token)
self.assertTrue(isinstance(e[0].curr, yaml.StreamStartToken)) self.assertIsInstance(e[0].curr, yaml.StreamStartToken)
self.assertTrue(isinstance(e[1], Token)) self.assertIsInstance(e[1], Token)
self.assertTrue(isinstance(e[1].curr, yaml.DocumentStartToken)) self.assertIsInstance(e[1].curr, yaml.DocumentStartToken)
self.assertTrue(isinstance(e[2], Line)) self.assertIsInstance(e[2], Line)
self.assertTrue(isinstance(e[3].curr, yaml.BlockMappingStartToken)) self.assertIsInstance(e[3].curr, yaml.BlockMappingStartToken)
self.assertTrue(isinstance(e[4].curr, yaml.KeyToken)) self.assertIsInstance(e[4].curr, yaml.KeyToken)
self.assertTrue(isinstance(e[6].curr, yaml.ValueToken)) self.assertIsInstance(e[6].curr, yaml.ValueToken)
self.assertTrue(isinstance(e[8], Line)) self.assertIsInstance(e[8], Line)
self.assertTrue(isinstance(e[11], Line)) self.assertIsInstance(e[11], Line)

View File

@@ -14,104 +14,14 @@
# 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/>.
import yaml
from yamllint import config
from yamllint.errors import LintProblem
from yamllint import parser
APP_NAME = 'yamllint' APP_NAME = 'yamllint'
APP_VERSION = '0.5.0' APP_VERSION = '0.6.0'
APP_DESCRIPTION = 'A linter for YAML files.' APP_DESCRIPTION = """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."""
__author__ = u'Adrien Vergé' __author__ = u'Adrien Vergé'
__copyright__ = u'Copyright 2016, Adrien Vergé' __copyright__ = u'Copyright 2016, Adrien Vergé'
__license__ = 'GPLv3' __license__ = 'GPLv3'
__version__ = APP_VERSION __version__ = APP_VERSION
def get_costemic_problems(buffer, conf):
rules = config.get_enabled_rules(conf)
# Split token rules from line rules
token_rules = [r for r in rules if r.TYPE == 'token']
line_rules = [r for r in rules if r.TYPE == 'line']
context = {}
for rule in token_rules:
context[rule.ID] = {}
for elem in parser.token_or_line_generator(buffer):
if isinstance(elem, parser.Token):
for rule in token_rules:
rule_conf = conf[rule.ID]
for problem in rule.check(rule_conf,
elem.curr, elem.prev, elem.next,
context[rule.ID]):
problem.rule = rule.ID
problem.level = rule_conf['level']
yield problem
elif isinstance(elem, parser.Line):
for rule in line_rules:
rule_conf = conf[rule.ID]
for problem in rule.check(rule_conf, elem):
problem.rule = rule.ID
problem.level = rule_conf['level']
yield problem
def get_syntax_error(buffer):
try:
list(yaml.parse(buffer, Loader=yaml.BaseLoader))
except yaml.error.MarkedYAMLError as e:
problem = LintProblem(e.problem_mark.line + 1,
e.problem_mark.column + 1,
'syntax error: ' + e.problem)
problem.level = 'error'
return problem
def _lint(buffer, conf):
# If the document contains a syntax error, save it and yield it at the
# right line
syntax_error = get_syntax_error(buffer)
for problem in get_costemic_problems(buffer, conf):
# 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):
yield syntax_error
# If there is already a yamllint error at the same place, discard
# it as it is probably redundant (and maybe it's just a 'warning',
# in which case the script won't even exit with a failure status).
if (syntax_error.line == problem.line and
syntax_error.column == problem.column):
syntax_error = None
continue
syntax_error = None
yield problem
if syntax_error:
yield syntax_error
def lint(input, conf):
"""Lints a YAML source.
Returns a generator of LintProblem objects.
:param input: buffer, string or stream to read from
:param conf: yamllint configuration object
"""
if type(input) == str:
return _lint(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
content = input.read()
return _lint(content, conf)
else:
raise TypeError('input should be a string or a stream')

View File

@@ -22,9 +22,8 @@ import sys
import argparse import argparse
from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION
from yamllint import config from yamllint.config import YamlLintConfig, YamlLintConfigError
from yamllint.errors import YamlLintConfigError from yamllint import linter
from yamllint import lint
def find_files_recursively(items): def find_files_recursively(items):
@@ -63,7 +62,7 @@ class Format(object):
return line return line
def run(argv): def run(argv=None):
parser = argparse.ArgumentParser(prog=APP_NAME, parser = argparse.ArgumentParser(prog=APP_NAME,
description=APP_DESCRIPTION) description=APP_DESCRIPTION)
parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+', parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+',
@@ -82,11 +81,11 @@ def run(argv):
try: try:
if args.config_file is not None: if args.config_file is not None:
conf = config.parse_config_from_file(args.config_file) conf = YamlLintConfig(file=args.config_file)
elif os.path.isfile('.yamllint'): elif os.path.isfile('.yamllint'):
conf = config.parse_config_from_file('.yamllint') conf = YamlLintConfig(file='.yamllint')
else: else:
conf = config.parse_config('extends: default') conf = YamlLintConfig('extends: default')
except YamlLintConfigError as e: except YamlLintConfigError as e:
print(e, file=sys.stderr) print(e, file=sys.stderr)
sys.exit(-1) sys.exit(-1)
@@ -97,7 +96,7 @@ def run(argv):
try: try:
first = True first = True
with open(file) as f: with open(file) as f:
for problem in lint(f, conf): for problem in linter.run(f, conf):
if args.format == 'parsable': if args.format == 'parsable':
print(Format.parsable(problem, file)) print(Format.parsable(problem, file))
else: else:

View File

@@ -35,6 +35,7 @@ rules:
check-multi-line-strings: no check-multi-line-strings: no
line-length: line-length:
max: 80 max: 80
allow-non-breakable-words: yes
new-line-at-end-of-file: {level: error} new-line-at-end-of-file: {level: error}
new-lines: new-lines:
type: unix type: unix

View File

@@ -19,10 +19,109 @@ import os.path
import yaml import yaml
import yamllint.rules import yamllint.rules
from yamllint.errors import YamlLintConfigError
def get_extended_conf(name): class YamlLintConfigError(Exception):
pass
class YamlLintConfig(object):
def __init__(self, content=None, file=None):
assert (content is None) ^ (file is None)
if file is not None:
with open(file) as f:
content = f.read()
self.parse(content)
self.validate()
def enabled_rules(self):
return [yamllint.rules.get(id) for id, val in self.rules.items()
if val is not False]
def extend(self, base_config):
assert isinstance(base_config, YamlLintConfig)
for rule in self.rules:
if (type(self.rules[rule]) == dict and
rule in base_config.rules and
base_config.rules[rule] is not False):
base_config.rules[rule].update(self.rules[rule])
else:
base_config.rules[rule] = self.rules[rule]
self.rules = base_config.rules
def parse(self, raw_content):
try:
conf = yaml.safe_load(raw_content)
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
self.rules = conf.get('rules', {})
# Does this conf override another conf that we need to load?
if 'extends' in conf:
path = get_extended_config_file(conf['extends'])
base = YamlLintConfig(file=path)
try:
self.extend(base)
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
def validate(self):
for id in self.rules:
try:
rule = yamllint.rules.get(id)
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
self.rules[id] = validate_rule_conf(rule, self.rules[id])
def validate_rule_conf(rule, conf):
if conf is False or conf == 'disable':
return False
if type(conf) == dict:
if 'level' not in conf:
conf['level'] = 'error'
elif conf['level'] not in ('error', 'warning'):
raise YamlLintConfigError(
'invalid config: level should be "error" or "warning"')
options = getattr(rule, 'CONF', {})
for optkey in conf:
if optkey == 'level':
continue
if optkey not in options:
raise YamlLintConfigError(
'invalid config: unknown option "%s" for rule "%s"' %
(optkey, rule.ID))
if type(options[optkey]) == tuple:
if conf[optkey] not in options[optkey]:
raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be in %s'
% (optkey, rule.ID, options[optkey]))
else:
if type(conf[optkey]) != options[optkey]:
raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be %s'
% (optkey, rule.ID, options[optkey].__name__))
for optkey in options:
if optkey not in conf:
raise YamlLintConfigError(
'invalid config: missing option "%s" for rule "%s"' %
(optkey, rule.ID))
else:
raise YamlLintConfigError(('invalid config: rule "%s": should be '
'either "disable" or a dict') % rule.ID)
return conf
def get_extended_config_file(name):
# Is it a standard conf shipped with yamllint... # Is it a standard conf shipped with yamllint...
if '/' not in name: if '/' not in name:
std_conf = os.path.join(os.path.dirname(os.path.realpath(__file__)), std_conf = os.path.join(os.path.dirname(os.path.realpath(__file__)),
@@ -33,81 +132,3 @@ def get_extended_conf(name):
# or a custom conf on filesystem? # or a custom conf on filesystem?
return name return name
def extend_config(content):
try:
conf = yaml.safe_load(content)
if 'rules' not in conf:
conf['rules'] = {}
# Does this conf override another conf that we need to load?
if 'extends' in conf:
base = parse_config_from_file(get_extended_conf(conf['extends']))
for rule in conf['rules']:
if type(conf['rules'][rule]) == dict and rule in base:
base[rule].update(conf['rules'][rule])
else:
base[rule] = conf['rules'][rule]
conf['rules'] = base
return conf
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
def parse_config(content):
conf = extend_config(content)
rules = {}
for id in conf['rules']:
try:
rule = yamllint.rules.get(id)
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
if conf['rules'][id] == 'disable':
continue
rules[id] = {'level': 'error'}
if type(conf['rules'][id]) == dict:
if 'level' in conf['rules'][id]:
if conf['rules'][id]['level'] not in ('error', 'warning'):
raise YamlLintConfigError(
'invalid config: level should be "error" or "warning"')
rules[id]['level'] = conf['rules'][id]['level']
options = getattr(rule, 'CONF', {})
for optkey in conf['rules'][id]:
if optkey == 'level':
continue
if optkey not in options:
raise YamlLintConfigError(
'invalid config: unknown option "%s" for rule "%s"' %
(optkey, id))
if type(options[optkey]) == tuple:
if conf['rules'][id][optkey] not in options[optkey]:
raise YamlLintConfigError(
('invalid config: option "%s" of "%s" should be '
'in %s') % (optkey, id, options[optkey]))
else:
if type(conf['rules'][id][optkey]) != options[optkey]:
raise YamlLintConfigError(
('invalid config: option "%s" of "%s" should be '
'%s' % (optkey, id, options[optkey].__name__)))
rules[id][optkey] = conf['rules'][id][optkey]
else:
raise YamlLintConfigError(('invalid config: rule "%s": should be '
'either "disable" or a dict') % id)
return rules
def parse_config_from_file(path):
with open(path) as f:
return parse_config(f.read())
def get_enabled_rules(conf):
return [yamllint.rules.get(r) for r in conf.keys() if conf[r] is not False]

View File

@@ -1,55 +0,0 @@
# -*- 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/>.
class LintProblem(object):
"""Represents a linting problem found by yamllint."""
def __init__(self, line, column, desc='<no description>', rule=None):
#: Line on which the problem was found (starting at 1)
self.line = line
#: Column on which the problem was found (starting at 1)
self.column = column
#: Human-readable description of the problem
self.desc = desc
#: Identifier of the rule that detected the problem
self.rule = rule
self.level = None
@property
def message(self):
if self.rule is not None:
return '%s (%s)' % (self.desc, self.rule)
return self.desc
def __eq__(self, other):
return (self.line == other.line and
self.column == other.column and
self.rule == other.rule)
def __lt__(self, other):
return (self.line < other.line or
(self.line == other.line and self.column < other.column))
def __repr__(self):
return '%d:%d: %s' % (self.line, self.column, self.message)
class YamlLintError(Exception):
pass
class YamlLintConfigError(YamlLintError):
pass

137
yamllint/linter.py Normal file
View File

@@ -0,0 +1,137 @@
# -*- 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 yaml
from yamllint import parser
class LintProblem(object):
"""Represents a linting problem found by yamllint."""
def __init__(self, line, column, desc='<no description>', rule=None):
#: Line on which the problem was found (starting at 1)
self.line = line
#: Column on which the problem was found (starting at 1)
self.column = column
#: Human-readable description of the problem
self.desc = desc
#: Identifier of the rule that detected the problem
self.rule = rule
self.level = None
@property
def message(self):
if self.rule is not None:
return '%s (%s)' % (self.desc, self.rule)
return self.desc
def __eq__(self, other):
return (self.line == other.line and
self.column == other.column and
self.rule == other.rule)
def __lt__(self, other):
return (self.line < other.line or
(self.line == other.line and self.column < other.column))
def __repr__(self):
return '%d:%d: %s' % (self.line, self.column, self.message)
def get_costemic_problems(buffer, conf):
rules = conf.enabled_rules()
# Split token rules from line rules
token_rules = [r for r in rules if r.TYPE == 'token']
line_rules = [r for r in rules if r.TYPE == 'line']
context = {}
for rule in token_rules:
context[rule.ID] = {}
for elem in parser.token_or_line_generator(buffer):
if isinstance(elem, parser.Token):
for rule in token_rules:
rule_conf = conf.rules[rule.ID]
for problem in rule.check(rule_conf,
elem.curr, elem.prev, elem.next,
context[rule.ID]):
problem.rule = rule.ID
problem.level = rule_conf['level']
yield problem
elif isinstance(elem, parser.Line):
for rule in line_rules:
rule_conf = conf.rules[rule.ID]
for problem in rule.check(rule_conf, elem):
problem.rule = rule.ID
problem.level = rule_conf['level']
yield problem
def get_syntax_error(buffer):
try:
list(yaml.parse(buffer, Loader=yaml.BaseLoader))
except yaml.error.MarkedYAMLError as e:
problem = LintProblem(e.problem_mark.line + 1,
e.problem_mark.column + 1,
'syntax error: ' + e.problem)
problem.level = 'error'
return problem
def _run(buffer, conf):
# If the document contains a syntax error, save it and yield it at the
# right line
syntax_error = get_syntax_error(buffer)
for problem in get_costemic_problems(buffer, conf):
# 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):
yield syntax_error
# If there is already a yamllint error at the same place, discard
# it as it is probably redundant (and maybe it's just a 'warning',
# in which case the script won't even exit with a failure status).
if (syntax_error.line == problem.line and
syntax_error.column == problem.column):
syntax_error = None
continue
syntax_error = None
yield problem
if syntax_error:
yield syntax_error
def run(input, conf):
"""Lints a YAML source.
Returns a generator of LintProblem objects.
:param input: buffer, string or stream to read from
:param conf: yamllint configuration object
"""
if type(input) == str:
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
content = input.read()
return _run(content, conf)
else:
raise TypeError('input should be a string or a stream')

View File

@@ -64,7 +64,7 @@ Use this rule to control the number of spaces before and after commas (``,``).
import yaml import yaml
from yamllint.errors import LintProblem from yamllint.linter import LintProblem
from yamllint.rules.common import spaces_after, spaces_before from yamllint.rules.common import spaces_after, spaces_before

View File

@@ -57,7 +57,7 @@ Use this rule to control the position and formatting of comments.
import yaml import yaml
from yamllint.errors import LintProblem from yamllint.linter import LintProblem
from yamllint.rules.common import get_comments_between_tokens from yamllint.rules.common import get_comments_between_tokens

View File

@@ -77,7 +77,7 @@ Use this rule to force comments to be indented like content.
import yaml import yaml
from yamllint.errors import LintProblem from yamllint.linter import LintProblem
from yamllint.rules.common import get_line_indent, get_comments_between_tokens from yamllint.rules.common import get_line_indent, get_comments_between_tokens

View File

@@ -16,7 +16,7 @@
import yaml import yaml
from yamllint.errors import LintProblem from yamllint.linter import LintProblem
def spaces_after(token, prev, next, min=-1, max=-1, def spaces_after(token, prev, next, min=-1, max=-1,

View File

@@ -76,7 +76,7 @@ Use this rule to require or forbid the use of document end marker (``...``).
import yaml import yaml
from yamllint.errors import LintProblem from yamllint.linter import LintProblem
ID = 'document-end' ID = 'document-end'

View File

@@ -66,7 +66,7 @@ Use this rule to require or forbid the use of document start marker (``---``).
import yaml import yaml
from yamllint.errors import LintProblem from yamllint.linter import LintProblem
ID = 'document-start' ID = 'document-start'

View File

@@ -50,7 +50,7 @@ Use this rule to set a maximal number of allowed consecutive blank lines.
""" """
from yamllint.errors import LintProblem from yamllint.linter import LintProblem
ID = 'empty-lines' ID = 'empty-lines'

View File

@@ -113,6 +113,18 @@ Use this rule to control the indentation.
Je vous écris une longue lettre parce que Je vous écris une longue lettre parce que
je n'ai pas le temps d'en écrire une courte. je n'ai pas le temps d'en écrire une courte.
the following code snippet would **PASS**:
::
Blaise Pascal: Je vous écris une longue lettre parce que
je n'ai pas le temps d'en écrire une courte.
the following code snippet would **FAIL**:
::
Blaise Pascal: Je vous écris une longue lettre parce que
je n'ai pas le temps d'en écrire une courte.
the following code snippet would **FAIL**: the following code snippet would **FAIL**:
:: ::
@@ -132,7 +144,7 @@ Use this rule to control the indentation.
import yaml import yaml
from yamllint.errors import LintProblem from yamllint.linter import LintProblem
from yamllint.rules.common import is_explicit_key from yamllint.rules.common import is_explicit_key
@@ -212,11 +224,7 @@ 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 indent < expected_indent: if indent != expected_indent:
yield LintProblem(line_no, indent + 1,
('wrong indentation: expected at least %d but '
'found %d') % (expected_indent, indent))
elif conf['check-multi-line-strings'] and 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))
@@ -252,7 +260,8 @@ def check(conf, token, prev, next, context):
'wrong indentation: expected %d but found %d' % 'wrong indentation: expected %d but found %d' %
(expected, found_indentation)) (expected, found_indentation))
if isinstance(token, yaml.ScalarToken): if (isinstance(token, yaml.ScalarToken) and
conf['check-multi-line-strings']):
for problem in check_scalar_indentation(conf, token, context): for problem in check_scalar_indentation(conf, token, context):
yield problem yield problem

View File

@@ -20,6 +20,9 @@ Use this rule to set a limit to lines length.
.. rubric:: Options .. rubric:: Options
* ``max`` defines the maximal (inclusive) length of lines. * ``max`` defines the maximal (inclusive) length of lines.
* ``allow-non-breakable-words`` is used to allow non breakable words (without
spaces inside) to overflow the limit. This is useful for long URLs, for
instance. Use ``yes`` to allow, ``no`` to forbid.
.. rubric:: Examples .. rubric:: Examples
@@ -38,19 +41,61 @@ Use this rule to set a limit to lines length.
long sentence: long sentence:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. tempor incididunt ut labore et dolore magna aliqua.
#. With ``line-length: {max: 60, allow-non-breakable-words: yes}``
the following code snippet would **PASS**:
::
this:
is:
- a:
http://localhost/very/very/very/very/very/very/very/very/long/url
# this comment is too long,
# but hard to split:
# http://localhost/another/very/very/very/very/very/very/very/very/long/url
the following code snippet would **FAIL**:
::
- this line is waaaaaaaaaaaaaay too long but could be easily splitted...
#. With ``line-length: {max: 60, allow-non-breakable-words: no}``
the following code snippet would **FAIL**:
::
this:
is:
- a:
http://localhost/very/very/very/very/very/very/very/very/long/url
""" """
from yamllint.errors import LintProblem from yamllint.linter import LintProblem
ID = 'line-length' ID = 'line-length'
TYPE = 'line' TYPE = 'line'
CONF = {'max': int} CONF = {'max': int,
'allow-non-breakable-words': bool}
def check(conf, line): def check(conf, line):
if line.end - line.start > conf['max']: if line.end - line.start > conf['max']:
if conf['allow-non-breakable-words']:
start = line.start
while start < line.end and line.buffer[start] == ' ':
start += 1
if start != line.end:
if line.buffer[start] == '#':
start += 2
if line.buffer.find(' ', start, line.end) == -1:
return
yield LintProblem(line.line_no, conf['max'] + 1, yield LintProblem(line.line_no, conf['max'] + 1,
'line too long (%d > %d characters)' % 'line too long (%d > %d characters)' %
(line.end - line.start, conf['max'])) (line.end - line.start, conf['max']))

View File

@@ -24,7 +24,7 @@ this convention too.
""" """
from yamllint.errors import LintProblem from yamllint.linter import LintProblem
ID = 'new-line-at-end-of-file' ID = 'new-line-at-end-of-file'

View File

@@ -24,7 +24,7 @@ Use this rule to force the type of new line characters.
""" """
from yamllint.errors import LintProblem from yamllint.linter import LintProblem
ID = 'new-lines' ID = 'new-lines'

View File

@@ -39,7 +39,7 @@ Use this rule to forbid trailing spaces at the end of lines.
import string import string
from yamllint.errors import LintProblem from yamllint.linter import LintProblem
ID = 'trailing-spaces' ID = 'trailing-spaces'