Compare commits

...

33 Commits

Author SHA1 Message Date
Adrien Vergé
a92743c8ca yamllint version 1.10.0 2017-11-05 10:17:55 +01:00
Adrien Vergé
501def327d tests: Use sys.executable instead of hard-coded 'python'
To test yamllint as a module, tests run commands like
`python -m yamllint`. But some environments (like continuous integration
of Debian or CentOS) don't always include the `python` executable (they
use `python3` instead).

Let's dynamically detect the Python executable path.
2017-11-05 10:06:46 +01:00
Adrien Vergé
ed5d319df8 tests: Use en_US.UTF-8 locale when C.UTF-8 not available
Some operating systems don't have the `C.UTF-8` locale installed yet
(for instance, CentOS 7). In such a case, fallback to `en_US.UTF-8` so
that tests can be run.

This follows commit 92ff315.
2017-11-05 10:02:22 +01:00
Adrien Vergé
6ec1e7b54a Distribution: Include tests in dist file
Since commit e948509 ("setup.py - don't distribute tests"), tests files
are not included in the `.tar.gz` bundle on a fresh repo clone. (On old
repos they were still included, because listed in
`yamllint.egg-info/SOURCES.txt`.)

Let's explicitly include them.
2017-11-05 09:50:46 +01:00
Adrien Vergé
c4475ece34 empty-values: Add forbid-in-flow-mappings conf
This allows preventing implicit `null` from empty values in flow
mappings.

For example:

    {a:}

    {a:, b: 2}

    {
      a: {
        b: ,
        c: {
          d: 4,
          e:
        }
      },
      f:
    }
2017-11-05 09:29:03 +01:00
Greg Dubicki
8537b0a164 Add rule: empty-values, to forbid implicit nulls
only in block mappings for now
2017-11-04 16:22:29 +01:00
Adrien Vergé
83ea74e2f8 CI: Compile documentation on Travis 2017-11-04 16:02:43 +01:00
Waylan Limberg
e43768f203 Better color support check.
Not all systems have `isatty` attribute on `sys.stdout` so check for
existance of attribute before checking value. Also don't use color in
Windows unless environ indicates support. Apparently, Windows can indicate
support by either the presence of `ANSICON` environ variable or if the
`TERM` environ variable is set to `ANSI`. Fixes #79.

No additional tests added, as the relevant tests use fcntl, which is a
Unix only lib. In fact, the tests won't even run in Windows.
2017-10-27 20:06:34 +02:00
Adrien Vergé
d422274563 style: Fix E722 errors reported by pycodestyle
Since a few days ago pycodestyle (formerly called pep8) has a new check:
E722 warning for bare except clauses.

Let's fix our code.
2017-10-27 17:22:35 +02:00
Adrien Vergé
2d931b5a81 yamllint version 1.9.0 2017-10-16 22:52:06 +02:00
Adrien Vergé
773bfc0f3c key-ordering: Add more test cases and documentation 2017-10-16 22:49:39 +02:00
Johannes F. Knauf
1543d0e435 New rule key-ordering
closes #67
2017-10-16 22:49:39 +02:00
Adrien Vergé
f82346dac7 indentation: Add more test cases for key following empty list 2017-10-16 22:17:58 +02:00
Tim Wade
ca540c113b Fix indentation rule for key following empty list
If a key-value pair follows an empty list, i.e.:

```yaml
a:
-
b: c
```

yamllint will complain:

```
warning  wrong indentation: expected 2 but found 0  (indentation)
```

This is because it is expecting the second key to be a continuation of
the block entry above:

```yaml
a:
-
  b: c
```

However, both are perfectly valid, though structurally different.
2017-10-16 22:17:58 +02:00
Adrien Vergé
c8fc170ff0 yamllint version 1.8.2 2017-10-10 12:30:00 +02:00
Adrien Vergé
c4a3e15ff0 docs(readthedocs): Fix builds on yamllint.readthedocs.io
Documentation builds on readthedocs.io partly fail because some modules
imported by yammlint cannot be imported in Sphinx automodule.

This commit fixes that using the tip at [1].

Closes #66

