Compare commits

...

34 Commits

Author SHA1 Message Date
Adrien Vergé
415b1c7091 [WIP] Fix for #334 (directories like 'dir.yaml' + recursive search)
Partially reverts a221898 to fix https://github.com/adrienverge/yamllint/issues/334

Linked to https://github.com/cpburnz/python-path-specification/issues/41.
2020-10-10 10:26:31 +02:00
Florian Bruhin
8eebab68ab Fix typo in changelog 2020-10-05 09:42:42 +02:00
Per Lundberg
2103bd73de README.rst: fix typo 2020-10-02 12:51:01 +02:00
Adrien Vergé
85c8631183 tests: Stop using deprecated 'python setup.py test'
Using `python setup.py test` is now deprecated [1], users are encouraged
to be explicit about the test command.

Running yamllint tests using the Python standard library (`unittest`)
can be done using:

    python -m unittest discover

Why not nose, tox or pytest? Because they would add a dependency, make
tests running more complicated and verbose for new users, and their
benefit is not worth for this simple project (only 2 runtime
dependencies: PyYAML and pathspec).

Resolves https://github.com/adrienverge/yamllint/issues/328.

[1]: https://github.com/pypa/setuptools/pull/1878
2020-09-30 09:43:31 +02:00
Adrien Vergé
16e0f9d7b2 yamllint version 1.25.0 2020-09-29 08:52:34 +02:00
Mathieu Couette
1a4f9fe00f gitignore: Add /.eggs
Quick PR to ignore the `/.eggs` folder, which appears to be generated every
time the `python setup.py test` command is run.

The content of the `./.eggs/README.txt` file:

> This directory contains eggs that were downloaded by setuptools to build,
> test, and run plug-ins.
> 
> This directory caches those eggs to prevent repeated downloads.
> 
> However, it is safe to delete this directory.
2020-09-27 10:06:33 +02:00
Mathieu Couette
027d1b0a9a directives: Fix DOS lines messing with rule IDs
Fixes #325

The linter allows a directive to contain trailing whitespace characters like
\r, but does not trim them before iterating on the rules. As a result, the last
rule in the list contains the trailing whitespace characters and never matches
any existing rule.

I added the necessary trimming, as well as a test with 2 checks to go along
with it.
2020-09-26 11:12:26 +02:00
Andrew Imeson
67cb4eb24d Auto-change output format if GitHub Actions detected 2020-09-23 15:41:25 +02:00
Andrew Imeson
50c7453824 Add support for GitHub Annotations output format
Support the format used by GitHub Actions to annotate pull
requests with linter failures
2020-09-23 15:41:25 +02:00
Satoru SATOH
549b136a04 fix: add runtime dependency to setuptools
yamllint depends on pkg_resources.load_entry_point from setuptools to
make its command working, so this runtime dependency to setuptools is
necessary to be listed.
2020-09-14 10:14:10 +02:00
Satoru SATOH
333ae52c78 Add 'forbid' configurations to the braces and brackets rules
Add 'forbid' configuration parameters to the braces and brackets rules
to allow users to forbid the use of flow style collections, flow
mappings and flow sequences.
2020-09-09 20:01:25 +02:00
Julien Falque
0a88c55194 quoted-strings: Fix detecting strings with hashtag as requiring quotes 2020-09-08 11:53:06 +02:00
Julien Falque
ac19d1e427 octal-values: Prevent detection of 8 and 9 as octal values 2020-09-08 09:53:54 +02:00
Adrien Vergé
597e88bb7b docs: Make 'yaml-file' config documentation clearer
Related to https://github.com/adrienverge/yamllint/issues/311.
2020-09-02 18:23:51 +02:00
Satoru SATOH
29d2b50d50 enhancement: add some metadata to provide extra info in its PyPI page
Add some metadata (project_urls) to provide extra info in its PyPI page.

Signed-off-by: Satoru SATOH <satoru.satoh@gmail.com>
2020-09-02 18:02:27 +02:00
Satoru SATOH
4171cdafc9 Move setuptools' packaging configuration from setup.py to setup.cfg
Move setuptools' packaging configuration from setup.py to setup.cfg to
simplify setup.py and make its packaging more dedeclarative.

Signed-off-by: Satoru SATOH <satoru.satoh@gmail.com>
2020-09-02 18:02:27 +02:00
Sorin Sbarnea
d274543b72 docs: Add Python API usage example
Fixes: #297
2020-08-25 20:10:32 +02:00
Kirill Deyko
8da98f2122 commas: Fix example in documentation
Error in the example snippet, it would NOT pass otherwise actually:
```
$ cat test.yml
strange var:
  [10, 20,30, {x: 1, y: 2}]

$ yamllint -d "{extends: default, rules: {commas: {min-spaces-after: 1, max-spaces-after: 1}}}" test.yml
test.yml
  1:1       warning  missing document start "---"  (document-start)
  2:11      error    too few spaces after comma  (commas)
```
2020-08-18 20:07:54 +02:00
Benjamin Wuethrich
b65769c9d2 docs: Add default values to rules with options 2020-07-22 11:54:05 +02:00
Wolfgang Walther
b80997eba6 CI: Add build environment without UTF-8 locales to travis-ci
Preventing regressions like #285
2020-07-20 13:57:06 +02:00
Adrien Vergé
8b758d4e7e yamllint version 1.24.2 2020-07-16 09:35:08 +02:00
Wolfgang Walther
b5b436a3a4 Add global "locale" config option and make key-ordering rule locale-aware
Support sorting by locale with strcoll(). Properly handle case and accents.

Note: this is a second implementation, for context see:
https://github.com/adrienverge/yamllint/pull/280
https://github.com/adrienverge/yamllint/issues/285
https://github.com/adrienverge/yamllint/pull/288
2020-07-16 09:34:13 +02:00
Adrien Vergé
0fceca2354 yamllint version 1.24.1 2020-07-15 14:49:51 +02:00
Adrien Vergé
9403f1f3ec Revert "Add global "locale" config option"
This reverts commit 9e90c77, because it caused a bug that affected
different people just after being released:
https://github.com/adrienverge/yamllint/issues/285
https://github.com/adrienverge/yamllint/issues/286
2020-07-15 14:48:48 +02:00
Adrien Vergé
0016390e78 yamllint version 1.24.0 2020-07-15 11:50:36 +02:00
Wolfgang Walther
9e90c777cb Add global "locale" config option and make key-ordering rule locale-aware
Support sorting by locale with strcoll(). Properly handle case and accents.
2020-07-15 11:46:05 +02:00
Jonathan Sokolowski
a2218988ee config: Do no match directories that look like YAML files
Fixes #279
2020-07-10 09:27:34 +02:00
Adrien Vergé
954fdd5e8f style: Fix 'noqa' for flake8 3.8.0
There was a change in behavior of E402, see:
https://gitlab.com/pycqa/flake8/-/issues/638#note_345108633
2020-07-08 16:27:08 +02:00
Sorin Sbarnea
bbcad943b6 style: Ignore flake8 warnings W503 and W504
Avoid W503/W504 with current code as the current code not compliant
and they are contradictory.
2020-05-03 16:55:57 +02:00
Adrien Vergé
30c90dbf70 Add contribution instructions in CONTRIBUTING.rst
Closes https://github.com/adrienverge/yamllint/issues/263.
2020-05-03 16:51:22 +02:00
Brad Solomon
512fe17047 Fix bug with CRLF in new-lines and require-starting-space
Pound-signs followed by a lone CRLF should not
raise if require-starting-space is specified.

If require-starting-space is true, *and* either:
- new-lines: disbale, or
- newlines: type: dos
is specified, a line with `#\r` or `#\r\n` should
not raise a false positive.

This commit also uses a Set for O(1) membership testing
and uses the correct escape sequence for the nul byte.

If we find a CRLF when looking for Unix newlines, yamllint
should always raise, regardless of logic with
require-starting-space.