[1]: http://docs.readthedocs.io/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules
2017-09-03 16:08:42 +02:00
Sebastian Finke
db57127971 docs(integration): Fix pre-commit config file 2017-08-17 12:07:23 +02:00
blackillzone
c8e516be2f Add documentation for pre-commit 2017-07-19 14:56:21 +02:00
blackillzone
1c0dd48ccd Update pre-commit hook file 2017-07-19 14:56:21 +02:00
Adrien Vergé
f4edb85a04 fix(config): Be clearer about the ignore conf type 2017-07-19 09:48:00 +02:00
Adrien Vergé
d99bb9fec3 yamllint version 1.8.1 2017-07-04 22:23:02 +02:00
Adrien Vergé
3c4013fda1 docs(CHANGELOG): Add a changelog
Closes #57
2017-07-04 22:20:57 +02:00
Adrien Vergé
1a961bd4b0 chore(tests): Also run tests on Python 2.6 2017-07-04 22:07:32 +02:00
Adrien Vergé
7a8cfeed6d chore(deps): Require pathspec >= 0.5.3
This new version adds support for Python 2.6.
2017-07-04 22:07:21 +02:00
Adrien Vergé
f9709bc6e6 yamllint version 1.8.0 2017-06-28 15:30:39 +02:00
Adrien Vergé
5060917e40 style(cli): Space import sections 2017-06-28 15:20:24 +02:00
Adrien Vergé
a052cf7dba chore(tests): Add flake8-import-order linter plugin 2017-06-28 15:18:40 +02:00
Adrien Vergé
ae33716529 chore(tests): Also run tests on Python 3.6 2017-06-28 15:14:46 +02:00
Adrien Vergé
df26cc0438 feat(config): Add support to ignore paths on per-rule basis
Example of configuration to use this feature:

    # For all rules
    ignore: |
      *.dont-lint-me.yaml
      /bin/
      !/bin/*.lint-me-anyway.yaml

    rules:
      key-duplicates:
        ignore: |
          generated
          *.template.yaml
      trailing-spaces:
        ignore: |
          *.ignore-trailing-spaces.yaml
          /ascii-art/*

Closes #43.
2017-06-28 15:14:46 +02:00
Adrien Vergé
342d7b49dd tests(cli): Create a temp test workspace only once
Do not re-create it for every test in the class.
2017-06-28 15:11:24 +02:00
Adrien Vergé
7d638d47b9 tests(cli): Refactor temp test workspace recreation
Make it simpler and re-usable.
2017-06-28 15:11:24 +02:00
Adrien Vergé
db116eaaaf Merge pull request #51 from sedrubal/feature_use-argparse-mutually_exclusive_group
Use argparse mutually_exclusive_group for --config-file and --config-data
2017-05-31 22:43:44 +02:00
sedrubal
30dfa78923 Use argparse mutually_exclusive_group for --config-file and --config-data
This does the same as your solution 😉
2017-05-28 22:59:33 +02:00
30 changed files with 1078 additions and 101 deletions

View File

@@ -8,4 +8,4 @@
description: This hook runs yamllint.
entry: yamllint
language: python
files: \.(yaml|yml)$
types: [file, yaml]

View File

@@ -1,17 +1,23 @@
---
language: python
python:
- 2.6
- 2.7
- 3.3
- 3.4
- 3.5
- 3.6
- nightly
install:
- pip install pyyaml flake8 coveralls
- pip install pyyaml flake8 flake8-import-order coveralls sphinx
- if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi
- pip install .
script:
- flake8 .
- if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then flake8 .; fi
- yamllint --strict $(git ls-files '*.yaml' '*.yml')
- coverage run --source=yamllint setup.py test
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then
python setup.py build_sphinx;
fi
after_success:
coveralls

38
CHANGELOG.rst Normal file
View File

@@ -0,0 +1,38 @@
Changelog
=========
1.10.0 (2017-11-05)
-------------------
- Fix colored output on Windows
- Check documentation compilation on continuous integration
- Add a new `empty-values` rule
- Make sure test files are included in dist bundle
- Tests: Use en_US.UTF-8 locale when C.UTF-8 not available
- Tests: Dynamically detect Python executable path
1.9.0 (2017-10-16)
------------------
- Add a new `key-ordering` rule
- Fix indentation rule for key following empty list
1.8.2 (2017-10-10)
------------------
- Be clearer about the `ignore` conf type
- Update pre-commit hook file
- Add documentation for pre-commit
1.8.1 (2017-07-04)
------------------
- Require pathspec >= 0.5.3
- Support Python 2.6
- Add a changelog
1.8.0 (2017-06-28)
------------------
- Refactor argparse with mutually_exclusive_group
- Add support to ignore paths in configuration

View File

@@ -1,3 +1,4 @@
include LICENSE
include README.rst
include docs/*
include tests/*.py tests/rules/*.py tests/yaml-1.2-spec-examples/*

View File

@@ -119,6 +119,27 @@ or for a whole block:
consectetur : adipiscing elit
# yamllint enable
Specific files can be ignored (totally or for some rules only) using a
``.gitignore``-style pattern:
.. code:: yaml
# For all rules
ignore: |
*.dont-lint-me.yaml
/bin/
!/bin/*.lint-me-anyway.yaml
rules:
key-duplicates:
ignore: |
generated
*.template.yaml
trailing-spaces:
ignore: |
*.ignore-trailing-spaces.yaml
/ascii-art/*
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`_
License

View File

@@ -4,6 +4,7 @@
import sys
import os
from unittest.mock import MagicMock
sys.path.insert(0, os.path.abspath('..')) # noqa
@@ -40,3 +41,15 @@ htmlhelp_basename = 'yamllintdoc'
man_pages = [
('index', 'yamllint', '', [u'Adrien Vergé'], 1)
]
# -- Build with sphinx automodule without needing to install third-party libs
class Mock(MagicMock):
@classmethod
def __getattr__(cls, name):
return MagicMock()
MOCK_MODULES = ['pathspec', 'yaml']
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)

View File

@@ -114,3 +114,57 @@ return code will be:
* ``0`` if no errors or warnings occur
* ``1`` if one or more errors occur
* ``2`` if no errors occur, but one or more warnings occur
Ignoring paths
--------------
It is possible to exclude specific files or directories, so that the linter
doesn't process them.
You can either totally ignore files (they won't be looked at):
.. code-block:: yaml
extends: default
ignore: |
/this/specific/file.yaml
/all/this/directory/
*.template.yaml
or ignore paths only for specific rules:
.. code-block:: yaml
extends: default
rules:
trailing-spaces:
ignore: |
/this-file-has-trailing-spaces-but-it-is-OK.yaml
/generated/*.yaml
Note that this ``.gitignore``-style path pattern allows complex path
exclusion/inclusion, see the `pathspec README file
<https://pypi.python.org/pypi/pathspec>`_ for more details.
Here is a more complex example:
.. code-block:: yaml
# For all rules
ignore: |
*.dont-lint-me.yaml
/bin/
!/bin/*.lint-me-anyway.yaml
extends: default
rules:
key-duplicates:
ignore: |
generated
*.template.yaml
trailing-spaces:
ignore: |
*.ignore-trailing-spaces.yaml
/ascii-art/*

View File

@@ -26,3 +26,4 @@ Table of contents
disable_with_comments
development
text_editors
integration

17
docs/integration.rst Normal file
View File

@@ -0,0 +1,17 @@
Integration with other software
===============================
Integration with pre-commit
---------------------------
You can integrate yamllint in `pre-commit <http://pre-commit.com/>`_ tool.
Here is an example, to add in your .pre-commit-config.yaml
.. code:: yaml
---
# Update the sha variable with the release version that you want, from the yamllint repo
- repo: https://github.com/adrienverge/yamllint.git
sha: v1.8.1
hooks:
- id: yamllint

View File

@@ -59,6 +59,11 @@ empty-lines
.. automodule:: yamllint.rules.empty_lines
empty-values
------------
.. automodule:: yamllint.rules.empty_values
hyphens
-------
@@ -74,6 +79,11 @@ key-duplicates
.. automodule:: yamllint.rules.key_duplicates
key-ordering
--------------
.. automodule:: yamllint.rules.key_ordering
line-length
-----------

View File

@@ -1,2 +1,11 @@
[bdist_wheel]
universal = 1
[flake8]
import-order-style = pep8
[build_sphinx]
all-files = 1
source-dir = docs
build-dir = docs/_build
warning-is-error = 1

View File

@@ -44,9 +44,7 @@ setup(
packages=find_packages(exclude=['tests', 'tests.*']),
entry_points={'console_scripts': ['yamllint=yamllint.cli:run']},
package_data={'yamllint': ['conf/*.yaml'],
'tests': ['yaml-1.2-spec-examples/*']},
install_requires=['pyyaml'],
tests_require=['nose'],
test_suite='nose.collector',
package_data={'yamllint': ['conf/*.yaml']},
install_requires=['pathspec >=0.5.3', 'pyyaml'],
test_suite='tests',
)

View File

@@ -14,7 +14,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
import os
import tempfile
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except AssertionError:
import unittest2 as unittest
import yaml
@@ -49,3 +56,21 @@ class RuleTestCase(unittest.TestCase):
real_problems = list(linter.run(source, self.build_fake_config(conf)))
self.assertEqual(real_problems, expected_problems)
def build_temp_workspace(files):
tempdir = tempfile.mkdtemp(prefix='yamllint-tests-')
for path, content in files.items():
path = os.path.join(tempdir, path)
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
if type(content) is list:
os.mkdir(path)
else:
mode = 'wb' if isinstance(content, bytes) else 'w'
with open(path, mode) as f:
f.write(content)
return tempdir

View File

@@ -0,0 +1,261 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Greg Dubicki
#
# 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/>.
from tests.common import RuleTestCase
class EmptyValuesTestCase(RuleTestCase):
rule_id = 'empty-values'
def test_disabled(self):
conf = ('empty-values: disable\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'foo:\n', conf)
self.check('---\n'
'foo:\n'
' bar:\n', conf)
self.check('---\n'
'{a:}\n', conf)
self.check('---\n'
'foo: {a:}\n', conf)
self.check('---\n'
'- {a:}\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf)
self.check('---\n'
'{a: {b: , c: {d: 4, e:}}, f:}\n', conf)
def test_in_block_mappings_disabled(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n', conf)
self.check('---\n'
'foo:\n'
'bar: aaa\n', conf)
def test_in_block_mappings_single_line(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'implicitly-null:\n', conf, problem1=(2, 17))
self.check('---\n'
'implicitly-null:with-colons:in-key:\n', conf,
problem1=(2, 36))
self.check('---\n'
'implicitly-null:with-colons:in-key2:\n', conf,
problem1=(2, 37))
def test_in_block_mappings_all_lines(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
'bar:\n'
'foobar:\n', conf, problem1=(2, 5),
problem2=(3, 5), problem3=(4, 8))
def test_in_block_mappings_explicit_end_of_document(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
'...\n', conf, problem1=(2, 5))
def test_in_block_mappings_not_end_of_document(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
'bar:\n'
' aaa\n', conf, problem1=(2, 5))
def test_in_block_mappings_different_level(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
' bar:\n'
'aaa: bbb\n', conf, problem1=(3, 6))
def test_in_block_mappings_empty_flow_mapping(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'foo: {a:}\n', conf)
self.check('---\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf)
def test_in_block_mappings_empty_block_sequence(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
' -\n', conf)
def test_in_block_mappings_not_empty_or_explicit_null(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
' bar:\n'
' aaa\n', conf)
self.check('---\n'
'explicitly-null: null\n', conf)
self.check('---\n'
'explicitly-null:with-colons:in-key: null\n', conf)
self.check('---\n'
'false-null: nulL\n', conf)
self.check('---\n'
'empty-string: \'\'\n', conf)
self.check('---\n'
'nullable-boolean: false\n', conf)
self.check('---\n'
'nullable-int: 0\n', conf)
self.check('---\n'
'First occurrence: &anchor Foo\n'
'Second occurrence: *anchor\n', conf)
def test_in_block_mappings_various_explicit_null(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'null-alias: ~\n', conf)
self.check('---\n'
'null-key1: {?: val}\n', conf)
self.check('---\n'
'null-key2: {? !!null "": val}\n', conf)
def test_in_block_mappings_comments(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n'
'comments: disable\n')
self.check('---\n'
'empty: # comment\n'
'foo:\n'
' bar: # comment\n', conf,
problem1=(2, 7),
problem2=(4, 7))
def test_in_flow_mappings_disabled(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: false}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'{a:}\n', conf)
self.check('---\n'
'foo: {a:}\n', conf)
self.check('---\n'
'- {a:}\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf)
self.check('---\n'
'{a: {b: , c: {d: 4, e:}}, f:}\n', conf)
def test_in_flow_mappings_single_line(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'{a:}\n', conf,
problem=(2, 4))
self.check('---\n'
'foo: {a:}\n', conf,
problem=(2, 9))
self.check('---\n'
'- {a:}\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf,
problem1=(2, 6),
problem2=(3, 6),
problem3=(4, 12),
problem4=(5, 12))
self.check('---\n'
'{a: {b: , c: {d: 4, e:}}, f:}\n', conf,
problem1=(2, 8),
problem2=(2, 23),
problem3=(2, 29))
def test_in_flow_mappings_multi_line(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'foo: {\n'
' a:\n'
'}\n', conf,
problem=(3, 5))
self.check('---\n'
'{\n'
' a: {\n'
' b: ,\n'
' c: {\n'
' d: 4,\n'
' e:\n'
' }\n'
' },\n'
' f:\n'
'}\n', conf,
problem1=(4, 7),
problem2=(7, 9),
problem3=(10, 5))
def test_in_flow_mappings_various_explicit_null(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'{explicit-null: null}\n', conf)
self.check('---\n'
'{null-alias: ~}\n', conf)
self.check('---\n'
'null-key1: {?: val}\n', conf)
self.check('---\n'
'null-key2: {? !!null "": val}\n', conf)
def test_in_flow_mappings_comments(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n'
'comments: disable\n')
self.check('---\n'
'{\n'
' a: {\n'
' b: , # comment\n'
' c: {\n'
' d: 4, # comment\n'
' e: # comment\n'
' }\n'
' },\n'
' f: # comment\n'
'}\n', conf,
problem1=(4, 7),
problem2=(7, 9),
problem3=(10, 5))

View File

@@ -589,6 +589,9 @@ class IndentationTestCase(RuleTestCase):
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
' k4:\n'
' -\n'
' k5: v3\n'
'...\n', conf)
conf = 'indentation: {spaces: 2, indent-sequences: true}'
self.check('---\n'
@@ -1208,6 +1211,20 @@ class IndentationTestCase(RuleTestCase):
' - name: Linux\n'
' date: 1991\n'
'...\n', conf, problem=(5, 10, 'syntax'))
conf = 'indentation: {spaces: 2, indent-sequences: true}'
self.check('---\n'
'a:\n'
'-\n' # empty list
'b: c\n'
'...\n', conf, problem=(3, 1))
conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
self.check('---\n'
'a:\n'
' -\n' # empty list
'b:\n'
'-\n'
'c: d\n'
'...\n', conf, problem=(5, 1))
def test_over_indented(self):
conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
@@ -1264,6 +1281,20 @@ class IndentationTestCase(RuleTestCase):
' - subel\n'
'...\n', conf,
problem=(2, 3))
conf = 'indentation: {spaces: 2, indent-sequences: false}'
self.check('---\n'
'a:\n'
' -\n' # empty list
'b: c\n'
'...\n', conf, problem=(3, 3))
conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
self.check('---\n'
'a:\n'
'-\n' # empty list
'b:\n'
' -\n'
'c: d\n'
'...\n', conf, problem=(5, 3))
def test_multi_lines(self):
conf = 'indentation: {spaces: consistent, indent-sequences: true}'

View File

@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Johannes F. Knauf
#
# 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/>.
from tests.common import RuleTestCase
class KeyOrderingTestCase(RuleTestCase):
rule_id = 'key-ordering'
def test_disabled(self):
conf = 'key-ordering: disable'
self.check('---\n'
'block mapping:\n'
' secondkey: a\n'
' firstkey: b\n', conf)
self.check('---\n'
'flow mapping:\n'
' {secondkey: a, firstkey: b}\n', conf)
self.check('---\n'
'second: before_first\n'
'at: root\n', conf)
self.check('---\n'
'nested but OK:\n'
' second: {first: 1}\n'
' third:\n'
' second: 2\n', conf)
def test_enabled(self):
conf = 'key-ordering: enable'
self.check('---\n'
'block mapping:\n'
' secondkey: a\n'
' firstkey: b\n', conf,
problem=(4, 3))
self.check('---\n'
'flow mapping:\n'
' {secondkey: a, firstkey: b}\n', conf,
problem=(3, 18))
self.check('---\n'
'second: before_first\n'
'at: root\n', conf,
problem=(3, 1))
self.check('---\n'
'nested but OK:\n'
' second: {first: 1}\n'
' third:\n'
' second: 2\n', conf)
def test_word_length(self):
conf = 'key-ordering: enable'
self.check('---\n'
'a: 1\n'
'ab: 1\n'
'abc: 1\n', conf)
self.check('---\n'
'a: 1\n'
'abc: 1\n'
'ab: 1\n', conf,
problem=(4, 1))
def test_key_duplicates(self):
conf = ('key-duplicates: disable\n'
'key-ordering: enable')
self.check('---\n'
'key: 1\n'
'key: 2\n', conf)
def test_case(self):
conf = 'key-ordering: enable'
self.check('---\n'
'T-shirt: 1\n'
'T-shirts: 2\n'
't-shirt: 3\n'
't-shirts: 4\n', conf)
self.check('---\n'
'T-shirt: 1\n'
't-shirt: 2\n'
'T-shirts: 3\n'
't-shirts: 4\n', conf,
problem=(4, 1))
def test_accents(self):
conf = 'key-ordering: enable'
self.check('---\n'
'hair: true\n'
'hais: true\n'
'haïr: true\n'
'haïssable: true\n', conf)
self.check('---\n'
'haïr: true\n'
'hais: true\n', conf,
problem=(3, 1))
self.check('---\n'
'haïr: true\n'
'hais: true\n', conf,
problem=(3, 1))
def test_key_tokens_in_flow_sequences(self):
conf = 'key-ordering: enable'
self.check('---\n'
'[\n'
' key: value, mappings, in, flow: sequence\n'
']\n', conf)

View File

@@ -23,65 +23,59 @@ import locale
import os
import pty
import shutil
import tempfile
import unittest
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except AssertionError:
import unittest2 as unittest
from yamllint import cli
from tests.common import build_temp_workspace
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
class CommandLineTestCase(unittest.TestCase):
def setUp(self):
self.wd = tempfile.mkdtemp(prefix='yamllint-tests-')
@classmethod
def setUpClass(cls):
super(CommandLineTestCase, cls).setUpClass()
# .yaml file at root
with open(os.path.join(self.wd, 'a.yaml'), 'w') as f:
f.write('---\n'
'- 1 \n'
'- 2')
cls.wd = build_temp_workspace({
# .yaml file at root
'a.yaml': '---\n'
'- 1 \n'
'- 2',
# file with only one warning
'warn.yaml': 'key: value\n',
# .yml file at root
'empty.yml': '',
# file in dir
'sub/ok.yaml': '---\n'
'key: value\n',
# file in very nested dir
's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml': '---\n'
'key: value\n'
'key: other value\n',
# empty dir
'empty-dir': [],
# non-YAML file
'no-yaml.json': '---\n'
'key: value\n',
# non-ASCII chars
'non-ascii/utf-8': (
u'---\n'
u'- hétérogénéité\n'
u'# 19.99 €\n'
u'- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
})
# file with only one warning
with open(os.path.join(self.wd, 'warn.yaml'), 'w') as f:
f.write('key: value\n')
@classmethod
def tearDownClass(cls):
super(CommandLineTestCase, cls).tearDownClass()
# .yml file at root
open(os.path.join(self.wd, 'empty.yml'), 'w').close()
# file in dir
os.mkdir(os.path.join(self.wd, 'sub'))
with open(os.path.join(self.wd, 'sub', 'ok.yaml'), 'w') as f:
f.write('---\n'
'key: value\n')
# file in very nested dir
dir = self.wd
for i in range(15):
dir = os.path.join(dir, 's')
os.mkdir(dir)
with open(os.path.join(dir, 'file.yaml'), 'w') as f:
f.write('---\n'
'key: value\n'
'key: other value\n')
# empty dir
os.mkdir(os.path.join(self.wd, 'empty-dir'))
# non-YAML file
with open(os.path.join(self.wd, 'no-yaml.json'), 'w') as f:
f.write('---\n'
'key: value\n')
# non-ASCII chars
os.mkdir(os.path.join(self.wd, 'non-ascii'))
with open(os.path.join(self.wd, 'non-ascii', 'utf-8'), 'wb') as f:
f.write((u'---\n'
u'- hétérogénéité\n'
u'# 19.99 €\n'
u'- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'))
def tearDown(self):
shutil.rmtree(self.wd)
shutil.rmtree(cls.wd)
def test_find_files_recursively(self):
self.assertEqual(
@@ -145,8 +139,11 @@ class CommandLineTestCase(unittest.TestCase):
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^Options --config-file and '
r'--config-data cannot be used')
self.assertRegexpMatches(
err.splitlines()[-1],
r'^yamllint: error: argument -d\/--config-data: '
r'not allowed with argument -c\/--config-file$'
)
def test_run_with_bad_config(self):
sys.stdout, sys.stderr = StringIO(), StringIO()
@@ -302,7 +299,10 @@ class CommandLineTestCase(unittest.TestCase):
# Make sure the default localization conditions on this "system"
# support UTF-8 encoding.
loc = locale.getlocale()
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
try:
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
except locale.Error:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:

View File

@@ -14,10 +14,24 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
import os
import shutil
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except AssertionError:
import unittest2 as unittest
from yamllint import cli
from yamllint import config
from tests.common import build_temp_workspace
class SimpleConfigTestCase(unittest.TestCase):
def test_parse_config(self):
@@ -30,7 +44,7 @@ class SimpleConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons']['max-spaces-before'], 0)
self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
self.assertEqual(len(new.enabled_rules()), 1)
self.assertEqual(len(new.enabled_rules(None)), 1)
def test_invalid_conf(self):
with self.assertRaises(config.YamlLintConfigError):
@@ -170,7 +184,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 2)
self.assertEqual(len(new.enabled_rules(None)), 2)
def test_extend_remove_rule(self):
old = config.YamlLintConfig('rules:\n'
@@ -187,7 +201,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons'], False)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 1)
self.assertEqual(len(new.enabled_rules(None)), 1)
def test_extend_edit_rule(self):
old = config.YamlLintConfig('rules:\n'
@@ -207,7 +221,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons']['max-spaces-after'], 4)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 2)
self.assertEqual(len(new.enabled_rules(None)), 2)
def test_extend_reenable_rule(self):
old = config.YamlLintConfig('rules:\n'
@@ -225,7 +239,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 2)
self.assertEqual(len(new.enabled_rules(None)), 2)
class ExtendedLibraryConfigTestCase(unittest.TestCase):
@@ -270,3 +284,94 @@ class ExtendedLibraryConfigTestCase(unittest.TestCase):
self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys()))
for rule in new.rules:
self.assertEqual(new.rules[rule], old.rules[rule])
class IgnorePathConfigTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
super(IgnorePathConfigTestCase, cls).setUpClass()
bad_yaml = ('---\n'
'- key: val1\n'
' key: val2\n'
'- trailing space \n'
'- lonely hyphen\n')
cls.wd = build_temp_workspace({
'bin/file.lint-me-anyway.yaml': bad_yaml,
'bin/file.yaml': bad_yaml,
'file-at-root.yaml': bad_yaml,
'file.dont-lint-me.yaml': bad_yaml,
'ign-dup/file.yaml': bad_yaml,
'ign-dup/sub/dir/file.yaml': bad_yaml,
'ign-trail/file.yaml': bad_yaml,
'include/ign-dup/sub/dir/file.yaml': bad_yaml,
's/s/ign-trail/file.yaml': bad_yaml,
's/s/ign-trail/s/s/file.yaml': bad_yaml,
's/s/ign-trail/s/s/file2.lint-me-anyway.yaml': bad_yaml,
'.yamllint': 'ignore: |\n'
' *.dont-lint-me.yaml\n'
' /bin/\n'
' !/bin/*.lint-me-anyway.yaml\n'
'\n'
'extends: default\n'
'\n'
'rules:\n'
' key-duplicates:\n'
' ignore: |\n'
' /ign-dup\n'
' trailing-spaces:\n'
' ignore: |\n'
' ign-trail\n'
' !*.lint-me-anyway.yaml\n',
})
cls.backup_wd = os.getcwd()
os.chdir(cls.wd)
@classmethod
def tearDownClass(cls):
super(IgnorePathConfigTestCase, cls).tearDownClass()
os.chdir(cls.backup_wd)
shutil.rmtree(cls.wd)
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
def test_run_with_ignored_path(self):
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
out = sys.stdout.getvalue()
out = '\n'.join(sorted(out.splitlines()))
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
trailing = '[error] trailing spaces (trailing-spaces)'
hyphen = '[error] too many spaces after hyphen (hyphens)'
self.assertEqual(out, '\n'.join((
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
'./file-at-root.yaml:3:3: ' + keydup,
'./file-at-root.yaml:4:17: ' + trailing,
'./file-at-root.yaml:5:5: ' + hyphen,
'./ign-dup/file.yaml:4:17: ' + trailing,
'./ign-dup/file.yaml:5:5: ' + hyphen,
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./ign-trail/file.yaml:3:3: ' + keydup,
'./ign-trail/file.yaml:5:5: ' + hyphen,
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))

View File

@@ -15,8 +15,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import io
import unittest
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except AssertionError:
import unittest2 as unittest
from yamllint.config import YamlLintConfig
from yamllint import linter

View File

@@ -18,9 +18,18 @@ import os
import shutil
import subprocess
import tempfile
import unittest
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except AssertionError:
import unittest2 as unittest
PYTHON = sys.executable or 'python'
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
class ModuleTestCase(unittest.TestCase):
def setUp(self):
self.wd = tempfile.mkdtemp(prefix='yamllint-tests-')
@@ -40,7 +49,7 @@ class ModuleTestCase(unittest.TestCase):
def test_run_module_no_args(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output(['python', '-m', 'yamllint'],
subprocess.check_output([PYTHON, '-m', 'yamllint'],
stderr=subprocess.STDOUT)
self.assertEqual(ctx.exception.returncode, 2)
self.assertRegexpMatches(ctx.exception.output.decode(),
@@ -48,7 +57,7 @@ class ModuleTestCase(unittest.TestCase):
def test_run_module_on_bad_dir(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output(['python', '-m', 'yamllint',
subprocess.check_output([PYTHON, '-m', 'yamllint',
'/does/not/exist'],
stderr=subprocess.STDOUT)
self.assertRegexpMatches(ctx.exception.output.decode(),
@@ -56,7 +65,7 @@ class ModuleTestCase(unittest.TestCase):
def test_run_module_on_file(self):
out = subprocess.check_output(
['python', '-m', 'yamllint', os.path.join(self.wd, 'warn.yaml')])
[PYTHON, '-m', 'yamllint', os.path.join(self.wd, 'warn.yaml')])
lines = out.decode().splitlines()
self.assertIn('/warn.yaml', lines[0])
self.assertEqual('\n'.join(lines[1:]),
@@ -65,7 +74,7 @@ class ModuleTestCase(unittest.TestCase):
def test_run_module_on_dir(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output(['python', '-m', 'yamllint', self.wd])
subprocess.check_output([PYTHON, '-m', 'yamllint', self.wd])
self.assertEqual(ctx.exception.returncode, 1)
files = ctx.exception.output.decode().split('\n\n')

View File

@@ -14,7 +14,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except AssertionError:
import unittest2 as unittest
import yaml

View File

@@ -22,7 +22,7 @@ indentation, etc."""
APP_NAME = 'yamllint'
APP_VERSION = '1.7.0'
APP_VERSION = '1.10.0'
APP_DESCRIPTION = __doc__
__author__ = u'Adrien Vergé'

View File

@@ -15,9 +15,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
import os.path
import sys
import os
import sys
import platform
import argparse
from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION
@@ -37,6 +38,15 @@ def find_files_recursively(items):
yield item
def supports_color():
supported_platform = not (platform.system() == 'Windows' and not
('ANSICON' in os.environ or
('TERM' in os.environ and
os.environ['TERM'] == 'ANSI')))
return (supported_platform and
hasattr(sys.stdout, 'isatty') and sys.stdout.isatty())
class Format(object):
@staticmethod
def parsable(problem, filename):
@@ -78,11 +88,13 @@ def run(argv=None):
description=APP_DESCRIPTION)
parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+',
help='files to check')
parser.add_argument('-c', '--config-file', dest='config_file',
action='store', help='path to a custom configuration')
parser.add_argument('-d', '--config-data', dest='config_data',
action='store',
help='custom configuration (as YAML source)')
config_group = parser.add_mutually_exclusive_group()
config_group.add_argument('-c', '--config-file', dest='config_file',
action='store',
help='path to a custom configuration')
config_group.add_argument('-d', '--config-data', dest='config_data',
action='store',
help='custom configuration (as YAML source)')
parser.add_argument('-f', '--format',
choices=('parsable', 'standard'), default='standard',
help='format for parsing output')
@@ -97,11 +109,6 @@ def run(argv=None):
args = parser.parse_args(argv)
if args.config_file is not None and args.config_data is not None:
print('Options --config-file and --config-data cannot be used '
'simultaneously.', file=sys.stderr)
sys.exit(-1)
# User-global config is supposed to be in ~/.config/yamllint/config
if 'XDG_CONFIG_HOME' in os.environ:
user_global_config = os.path.join(
@@ -129,13 +136,14 @@ def run(argv=None):
max_level = 0
for file in find_files_recursively(args.files):
filepath = file[2:] if file.startswith('./') else file
try:
first = True
with open(file) as f:
for problem in linter.run(f, conf):
for problem in linter.run(f, conf, filepath):
if args.format == 'parsable':
print(Format.parsable(problem, file))
elif sys.stdout.isatty():
elif supports_color():
if first:
print('\033[4m%s\033[0m' % file)
first = False

View File

@@ -32,6 +32,9 @@ rules:
max: 2
max-start: 0
max-end: 0
empty-values:
forbid-in-block-mappings: false
forbid-in-flow-mappings: false
hyphens:
max-spaces-after: 1
indentation:
@@ -39,6 +42,7 @@ rules:
indent-sequences: true
check-multi-line-strings: false
key-duplicates: enable
key-ordering: disable
line-length:
max: 80
allow-non-breakable-words: true

View File

@@ -16,6 +16,7 @@
import os.path
import pathspec
import yaml
import yamllint.rules
@@ -29,6 +30,8 @@ class YamlLintConfig(object):
def __init__(self, content=None, file=None):
assert (content is None) ^ (file is None)
self.ignore = None
if file is not None:
with open(file) as f:
content = f.read()
@@ -36,9 +39,14 @@ class YamlLintConfig(object):
self.parse(content)
self.validate()
def enabled_rules(self):
def is_file_ignored(self, filepath):
return self.ignore and self.ignore.match_file(filepath)
def enabled_rules(self, filepath):
return [yamllint.rules.get(id) for id, val in self.rules.items()
if val is not False]
if val is not False and (
filepath is None or 'ignore' not in val or
not val['ignore'].match_file(filepath))]
def extend(self, base_config):
assert isinstance(base_config, YamlLintConfig)
@@ -53,6 +61,9 @@ class YamlLintConfig(object):
self.rules = base_config.rules
if base_config.ignore is not None:
self.ignore = base_config.ignore
def parse(self, raw_content):
try:
conf = yaml.safe_load(raw_content)
@@ -73,6 +84,13 @@ class YamlLintConfig(object):
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
if 'ignore' in conf:
if type(conf['ignore']) != str:
raise YamlLintConfigError(
'invalid config: ignore should contain file patterns')
self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
def validate(self):
for id in self.rules:
try:
@@ -90,6 +108,14 @@ def validate_rule_conf(rule, conf):
conf = {}
if type(conf) == dict:
if ('ignore' in conf and
type(conf['ignore']) != pathspec.pathspec.PathSpec):
if type(conf['ignore']) != str:
raise YamlLintConfigError(
'invalid config: ignore should contain file patterns')
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
if 'level' not in conf:
conf['level'] = 'error'
elif conf['level'] not in ('error', 'warning'):
@@ -98,7 +124,7 @@ def validate_rule_conf(rule, conf):
options = getattr(rule, 'CONF', {})
for optkey in conf:
if optkey == 'level':
if optkey in ('ignore', 'level'):
continue
if optkey not in options:
raise YamlLintConfigError(

View File

@@ -63,8 +63,8 @@ class LintProblem(object):
return '%d:%d: %s' % (self.line, self.column, self.message)
def get_cosmetic_problems(buffer, conf):
rules = conf.enabled_rules()
def get_cosmetic_problems(buffer, conf, filepath):
rules = conf.enabled_rules(filepath)
# Split token rules from line rules
token_rules = [r for r in rules if r.TYPE == 'token']
@@ -185,7 +185,7 @@ def get_syntax_error(buffer):
return problem
def _run(buffer, conf):
def _run(buffer, conf, filepath):
assert hasattr(buffer, '__getitem__'), \
'_run() argument must be a buffer, not a stream'
@@ -193,7 +193,7 @@ def _run(buffer, conf):
# right line
syntax_error = get_syntax_error(buffer)
for problem in get_cosmetic_problems(buffer, conf):
for problem in get_cosmetic_problems(buffer, conf, filepath):
# Insert the syntax error (if any) at the right place...
if (syntax_error and syntax_error.line <= problem.line and
syntax_error.column <= problem.column):
@@ -215,7 +215,7 @@ def _run(buffer, conf):
yield syntax_error
def run(input, conf):
def run(input, conf, filepath=None):
"""Lints a YAML source.
Returns a generator of LintProblem objects.
@@ -223,11 +223,14 @@ def run(input, conf):
:param input: buffer, string or stream to read from
:param conf: yamllint configuration object
"""
if conf.is_file_ignored(filepath):
return ()
if type(input) in (type(b''), type(u'')): # compat with Python 2 & 3
return _run(input, conf)
return _run(input, conf, filepath)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
# We need to have everything in memory to parse correctly
content = input.read()
return _run(content, conf)
return _run(content, conf, filepath)
else:
raise TypeError('input should be a string or a stream')

View File

@@ -24,9 +24,11 @@ from yamllint.rules import (
document_end,
document_start,
empty_lines,
empty_values,
hyphens,
indentation,
key_duplicates,
key_ordering,
line_length,
new_line_at_end_of_file,
new_lines,
@@ -44,9 +46,11 @@ _RULES = {
document_end.ID: document_end,
document_start.ID: document_start,
empty_lines.ID: empty_lines,
empty_values.ID: empty_values,
hyphens.ID: hyphens,
indentation.ID: indentation,
key_duplicates.ID: key_duplicates,
key_ordering.ID: key_ordering,
line_length.ID: line_length,
new_line_at_end_of_file.ID: new_line_at_end_of_file,
new_lines.ID: new_lines,

View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Greg Dubicki
#
# 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/>.
"""
Use this rule to prevent nodes with empty content, that implicitly result in
``null`` values.
.. rubric:: Options
* Use ``forbid-in-block-mappings`` to prevent empty values in block mappings.
* Use ``forbid-in-flow-mappings`` to prevent empty values in flow mappings.
.. rubric:: Examples
#. With ``empty-values: {forbid-in-block-mappings: true}``
the following code snippets would **PASS**:
::
some-mapping:
sub-element: correctly indented
::
explicitly-null: null
the following code snippets would **FAIL**:
::
some-mapping:
sub-element: incorrectly indented
::
implicitly-null:
#. With ``empty-values: {forbid-in-flow-mappings: true}``
the following code snippet would **PASS**:
::
{prop: null}
{a: 1, b: 2, c: 3}
the following code snippets would **FAIL**:
::
{prop: }
::
{a: 1, b:, c: 3}
"""
import yaml
from yamllint.linter import LintProblem
ID = 'empty-values'
TYPE = 'token'
CONF = {'forbid-in-block-mappings': bool,
'forbid-in-flow-mappings': bool}
def check(conf, token, prev, next, nextnext, context):
if conf['forbid-in-block-mappings']:
if isinstance(token, yaml.ValueToken) and isinstance(next, (
yaml.KeyToken, yaml.BlockEndToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'empty value in block mapping')
if conf['forbid-in-flow-mappings']:
if isinstance(token, yaml.ValueToken) and isinstance(next, (
yaml.FlowEntryToken, yaml.FlowMappingEndToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'empty value in flow mapping')

View File

@@ -399,6 +399,10 @@ def _check(conf, token, prev, next, nextnext, context):
# - item 1
# - item 2
indent = next.start_mark.column
elif next.start_mark.column == token.start_mark.column:
# -
# key: value
indent = next.start_mark.column
else:
# -
# item 1

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Johannes F. Knauf
#
# 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/>.
"""
Use this rule to enforce alphabetical ordering of keys in mappings. The sorting
order uses the Unicode code point number. As a result, the ordering is
case-sensitive and not accent-friendly (see examples below).
.. rubric:: Examples
#. With ``key-ordering: {}``
the following code snippet would **PASS**:
::
- key 1: v
key 2: val
key 3: value
- {a: 1, b: 2, c: 3}
- T-shirt: 1
T-shirts: 2
t-shirt: 3
t-shirts: 4
- hair: true
hais: true
haïr: true
haïssable: true
the following code snippet would **FAIL**:
::
- key 2: v
key 1: val
the following code snippet would **FAIL**:
::
- {b: 1, a: 2}
the following code snippet would **FAIL**:
::
- T-shirt: 1
t-shirt: 2
T-shirts: 3
t-shirts: 4
the following code snippet would **FAIL**:
::
- haïr: true
hais: true
"""
import yaml
from yamllint.linter import LintProblem
ID = 'key-ordering'
TYPE = 'token'
CONF = {}
MAP, SEQ = range(2)
class Parent(object):
def __init__(self, type):
self.type = type
self.keys = []
def check(conf, token, prev, next, nextnext, context):
if 'stack' not in context:
context['stack'] = []
if isinstance(token, (yaml.BlockMappingStartToken,
yaml.FlowMappingStartToken)):
context['stack'].append(Parent(MAP))
elif isinstance(token, (yaml.BlockSequenceStartToken,
yaml.FlowSequenceStartToken)):
context['stack'].append(Parent(SEQ))
elif isinstance(token, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)):
context['stack'].pop()
elif (isinstance(token, yaml.KeyToken) and
isinstance(next, yaml.ScalarToken)):
# This check is done because KeyTokens can be found inside flow
# sequences... strange, but allowed.
if len(context['stack']) > 0 and context['stack'][-1].type == MAP:
if any(next.value < key for key in context['stack'][-1].keys):
yield LintProblem(
next.start_mark.line + 1, next.start_mark.column + 1,
'wrong ordering of key "%s" in mapping' % next.value)
else:
context['stack'][-1].keys.append(next.value)