Closes: Issue #171.
2020-04-30 16:38:19 +02:00
Will Badart
278a79f093 Mention YAMLLINT_CONFIG_FILE in the documentation 2020-04-29 09:43:16 +02:00
Brad Solomon
e98aacf62c Add Python 3.8 to PyPI/trove classifier data
3.8 is now formally supported in .travis.yml
as of this commit.
2020-04-29 09:39:50 +02:00
Will Badart
94c0416f6b Specify config with environment variable YAMLLINT_CONFIG_FILE
Add option to specify config file with environment variable.
Add test case.
2020-04-28 11:13:32 +02:00
43 changed files with 826 additions and 77 deletions

3
.gitignore vendored
View File

@@ -4,3 +4,6 @@ __pycache__
/dist /dist
/yamllint.egg-info /yamllint.egg-info
/build /build
/.eggs
*.yaml
!*.yaml/

View File

@@ -8,15 +8,19 @@ python:
- 3.7 - 3.7
- 3.8 - 3.8
- nightly - nightly
env:
- REMOVE_LOCALES=false
- REMOVE_LOCALES=true
install: install:
- pip install pyyaml coveralls flake8 flake8-import-order doc8 - pip install pyyaml coveralls flake8 flake8-import-order doc8
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then pip install sphinx; fi - if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then pip install sphinx; fi
- pip install . - pip install .
- if [[ $REMOVE_LOCALES = "true" ]]; then sudo rm -rf /usr/lib/locale/*; fi
script: script:
- if [[ $TRAVIS_PYTHON_VERSION != nightly ]]; then flake8 .; fi - if [[ $TRAVIS_PYTHON_VERSION != nightly ]]; then flake8 .; fi
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then doc8 $(git ls-files '*.rst'); fi - if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then doc8 $(git ls-files '*.rst'); fi
- yamllint --strict $(git ls-files '*.yaml' '*.yml') - yamllint --strict $(git ls-files '*.yaml' '*.yml')
- coverage run --source=yamllint setup.py test - coverage run --source=yamllint -m unittest discover
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then - if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then
python setup.py build_sphinx; python setup.py build_sphinx;
fi fi

View File

@@ -1,6 +1,41 @@
Changelog Changelog
========= =========
1.25.0 (2020-09-29)
-------------------
- Run tests on Travis both with and without UTF-8 locales
- Improve documentation with default values to rules with options
- Improve documentation with a Python API usage example
- Fix documentation on ``commas`` examples
- Packaging: move setuptools' configuration from ``setup.py`` to ``setup.cfg``
- Packaging: add extra info in PyPI metadata
- Improve documentation on ``yaml-files``
- Fix ``octal-values`` to prevent detection of ``8`` and ``9`` as octal values
- Fix ``quoted-strings`` Fix detecting strings with hashtag as requiring quotes
- Add ``forbid`` configuration to the ``braces`` and ``brackets`` rules
- Fix runtime dependencies missing ``setuptools``
- Add a new output format for GitHub Annotations (``--format github``)
- Fix DOS lines messing with rule IDs in directives
1.24.2 (2020-07-16)
-------------------
- Add ``locale`` config option and make ``key-ordering`` locale-aware
1.24.1 (2020-07-15)
-------------------
- Revert ``locale`` config option from version 1.24.0 because of a bug
1.24.0 (2020-07-15)
-------------------
- Specify config with environment variable ``YAMLLINT_CONFIG_FILE``
- Fix bug with CRLF in ``new-lines`` and ``require-starting-space``
- Do not run linter on directories whose names look like YAML files
- Add ``locale`` config option and make ``key-ordering`` locale-aware
1.23.0 (2020-04-17) 1.23.0 (2020-04-17)
------------------- -------------------

46
CONTRIBUTING.rst Normal file
View File

@@ -0,0 +1,46 @@
Contributing
============
Pull requests are the best way to propose changes to the codebase.
Contributions are welcome, but they have to meet some criteria.
Pull Request Process
--------------------
1. Fork this Git repository and create your branch from ``master``.
2. Make sure the tests pass:
.. code:: bash
pip install --user .
python -m unittest discover # all tests...
python -m unittest tests/rules/test_commas.py # or just some tests (faster)
3. If you add code that should be tested, add tests.
4. Make sure the linters pass:
.. code:: bash
flake8 .
If you added/modified documentation:
.. code:: bash
doc8 $(git ls-files '*.rst')
If you touched YAML files:
.. code:: bash
yamllint --strict $(git ls-files '*.yaml' '*.yml')
5. If relevant, update documentation (either in ``docs`` directly or in rules
files themselves).
6. Write a `good commit message
<http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html>`_.
7. Then, open a pull request.

View File

@@ -22,7 +22,7 @@ indentation, etc.
Written in Python (compatible with Python 2 & 3). Written in Python (compatible with Python 2 & 3).
⚠ Python 2 upstream support stopped on January 1, 2020. yamllint will keep ⚠ Python 2 upstream support stopped on January 1, 2020. yamllint will keep
best-effort support for Python 2.7 until January 1, 2021. Passed that date, best-effort support for Python 2.7 until January 1, 2021. Past that date,
yamllint will drop all Python 2-related code. yamllint will drop all Python 2-related code.
Documentation Documentation

View File

@@ -6,9 +6,9 @@ import sys
import os import os
from unittest.mock import MagicMock from unittest.mock import MagicMock
sys.path.insert(0, os.path.abspath('..')) # noqa sys.path.insert(0, os.path.abspath('..'))
from yamllint import __copyright__, APP_NAME, APP_VERSION from yamllint import __copyright__, APP_NAME, APP_VERSION # noqa
# -- General configuration ------------------------------------------------ # -- General configuration ------------------------------------------------

View File

@@ -16,6 +16,7 @@ following locations (by order of preference):
- ``.yamllint``, ``.yamllint.yaml`` or ``.yamllint.yml`` in the current working - ``.yamllint``, ``.yamllint.yaml`` or ``.yamllint.yml`` in the current working
directory directory
- the file referenced by ``$YAMLLINT_CONFIG_FILE``, if set
- ``$XDG_CONFIG_HOME/yamllint/config`` - ``$XDG_CONFIG_HOME/yamllint/config``
- ``~/.config/yamllint/config`` - ``~/.config/yamllint/config``
@@ -122,8 +123,8 @@ warning level problems, only error level ones.
YAML files extensions YAML files extensions
--------------------- ---------------------
To configure what yamllint should consider as YAML files, set ``yaml-files`` To configure what yamllint should consider as YAML files when listing
configuration option. The default is: directories, set ``yaml-files`` configuration option. The default is:
.. code-block:: yaml .. code-block:: yaml
@@ -188,3 +189,22 @@ Here is a more complex example:
ignore: | ignore: |
*.ignore-trailing-spaces.yaml *.ignore-trailing-spaces.yaml
ascii-art/* ascii-art/*
Setting the locale
------------------
It is possible to set the ``locale`` option globally. This is passed to Python's
`locale.setlocale
<https://docs.python.org/3/library/locale.html#locale.setlocale>`_,
so an empty string ``""`` will use the system default locale, while e.g.
``"en_US.UTF-8"`` will use that.
Currently this only affects the ``key-ordering`` rule. The default will order
by Unicode code point number, while locales will sort case and accents
properly as well.
.. code-block:: yaml
extends: default
locale: en_US.UTF-8

View File

@@ -2,7 +2,17 @@ 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.
Basic example of running the linter from Python:
.. code-block:: python
import yamllint
yaml_config = yamllint.config.YamlLintConfig("extends: default")
for p in yamllint.linter.run("example.yaml", yaml_config):
print(p.desc, p.line, p.rule)
.. automodule:: yamllint.linter .. automodule:: yamllint.linter
:members: :members:

View File

@@ -17,3 +17,37 @@ Here is an example, to add in your .pre-commit-config.yaml
hooks: hooks:
- id: yamllint - id: yamllint
args: [-c=/path/to/.yamllint] args: [-c=/path/to/.yamllint]
Integration with GitHub Actions
-------------------------------
yamllint auto-detects when it's running inside of `GitHub
Actions<https://github.com/features/actions>` and automatically uses the suited
output format to decorate code with linting errors automatically. You can also
force the GitHub Actions output with ``yamllint --format github``.
An example workflow using GitHub Actions:
.. code:: yaml
---
name: yamllint test
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install yamllint
run: pip install yamllint
- name: Lint YAML files
run: yamllint .

View File

@@ -4,9 +4,68 @@ universal = 1
[flake8] [flake8]
import-order-style = pep8 import-order-style = pep8
application-import-names = yamllint application-import-names = yamllint
ignore = W503,W504
[build_sphinx] [build_sphinx]
all-files = 1 all-files = 1
source-dir = docs source-dir = docs
build-dir = docs/_build build-dir = docs/_build
warning-is-error = 1 warning-is-error = 1
[metadata]
keywords =
yaml
lint
linter
syntax
checker
url = https://github.com/adrienverge/yamllint
classifiers =
Development Status :: 5 - Production/Stable
Environment :: Console
Intended Audience :: Developers
License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Topic :: Software Development
Topic :: Software Development :: Debuggers
Topic :: Software Development :: Quality Assurance
Topic :: Software Development :: Testing
project_urls =
Documentation = https://yamllint.readthedocs.io
Download = https://pypi.org/project/yamllint/#files
Bug Tracker = https://github.com/adrienverge/yamllint/issues
Source Code = https://github.com/adrienverge/yamllint
[options]
packages = find:
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
include_package_data = True
install_requires =
pathspec >= 0.5.3
pyyaml
setuptools
test_suite = tests
[options.packages.find]
exclude =
tests
tests.*
[options.package_data]
yamllint = conf/*.yaml
[options.entry_points]
console_scripts =
yamllint = yamllint.cli:run

View File

@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from setuptools import find_packages, setup from setuptools import setup
from yamllint import (__author__, __license__, from yamllint import (__author__, __license__,
APP_NAME, APP_VERSION, APP_DESCRIPTION) APP_NAME, APP_VERSION, APP_DESCRIPTION)
@@ -27,30 +27,4 @@ setup(
description=APP_DESCRIPTION.split('\n')[0], description=APP_DESCRIPTION.split('\n')[0],
long_description=APP_DESCRIPTION, long_description=APP_DESCRIPTION,
license=__license__, license=__license__,
keywords=['yaml', 'lint', 'linter', 'syntax', 'checker'],
url='https://github.com/adrienverge/yamllint',
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Software Development',
'Topic :: Software Development :: Debuggers',
'Topic :: Software Development :: Quality Assurance',
'Topic :: Software Development :: Testing',
],
packages=find_packages(exclude=['tests', 'tests.*']),
entry_points={'console_scripts': ['yamllint=yamllint.cli:run']},
package_data={'yamllint': ['conf/*.yaml']},
install_requires=['pathspec >=0.5.3', 'pyyaml'],
test_suite='tests',
) )

View File

@@ -57,7 +57,7 @@ def build_temp_workspace(files):
tempdir = tempfile.mkdtemp(prefix='yamllint-tests-') tempdir = tempfile.mkdtemp(prefix='yamllint-tests-')
for path, content in files.items(): for path, content in files.items():
path = os.path.join(tempdir, path) path = os.path.join(tempdir, path).encode('utf-8')
if not os.path.exists(os.path.dirname(path)): if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path)) os.makedirs(os.path.dirname(path))

View File

@@ -31,6 +31,36 @@ class ColonTestCase(RuleTestCase):
'dict6: { a: 1, b, c: 3 }\n' 'dict6: { a: 1, b, c: 3 }\n'
'dict7: { a: 1, b, c: 3 }\n', conf) 'dict7: { a: 1, b, c: 3 }\n', conf)
def test_forbid(self):
conf = ('braces:\n'
' forbid: false\n')
self.check('---\n'
'dict: {}\n', conf)
self.check('---\n'
'dict: {a}\n', conf)
self.check('---\n'
'dict: {a: 1}\n', conf)
self.check('---\n'
'dict: {\n'
' a: 1\n'
'}\n', conf)
conf = ('braces:\n'
' forbid: true\n')
self.check('---\n'
'dict:\n'
' a: 1\n', conf)
self.check('---\n'
'dict: {}\n', conf, problem=(2, 8))
self.check('---\n'
'dict: {a}\n', conf, problem=(2, 8))
self.check('---\n'
'dict: {a: 1}\n', conf, problem=(2, 8))
self.check('---\n'
'dict: {\n'
' a: 1\n'
'}\n', conf, problem=(2, 8))
def test_min_spaces(self): def test_min_spaces(self):
conf = ('braces:\n' conf = ('braces:\n'
' max-spaces-inside: -1\n' ' max-spaces-inside: -1\n'

View File

@@ -31,6 +31,35 @@ class ColonTestCase(RuleTestCase):
'array6: [ a, b, c ]\n' 'array6: [ a, b, c ]\n'
'array7: [ a, b, c ]\n', conf) 'array7: [ a, b, c ]\n', conf)
def test_forbid(self):
conf = ('brackets:\n'
' forbid: false\n')
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [a, b]\n', conf)
self.check('---\n'
'array: [\n'
' a,\n'
' b\n'
']\n', conf)
conf = ('brackets:\n'
' forbid: true\n')
self.check('---\n'
'array:\n'
' - a\n'
' - b\n', conf)
self.check('---\n'
'array: []\n', conf, problem=(2, 9))
self.check('---\n'
'array: [a, b]\n', conf, problem=(2, 9))
self.check('---\n'
'array: [\n'
' a,\n'
' b\n'
']\n', conf, problem=(2, 9))
def test_min_spaces(self): def test_min_spaces(self):
conf = ('brackets:\n' conf = ('brackets:\n'
' max-spaces-inside: -1\n' ' max-spaces-inside: -1\n'

View File

@@ -186,6 +186,27 @@ class CommentsTestCase(RuleTestCase):
'inline: comment #\n' 'inline: comment #\n'
'foo: bar\n', conf) 'foo: bar\n', conf)
def test_empty_comment_crlf_dos_newlines(self):
conf = ('comments:\n'
' require-starting-space: true\n'
' min-spaces-from-content: 2\n'
'new-lines:\n'
' type: dos\n')
self.check('---\r\n'
'# This is paragraph 1.\r\n'
'#\r\n'
'# This is paragraph 2.\r\n', conf)
def test_empty_comment_crlf_disabled_newlines(self):
conf = ('comments:\n'
' require-starting-space: true\n'
' min-spaces-from-content: 2\n'
'new-lines: disable\n')
self.check('---\r\n'
'# This is paragraph 1.\r\n'
'#\r\n'
'# This is paragraph 2.\r\n', conf)
def test_first_line(self): def test_first_line(self):
conf = ('comments:\n' conf = ('comments:\n'
' require-starting-space: true\n' ' require-starting-space: true\n'

View File

@@ -14,6 +14,8 @@
# 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 locale
from tests.common import RuleTestCase from tests.common import RuleTestCase
@@ -103,10 +105,6 @@ class KeyOrderingTestCase(RuleTestCase):
'haïr: true\n' 'haïr: true\n'
'hais: true\n', conf, 'hais: true\n', conf,
problem=(3, 1)) 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): def test_key_tokens_in_flow_sequences(self):
conf = 'key-ordering: enable' conf = 'key-ordering: enable'
@@ -114,3 +112,39 @@ class KeyOrderingTestCase(RuleTestCase):
'[\n' '[\n'
' key: value, mappings, in, flow: sequence\n' ' key: value, mappings, in, flow: sequence\n'
']\n', conf) ']\n', conf)
def test_locale_case(self):
self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None))
try:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
except locale.Error:
self.skipTest('locale en_US.UTF-8 not available')
conf = ('key-ordering: enable')
self.check('---\n'
't-shirt: 1\n'
'T-shirt: 2\n'
't-shirts: 3\n'
'T-shirts: 4\n', conf)
self.check('---\n'
't-shirt: 1\n'
't-shirts: 2\n'
'T-shirt: 3\n'
'T-shirts: 4\n', conf,
problem=(4, 1))
def test_locale_accents(self):
self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None))
try:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
except locale.Error:
self.skipTest('locale en_US.UTF-8 not available')
conf = ('key-ordering: enable')
self.check('---\n'
'hair: true\n'
'haïr: true\n'
'hais: true\n'
'haïssable: true\n', conf)
self.check('---\n'
'hais: true\n'
'haïr: true\n', conf,
problem=(3, 1))

View File

@@ -40,6 +40,16 @@ class NewLinesTestCase(RuleTestCase):
self.check('---\ntext\n', conf) self.check('---\ntext\n', conf)
self.check('---\r\ntext\r\n', conf, problem=(1, 4)) self.check('---\r\ntext\r\n', conf, problem=(1, 4))
def test_unix_type_required_st_sp(self):
# If we find a CRLF when looking for Unix newlines, yamllint
# should always raise, regardless of logic with
# require-starting-space.
conf = ('new-line-at-end-of-file: disable\n'
'new-lines: {type: unix}\n'
'comments:\n'
' require-starting-space: true\n')
self.check('---\r\n#\r\n', conf, problem=(1, 4))
def test_dos_type(self): def test_dos_type(self):
conf = ('new-line-at-end-of-file: disable\n' conf = ('new-line-at-end-of-file: disable\n'
'new-lines: {type: dos}\n') 'new-lines: {type: dos}\n')

View File

@@ -50,6 +50,8 @@ class OctalValuesTestCase(RuleTestCase):
' - 0.10\n' ' - 0.10\n'
' - .01\n' ' - .01\n'
' - 0e3\n', conf) ' - 0e3\n', conf)
self.check('with-decimal-digits: 012345678', conf)
self.check('with-decimal-digits: 012345679', conf)
def test_explicit_octal_values(self): def test_explicit_octal_values(self):
conf = ('octal-values:\n' conf = ('octal-values:\n'
@@ -74,3 +76,5 @@ class OctalValuesTestCase(RuleTestCase):
' - .01\n' ' - .01\n'
' - 0e3\n', conf) ' - 0e3\n', conf)
self.check('user-city: "010"', conf) self.check('user-city: "010"', conf)
self.check('with-decimal-digits: 0o012345678', conf)
self.check('with-decimal-digits: 0o012345679', conf)

View File

@@ -330,7 +330,8 @@ class QuotedTestCase(RuleTestCase):
'- "%wheel ALL=(ALL) NOPASSWD: ALL"\n' '- "%wheel ALL=(ALL) NOPASSWD: ALL"\n'
'- \'"quoted"\'\n' '- \'"quoted"\'\n'
'- "\'foo\' == \'bar\'"\n' '- "\'foo\' == \'bar\'"\n'
'- "\'Mac\' in ansible_facts.product_name"\n', '- "\'Mac\' in ansible_facts.product_name"\n'
'- \'foo # bar\'\n',
conf) conf)
self.check('---\n' self.check('---\n'
'k1: ""\n' 'k1: ""\n'

View File

@@ -24,6 +24,7 @@ import os
import pty import pty
import shutil import shutil
import sys import sys
import tempfile
import unittest import unittest
from tests.common import build_temp_workspace from tests.common import build_temp_workspace
@@ -55,6 +56,16 @@ class RunContext(object):
return self._raises_ctx.exception.code return self._raises_ctx.exception.code
# Check system's UTF-8 availability
def utf8_available():
try:
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
locale.setlocale(locale.LC_ALL, (None, None))
return True
except locale.Error:
return False
class CommandLineTestCase(unittest.TestCase): class CommandLineTestCase(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@@ -72,6 +83,9 @@ class CommandLineTestCase(unittest.TestCase):
# file in dir # file in dir
'sub/ok.yaml': '---\n' 'sub/ok.yaml': '---\n'
'key: value\n', 'key: value\n',
# directory that looks like a yaml file
'sub/directory.yaml/not-yaml.txt': '',
'sub/directory.yaml/empty.yml': '',
# file in very nested dir # file in very nested dir
's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml': '---\n' 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml': '---\n'
'key: value\n' 'key: value\n'
@@ -82,7 +96,7 @@ class CommandLineTestCase(unittest.TestCase):
'no-yaml.json': '---\n' 'no-yaml.json': '---\n'
'key: value\n', 'key: value\n',
# non-ASCII chars # non-ASCII chars
'non-ascii/éçäγλνπ¥/utf-8': ( u'non-ascii/éçäγλνπ¥/utf-8': (
u'---\n' u'---\n'
u'- hétérogénéité\n' u'- hétérogénéité\n'
u'# 19.99 €\n' u'# 19.99 €\n'
@@ -91,6 +105,13 @@ class CommandLineTestCase(unittest.TestCase):
# dos line endings yaml # dos line endings yaml
'dos.yml': '---\r\n' 'dos.yml': '---\r\n'
'dos: true', 'dos: true',
# different key-ordering by locale
'c.yaml': '---\n'
'A: true\n'
'a: true',
'en.yaml': '---\n'
'a: true\n'
'A: true'
}) })
@classmethod @classmethod
@@ -99,14 +120,19 @@ class CommandLineTestCase(unittest.TestCase):
shutil.rmtree(cls.wd) shutil.rmtree(cls.wd)
@unittest.skipIf(not utf8_available() and sys.version_info < (3, 7),
'UTF-8 paths not supported')
def test_find_files_recursively(self): def test_find_files_recursively(self):
conf = config.YamlLintConfig('extends: default') conf = config.YamlLintConfig('extends: default')
self.assertEqual( self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)), sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'a.yaml'), [os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'dos.yml'), os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'), os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'), os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/ok.yaml'), os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')], os.path.join(self.wd, 'warn.yaml')],
) )
@@ -131,6 +157,7 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual( self.assertEqual(
sorted(cli.find_files_recursively(items, conf)), sorted(cli.find_files_recursively(items, conf)),
[os.path.join(self.wd, '/etc/another/file'), [os.path.join(self.wd, '/etc/another/file'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/ok.yaml')], os.path.join(self.wd, 'sub/ok.yaml')],
) )
@@ -140,6 +167,8 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual( self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)), sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'a.yaml'), [os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'), os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/ok.yaml'), os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')] os.path.join(self.wd, 'warn.yaml')]
@@ -151,7 +180,8 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual( self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)), sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'dos.yml'), [os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml')] os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml')]
) )
conf = config.YamlLintConfig('extends: default\n' conf = config.YamlLintConfig('extends: default\n'
@@ -168,11 +198,15 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual( self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)), sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'a.yaml'), [os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'dos.yml'), os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'), os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 'no-yaml.json'), os.path.join(self.wd, 'no-yaml.json'),
os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8'), os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'), os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/directory.yaml/not-yaml.txt'),
os.path.join(self.wd, 'sub/ok.yaml'), os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')] os.path.join(self.wd, 'warn.yaml')]
) )
@@ -185,11 +219,15 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual( self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)), sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'a.yaml'), [os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'dos.yml'), os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'), os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 'no-yaml.json'), os.path.join(self.wd, 'no-yaml.json'),
os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8'), os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'), os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/directory.yaml/not-yaml.txt'),
os.path.join(self.wd, 'sub/ok.yaml'), os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')] os.path.join(self.wd, 'warn.yaml')]
) )
@@ -285,6 +323,65 @@ class CommandLineTestCase(unittest.TestCase):
cli.run((os.path.join(self.wd, 'a.yaml'), )) cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.returncode, 1) self.assertEqual(ctx.returncode, 1)
def test_run_with_user_yamllint_config_file_in_env(self):
self.addCleanup(os.environ.__delitem__, 'YAMLLINT_CONFIG_FILE')
with tempfile.NamedTemporaryFile('w') as f:
os.environ['YAMLLINT_CONFIG_FILE'] = f.name
f.write('rules: {trailing-spaces: disable}')
f.flush()
with RunContext(self) as ctx:
cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.returncode, 0)
with tempfile.NamedTemporaryFile('w') as f:
os.environ['YAMLLINT_CONFIG_FILE'] = f.name
f.write('rules: {trailing-spaces: enable}')
f.flush()
with RunContext(self) as ctx:
cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.returncode, 1)
def test_run_with_locale(self):
# check for availability of locale, otherwise skip the test
# reset to default before running the test,
# as the first two runs don't use setlocale()
try:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
except locale.Error:
self.skipTest('locale en_US.UTF-8 not available')
locale.setlocale(locale.LC_ALL, (None, None))
# C + en.yaml should fail
with RunContext(self) as ctx:
cli.run(('-d', 'rules: { key-ordering: enable }',
os.path.join(self.wd, 'en.yaml')))
self.assertEqual(ctx.returncode, 1)
# C + c.yaml should pass
with RunContext(self) as ctx:
cli.run(('-d', 'rules: { key-ordering: enable }',
os.path.join(self.wd, 'c.yaml')))
self.assertEqual(ctx.returncode, 0)
# the next two runs use setlocale() inside,
# so we need to clean up afterwards
self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None))
# en_US + en.yaml should pass
with RunContext(self) as ctx:
cli.run(('-d', 'locale: en_US.UTF-8\n'
'rules: { key-ordering: enable }',
os.path.join(self.wd, 'en.yaml')))
self.assertEqual(ctx.returncode, 0)
# en_US + c.yaml should fail
with RunContext(self) as ctx:
cli.run(('-d', 'locale: en_US.UTF-8\n'
'rules: { key-ordering: enable }',
os.path.join(self.wd, 'c.yaml')))
self.assertEqual(ctx.returncode, 1)
def test_run_version(self): def test_run_version(self):
with RunContext(self) as ctx: with RunContext(self) as ctx:
cli.run(('--version', )) cli.run(('--version', ))
@@ -340,18 +437,12 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(('-f', 'parsable', path)) cli.run(('-f', 'parsable', path))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', '')) self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
@unittest.skipIf(not utf8_available(), 'C.UTF-8 not available')
def test_run_non_ascii_file(self): def test_run_non_ascii_file(self):
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None))
path = os.path.join(self.wd, 'non-ascii', 'éçäγλνπ¥', 'utf-8') path = os.path.join(self.wd, 'non-ascii', 'éçäγλνπ¥', 'utf-8')
# Make sure the default localization conditions on this "system"
# support UTF-8 encoding.
loc = locale.getlocale()
try:
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
except locale.Error:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
self.addCleanup(locale.setlocale, locale.LC_ALL, loc)
with RunContext(self) as ctx: with RunContext(self) as ctx:
cli.run(('-f', 'parsable', path)) cli.run(('-f', 'parsable', path))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', '')) self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
@@ -458,6 +549,38 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual( self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, '')) (ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_format_github(self):
path = os.path.join(self.wd, 'a.yaml')
with RunContext(self) as ctx:
cli.run((path, '--format', 'github'))
expected_out = (
'::error file=%s,line=2,col=4::[trailing-spaces] trailing'
' spaces\n'
'::error file=%s,line=3,col=4::[new-line-at-end-of-file] no'
' new line character at the end of file\n'
% (path, path))
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_github_actions_detection(self):
path = os.path.join(self.wd, 'a.yaml')
self.addCleanup(os.environ.__delitem__, 'GITHUB_ACTIONS')
self.addCleanup(os.environ.__delitem__, 'GITHUB_WORKFLOW')
with RunContext(self) as ctx:
os.environ['GITHUB_ACTIONS'] = 'something'
os.environ['GITHUB_WORKFLOW'] = 'something'
cli.run((path, ))
expected_out = (
'::error file=%s,line=2,col=4::[trailing-spaces] trailing'
' spaces\n'
'::error file=%s,line=3,col=4::[new-line-at-end-of-file] no'
' new line character at the end of file\n'
% (path, path))
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_read_from_stdin(self): def test_run_read_from_stdin(self):
# prepares stdin with an invalid yaml string so that we can check # prepares stdin with an invalid yaml string so that we can check
# for its specific error, and be assured that stdin was read # for its specific error, and be assured that stdin was read

View File

@@ -232,6 +232,34 @@ class YamllintDirectivesTestCase(RuleTestCase):
problem1=(3, 18, 'trailing-spaces'), problem1=(3, 18, 'trailing-spaces'),
problem2=(4, 8, 'colons')) problem2=(4, 8, 'colons'))
def test_disable_directive_with_rules_and_dos_lines(self):
conf = self.conf + 'new-lines: {type: dos}\n'
self.check('---\r\n'
'- [valid , YAML]\r\n'
'# yamllint disable rule:trailing-spaces\r\n'
'- trailing spaces \r\n'
'- bad : colon\r\n'
'- [valid , YAML]\r\n'
'# yamllint enable rule:trailing-spaces\r\n'
'- bad : colon and spaces \r\n'
'- [valid , YAML]\r\n',
conf,
problem1=(5, 8, 'colons'),
problem2=(8, 7, 'colons'),
problem3=(8, 26, 'trailing-spaces'))
self.check('---\r\n'
'- [valid , YAML]\r\n'
'- trailing spaces \r\n'
'- bad : colon\r\n'
'- [valid , YAML]\r\n'
'# yamllint disable-line rule:colons\r\n'
'- bad : colon and spaces \r\n'
'- [valid , YAML]\r\n',
conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(4, 8, 'colons'),
problem3=(7, 26, 'trailing-spaces'))
def test_directive_on_last_line(self): def test_directive_on_last_line(self):
conf = 'new-line-at-end-of-file: {}' conf = 'new-line-at-end-of-file: {}'
self.check('---\n' self.check('---\n'

View File

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

View File

@@ -18,6 +18,7 @@ from __future__ import print_function
import argparse import argparse
import io import io
import locale
import os import os
import platform import platform
import sys import sys
@@ -84,6 +85,19 @@ class Format(object):
line += ' \033[2m(%s)\033[0m' % problem.rule line += ' \033[2m(%s)\033[0m' % problem.rule
return line return line
@staticmethod
def github(problem, filename):
line = '::'
line += problem.level
line += ' file=' + filename + ','
line += 'line=' + format(problem.line) + ','
line += 'col=' + format(problem.column)
line += '::'
if problem.rule:
line += '[' + problem.rule + '] '
line += problem.desc
return line
def show_problems(problems, file, args_format, no_warn): def show_problems(problems, file, args_format, no_warn):
max_level = 0 max_level = 0
@@ -95,6 +109,10 @@ def show_problems(problems, file, args_format, no_warn):
continue continue
if args_format == 'parsable': if args_format == 'parsable':
print(Format.parsable(problem, file)) print(Format.parsable(problem, file))
elif args_format == 'github' or (args_format == 'auto' and
'GITHUB_ACTIONS' in os.environ and
'GITHUB_WORKFLOW' in os.environ):
print(Format.github(problem, file))
elif args_format == 'colored' or \ elif args_format == 'colored' or \
(args_format == 'auto' and supports_color()): (args_format == 'auto' and supports_color()):
if first: if first:
@@ -130,7 +148,8 @@ def run(argv=None):
action='store', action='store',
help='custom configuration (as YAML source)') help='custom configuration (as YAML source)')
parser.add_argument('-f', '--format', parser.add_argument('-f', '--format',
choices=('parsable', 'standard', 'colored', 'auto'), choices=('parsable', 'standard', 'colored', 'github',
'auto'),
default='auto', help='format for parsing output') default='auto', help='format for parsing output')
parser.add_argument('-s', '--strict', parser.add_argument('-s', '--strict',
action='store_true', action='store_true',
@@ -144,8 +163,11 @@ def run(argv=None):
args = parser.parse_args(argv) args = parser.parse_args(argv)
if 'YAMLLINT_CONFIG_FILE' in os.environ:
user_global_config = os.path.expanduser(
os.environ['YAMLLINT_CONFIG_FILE'])
# User-global config is supposed to be in ~/.config/yamllint/config # User-global config is supposed to be in ~/.config/yamllint/config
if 'XDG_CONFIG_HOME' in os.environ: elif 'XDG_CONFIG_HOME' in os.environ:
user_global_config = os.path.join( user_global_config = os.path.join(
os.environ['XDG_CONFIG_HOME'], 'yamllint', 'config') os.environ['XDG_CONFIG_HOME'], 'yamllint', 'config')
else: else:
@@ -172,6 +194,9 @@ def run(argv=None):
print(e, file=sys.stderr) print(e, file=sys.stderr)
sys.exit(-1) sys.exit(-1)
if conf.locale is not None:
locale.setlocale(locale.LC_ALL, conf.locale)
max_level = 0 max_level = 0
for file in find_files_recursively(args.files, conf): for file in find_files_recursively(args.files, conf):

View File

@@ -2,8 +2,11 @@
yaml-files: yaml-files:
- '*.yaml' - '*.yaml'
- '!*.yaml/'
- '*.yml' - '*.yml'
- '!*.yml/'
- '.yamllint' - '.yamllint'
- '!.yamllint/'
rules: rules:
braces: enable braces: enable

View File

@@ -32,8 +32,16 @@ class YamlLintConfig(object):
self.ignore = None self.ignore = None
self.yaml_files = pathspec.PathSpec.from_lines( self.yaml_files = pathspec.PathSpec.from_lines('gitwildmatch', [
'gitwildmatch', ['*.yaml', '*.yml', '.yamllint']) '*.yaml',
'!*.yaml/',
'*.yml',
'!*.yml/',
'.yamllint',
'!.yamllint/',
])
self.locale = None
if file is not None: if file is not None:
with open(file) as f: with open(file) as f:
@@ -111,6 +119,12 @@ class YamlLintConfig(object):
self.yaml_files = pathspec.PathSpec.from_lines('gitwildmatch', self.yaml_files = pathspec.PathSpec.from_lines('gitwildmatch',
conf['yaml-files']) conf['yaml-files'])
if 'locale' in conf:
if not isinstance(conf['locale'], str):
raise YamlLintConfigError(
'invalid config: locale should be a string')
self.locale = conf['locale']
def validate(self): def validate(self):
for id in self.rules: for id in self.rules:
try: try:

View File

@@ -87,7 +87,8 @@ def get_cosmetic_problems(buffer, conf, filepath):
return # this certainly wasn't a yamllint directive comment return # this certainly wasn't a yamllint directive comment
if re.match(r'^# yamllint disable( rule:\S+)*\s*$', comment): if re.match(r'^# yamllint disable( rule:\S+)*\s*$', comment):
rules = [item[5:] for item in comment[18:].split(' ')][1:] items = comment[18:].rstrip().split(' ')
rules = [item[5:] for item in items][1:]
if len(rules) == 0: if len(rules) == 0:
self.rules = self.all_rules.copy() self.rules = self.all_rules.copy()
else: else:
@@ -96,7 +97,8 @@ def get_cosmetic_problems(buffer, conf, filepath):
self.rules.add(id) self.rules.add(id)
elif re.match(r'^# yamllint enable( rule:\S+)*\s*$', comment): elif re.match(r'^# yamllint enable( rule:\S+)*\s*$', comment):
rules = [item[5:] for item in comment[17:].split(' ')][1:] items = comment[17:].rstrip().split(' ')
rules = [item[5:] for item in items][1:]
if len(rules) == 0: if len(rules) == 0:
self.rules.clear() self.rules.clear()
else: else:
@@ -114,7 +116,8 @@ def get_cosmetic_problems(buffer, conf, filepath):
return # this certainly wasn't a yamllint directive comment return # this certainly wasn't a yamllint directive comment
if re.match(r'^# yamllint disable-line( rule:\S+)*\s*$', comment): if re.match(r'^# yamllint disable-line( rule:\S+)*\s*$', comment):
rules = [item[5:] for item in comment[23:].split(' ')][1:] items = comment[23:].rstrip().split(' ')
rules = [item[5:] for item in items][1:]
if len(rules) == 0: if len(rules) == 0:
self.rules = self.all_rules.copy() self.rules = self.all_rules.copy()
else: else:

View File

@@ -15,10 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
Use this rule to control the number of spaces inside braces (``{`` and ``}``). Use this rule to control the use of flow mappings or number of spaces inside
braces (``{`` and ``}``).
.. rubric:: Options .. rubric:: Options
* ``forbid`` is used to forbid the use of flow mappings which are denoted by
surrounding braces (``{`` and ``}``). Use ``true`` to forbid the use of flow
mappings completely.
* ``min-spaces-inside`` defines the minimal number of spaces required inside * ``min-spaces-inside`` defines the minimal number of spaces required inside
braces. braces.
* ``max-spaces-inside`` defines the maximal number of spaces allowed inside * ``max-spaces-inside`` defines the maximal number of spaces allowed inside
@@ -28,8 +32,34 @@ Use this rule to control the number of spaces inside braces (``{`` and ``}``).
* ``max-spaces-inside-empty`` defines the maximal number of spaces allowed * ``max-spaces-inside-empty`` defines the maximal number of spaces allowed
inside empty braces. inside empty braces.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
braces:
forbid: false
min-spaces-inside: 0
max-spaces-inside: 0
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
.. rubric:: Examples .. rubric:: Examples
#. With ``braces: {forbid: true}``
the following code snippet would **PASS**:
::
object:
key1: 4
key2: 8
the following code snippet would **FAIL**:
::
object: { key1: 4, key2: 8 }
#. With ``braces: {min-spaces-inside: 0, max-spaces-inside: 0}`` #. With ``braces: {min-spaces-inside: 0, max-spaces-inside: 0}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
@@ -92,23 +122,31 @@ Use this rule to control the number of spaces inside braces (``{`` and ``}``).
import yaml import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import spaces_after, spaces_before from yamllint.rules.common import spaces_after, spaces_before
ID = 'braces' ID = 'braces'
TYPE = 'token' TYPE = 'token'
CONF = {'min-spaces-inside': int, CONF = {'forbid': bool,
'min-spaces-inside': int,
'max-spaces-inside': int, 'max-spaces-inside': int,
'min-spaces-inside-empty': int, 'min-spaces-inside-empty': int,
'max-spaces-inside-empty': int} 'max-spaces-inside-empty': int}
DEFAULT = {'min-spaces-inside': 0, DEFAULT = {'forbid': False,
'min-spaces-inside': 0,
'max-spaces-inside': 0, 'max-spaces-inside': 0,
'min-spaces-inside-empty': -1, 'min-spaces-inside-empty': -1,
'max-spaces-inside-empty': -1} 'max-spaces-inside-empty': -1}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):
if (isinstance(token, yaml.FlowMappingStartToken) and if conf['forbid'] and isinstance(token, yaml.FlowMappingStartToken):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'forbidden flow mapping')
elif (isinstance(token, yaml.FlowMappingStartToken) and
isinstance(next, yaml.FlowMappingEndToken)): isinstance(next, yaml.FlowMappingEndToken)):
problem = spaces_after(token, prev, next, problem = spaces_after(token, prev, next,
min=(conf['min-spaces-inside-empty'] min=(conf['min-spaces-inside-empty']

View File

@@ -15,11 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
Use this rule to control the number of spaces inside brackets (``[`` and Use this rule to control the use of flow sequences or the number of spaces
``]``). inside brackets (``[`` and ``]``).
.. rubric:: Options .. rubric:: Options
* ``forbid`` is used to forbid the use of flow sequences which are denoted by
surrounding brackets (``[`` and ``]``). Use ``true`` to forbid the use of
flow sequences completely.
* ``min-spaces-inside`` defines the minimal number of spaces required inside * ``min-spaces-inside`` defines the minimal number of spaces required inside
brackets. brackets.
* ``max-spaces-inside`` defines the maximal number of spaces allowed inside * ``max-spaces-inside`` defines the maximal number of spaces allowed inside
@@ -29,8 +32,35 @@ Use this rule to control the number of spaces inside brackets (``[`` and
* ``max-spaces-inside-empty`` defines the maximal number of spaces allowed * ``max-spaces-inside-empty`` defines the maximal number of spaces allowed
inside empty brackets. inside empty brackets.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
brackets:
forbid: false
min-spaces-inside: 0
max-spaces-inside: 0
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
.. rubric:: Examples .. rubric:: Examples
#. With ``brackets: {forbid: true}``
the following code snippet would **PASS**:
::
object:
- 1
- 2
- abc
the following code snippet would **FAIL**:
::
object: [ 1, 2, abc ]
#. With ``brackets: {min-spaces-inside: 0, max-spaces-inside: 0}`` #. With ``brackets: {min-spaces-inside: 0, max-spaces-inside: 0}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
@@ -93,23 +123,31 @@ Use this rule to control the number of spaces inside brackets (``[`` and
import yaml import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import spaces_after, spaces_before from yamllint.rules.common import spaces_after, spaces_before
ID = 'brackets' ID = 'brackets'
TYPE = 'token' TYPE = 'token'
CONF = {'min-spaces-inside': int, CONF = {'forbid': bool,
'min-spaces-inside': int,
'max-spaces-inside': int, 'max-spaces-inside': int,
'min-spaces-inside-empty': int, 'min-spaces-inside-empty': int,
'max-spaces-inside-empty': int} 'max-spaces-inside-empty': int}
DEFAULT = {'min-spaces-inside': 0, DEFAULT = {'forbid': False,
'min-spaces-inside': 0,
'max-spaces-inside': 0, 'max-spaces-inside': 0,
'min-spaces-inside-empty': -1, 'min-spaces-inside-empty': -1,
'max-spaces-inside-empty': -1} 'max-spaces-inside-empty': -1}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):
if (isinstance(token, yaml.FlowSequenceStartToken) and if conf['forbid'] and isinstance(token, yaml.FlowSequenceStartToken):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'forbidden flow sequence')
elif (isinstance(token, yaml.FlowSequenceStartToken) and
isinstance(next, yaml.FlowSequenceEndToken)): isinstance(next, yaml.FlowSequenceEndToken)):
problem = spaces_after(token, prev, next, problem = spaces_after(token, prev, next,
min=(conf['min-spaces-inside-empty'] min=(conf['min-spaces-inside-empty']

View File

@@ -24,6 +24,15 @@ Use this rule to control the number of spaces before and after colons (``:``).
* ``max-spaces-after`` defines the maximal number of spaces allowed after * ``max-spaces-after`` defines the maximal number of spaces allowed after
colons (use ``-1`` to disable). colons (use ``-1`` to disable).
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
colons:
max-spaces-before: 0
max-spaces-after: 1
.. rubric:: Examples .. rubric:: Examples
#. With ``colons: {max-spaces-before: 0, max-spaces-after: 1}`` #. With ``colons: {max-spaces-before: 0, max-spaces-after: 1}``

View File

@@ -26,6 +26,16 @@ Use this rule to control the number of spaces before and after commas (``,``).
* ``max-spaces-after`` defines the maximal number of spaces allowed after * ``max-spaces-after`` defines the maximal number of spaces allowed after
commas (use ``-1`` to disable). commas (use ``-1`` to disable).
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
commas:
max-spaces-before: 0
min-spaces-after: 1
max-spaces-after: 1
.. rubric:: Examples .. rubric:: Examples
#. With ``commas: {max-spaces-before: 0}`` #. With ``commas: {max-spaces-before: 0}``
@@ -66,7 +76,7 @@ Use this rule to control the number of spaces before and after commas (``,``).
:: ::
strange var: strange var:
[10, 20,30, {x: 1, y: 2}] [10, 20, 30, {x: 1, y: 2}]
the following code snippet would **FAIL**: the following code snippet would **FAIL**:
:: ::

View File

@@ -28,6 +28,16 @@ Use this rule to control the position and formatting of comments.
content. It defines the minimal required number of spaces between a comment content. It defines the minimal required number of spaces between a comment
and its preceding content. and its preceding content.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
comments:
require-starting-space: true
ignore-shebangs: true
min-spaces-from-content: 2
.. rubric:: Examples .. rubric:: Examples
#. With ``comments: {require-starting-space: true}`` #. With ``comments: {require-starting-space: true}``
@@ -97,7 +107,9 @@ def check(conf, comment):
comment.column_no == 1 and comment.column_no == 1 and
re.match(r'^!\S', comment.buffer[text_start:])): re.match(r'^!\S', comment.buffer[text_start:])):
return return
elif comment.buffer[text_start] not in (' ', '\n', '\0'): # We can test for both \r and \r\n just by checking first char
# \r itself is a valid newline on some older OS.
elif comment.buffer[text_start] not in {' ', '\n', '\r', '\x00'}:
column = comment.column_no + text_start - comment.pointer column = comment.column_no + text_start - comment.pointer
yield LintProblem(comment.line_no, yield LintProblem(comment.line_no,
column, column,

View File

@@ -22,6 +22,14 @@ Use this rule to require or forbid the use of document end marker (``...``).
* Set ``present`` to ``true`` when the document end marker is required, or to * Set ``present`` to ``true`` when the document end marker is required, or to
``false`` when it is forbidden. ``false`` when it is forbidden.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
document-end:
present: true
.. rubric:: Examples .. rubric:: Examples
#. With ``document-end: {present: true}`` #. With ``document-end: {present: true}``

View File

@@ -22,6 +22,14 @@ Use this rule to require or forbid the use of document start marker (``---``).
* Set ``present`` to ``true`` when the document start marker is required, or to * Set ``present`` to ``true`` when the document start marker is required, or to
``false`` when it is forbidden. ``false`` when it is forbidden.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
document-start:
present: true
.. rubric:: Examples .. rubric:: Examples
#. With ``document-start: {present: true}`` #. With ``document-start: {present: true}``

View File

@@ -25,6 +25,16 @@ Use this rule to set a maximal number of allowed consecutive blank lines.
* ``max-end`` defines the maximal number of empty lines allowed at the end of * ``max-end`` defines the maximal number of empty lines allowed at the end of
the file. This option takes precedence over ``max``. the file. This option takes precedence over ``max``.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
empty-lines:
max: 2
max-start: 0
max-end: 0
.. rubric:: Examples .. rubric:: Examples
#. With ``empty-lines: {max: 1}`` #. With ``empty-lines: {max: 1}``

View File

@@ -23,6 +23,15 @@ Use this rule to prevent nodes with empty content, that implicitly result in
* Use ``forbid-in-block-mappings`` to prevent empty values in block mappings. * Use ``forbid-in-block-mappings`` to prevent empty values in block mappings.
* Use ``forbid-in-flow-mappings`` to prevent empty values in flow mappings. * Use ``forbid-in-flow-mappings`` to prevent empty values in flow mappings.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
empty-values:
forbid-in-block-mappings: true
forbid-in-flow-mappings: true
.. rubric:: Examples .. rubric:: Examples
#. With ``empty-values: {forbid-in-block-mappings: true}`` #. With ``empty-values: {forbid-in-block-mappings: true}``

View File

@@ -22,6 +22,14 @@ Use this rule to control the number of spaces after hyphens (``-``).
* ``max-spaces-after`` defines the maximal number of spaces allowed after * ``max-spaces-after`` defines the maximal number of spaces allowed after
hyphens. hyphens.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
hyphens:
max-spaces-after: 1
.. rubric:: Examples .. rubric:: Examples
#. With ``hyphens: {max-spaces-after: 1}`` #. With ``hyphens: {max-spaces-after: 1}``

View File

@@ -32,6 +32,16 @@ Use this rule to control the indentation.
* ``check-multi-line-strings`` defines whether to lint indentation in * ``check-multi-line-strings`` defines whether to lint indentation in
multi-line strings. Set to ``true`` to enable, ``false`` to disable. multi-line strings. Set to ``true`` to enable, ``false`` to disable.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
indentation:
spaces: consistent
indent-sequences: true
check-multi-line-strings: false
.. rubric:: Examples .. rubric:: Examples
#. With ``indentation: {spaces: 1}`` #. With ``indentation: {spaces: 1}``

View File

@@ -16,8 +16,10 @@
""" """
Use this rule to enforce alphabetical ordering of keys in mappings. The sorting 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 order uses the Unicode code point number as a default. As a result, the
case-sensitive and not accent-friendly (see examples below). ordering is case-sensitive and not accent-friendly (see examples below).
This can be changed by setting the global ``locale`` option. This allows to
sort case and accents properly.
.. rubric:: Examples .. rubric:: Examples
@@ -63,8 +65,24 @@ case-sensitive and not accent-friendly (see examples below).
- haïr: true - haïr: true
hais: true hais: true
#. With global option ``locale: "en_US.UTF-8"`` and rule ``key-ordering: {}``
as opposed to before, the following code snippet would now **PASS**:
::
- t-shirt: 1
T-shirt: 2
t-shirts: 3
T-shirts: 4
- hair: true
haïr: true
hais: true
haïssable: true
""" """
from locale import strcoll
import yaml import yaml
from yamllint.linter import LintProblem from yamllint.linter import LintProblem
@@ -101,7 +119,8 @@ def check(conf, token, prev, next, nextnext, context):
# This check is done because KeyTokens can be found inside flow # This check is done because KeyTokens can be found inside flow
# sequences... strange, but allowed. # sequences... strange, but allowed.
if len(context['stack']) > 0 and context['stack'][-1].type == MAP: if len(context['stack']) > 0 and context['stack'][-1].type == MAP:
if any(next.value < key for key in context['stack'][-1].keys): if any(strcoll(next.value, key) < 0
for key in context['stack'][-1].keys):
yield LintProblem( yield LintProblem(
next.start_mark.line + 1, next.start_mark.column + 1, next.start_mark.line + 1, next.start_mark.column + 1,
'wrong ordering of key "%s" in mapping' % next.value) 'wrong ordering of key "%s" in mapping' % next.value)

View File

@@ -30,6 +30,16 @@ recommend running yamllint with Python 3.
* ``allow-non-breakable-inline-mappings`` implies ``allow-non-breakable-words`` * ``allow-non-breakable-inline-mappings`` implies ``allow-non-breakable-words``
and extends it to also allow non-breakable words in inline mappings. and extends it to also allow non-breakable words in inline mappings.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
line-length:
max: 80
allow-non-breakable-words: true
allow-non-breakable-inline-mappings: false
.. rubric:: Examples .. rubric:: Examples
#. With ``line-length: {max: 70}`` #. With ``line-length: {max: 70}``

View File

@@ -21,6 +21,14 @@ Use this rule to force the type of new line characters.
* Set ``type`` to ``unix`` to use UNIX-typed new line characters (``\\n``), or * Set ``type`` to ``unix`` to use UNIX-typed new line characters (``\\n``), or
``dos`` to use DOS-typed new line characters (``\\r\\n``). ``dos`` to use DOS-typed new line characters (``\\r\\n``).
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
new-lines:
type: unix
""" """

View File

@@ -20,6 +20,20 @@ start with ``0`` are interpreted as octal, but this is not always wanted.
For instance ``010`` is the city code of Beijing, and should not be For instance ``010`` is the city code of Beijing, and should not be
converted to ``8``. converted to ``8``.
.. rubric:: Options
* Use ``forbid-implicit-octal`` to prevent numbers starting with ``0``.
* Use ``forbid-explicit-octal`` to prevent numbers starting with ``0o``.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
octal-values:
forbid-implicit-octal: true
forbid-explicit-octal: true
.. rubric:: Examples .. rubric:: Examples
#. With ``octal-values: {forbid-implicit-octal: true}`` #. With ``octal-values: {forbid-implicit-octal: true}``
@@ -57,6 +71,8 @@ converted to ``8``.
city-code: 0o10 city-code: 0o10
""" """
import re
import yaml import yaml
from yamllint.linter import LintProblem from yamllint.linter import LintProblem
@@ -70,6 +86,10 @@ DEFAULT = {'forbid-implicit-octal': True,
'forbid-explicit-octal': True} 'forbid-explicit-octal': True}
def _is_octal_number(string):
return re.match(r'^[0-7]+$', string) is not None
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):
if prev and isinstance(prev, yaml.tokens.TagToken): if prev and isinstance(prev, yaml.tokens.TagToken):
return return
@@ -78,7 +98,8 @@ def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.tokens.ScalarToken): if isinstance(token, yaml.tokens.ScalarToken):
if not token.style: if not token.style:
val = token.value val = token.value
if val.isdigit() and len(val) > 1 and val[0] == '0': if (val.isdigit() and len(val) > 1 and val[0] == '0' and
_is_octal_number(val[1:])):
yield LintProblem( yield LintProblem(
token.start_mark.line + 1, token.end_mark.column + 1, token.start_mark.line + 1, token.end_mark.column + 1,
'forbidden implicit octal value "%s"' % 'forbidden implicit octal value "%s"' %
@@ -88,7 +109,8 @@ def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.tokens.ScalarToken): if isinstance(token, yaml.tokens.ScalarToken):
if not token.style: if not token.style:
val = token.value val = token.value
if len(val) > 2 and val[:2] == '0o' and val[2:].isdigit(): if (len(val) > 2 and val[:2] == '0o' and
_is_octal_number(val[2:])):
yield LintProblem( yield LintProblem(
token.start_mark.line + 1, token.end_mark.column + 1, token.start_mark.line + 1, token.end_mark.column + 1,
'forbidden explicit octal value "%s"' % 'forbidden explicit octal value "%s"' %

View File

@@ -34,6 +34,17 @@ used.
**Note**: Multi-line strings (with ``|`` or ``>``) will not be checked. **Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
quoted-strings:
quote-type: any
required: true
extra-required: []
extra-allowed: []
.. rubric:: Examples .. rubric:: Examples
#. With ``quoted-strings: {quote-type: any, required: true}`` #. With ``quoted-strings: {quote-type: any, required: true}``
@@ -149,7 +160,7 @@ def _quotes_are_needed(string):
try: try:
a, b = loader.get_token(), loader.get_token() a, b = loader.get_token(), loader.get_token()
if (isinstance(a, yaml.ScalarToken) and a.style is None and if (isinstance(a, yaml.ScalarToken) and a.style is None and
isinstance(b, yaml.BlockEndToken)): isinstance(b, yaml.BlockEndToken) and a.value == string):
return False return False
return True return True
except yaml.scanner.ScannerError: except yaml.scanner.ScannerError:

View File

@@ -34,6 +34,15 @@ This can be useful to prevent surprises from YAML parsers transforming
``truthy`` rule applies to both keys and values. Set this option to ``false`` ``truthy`` rule applies to both keys and values. Set this option to ``false``
to prevent this. to prevent this.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
truthy:
allowed-values: ['true', 'false']
check-keys: true
.. rubric:: Examples .. rubric:: Examples
#. With ``truthy: {}`` #. With ``truthy: {}``