Compare commits

...

68 Commits

Author SHA1 Message Date
Adrien Vergé
37700ab3e6 yamllint version 1.15.0 2019-02-11 14:18:11 +01:00
Adrien Vergé
f66661e36d docs(cli): Add a paragraph about standard input
See commit 05dfcbc "cli: Add command line option - to read from standard
input", cc @miguelbarao.
2019-02-11 14:16:33 +01:00
Adrien Vergé
d6b89e94e4 chore(docs): Fix conf.py styling 2019-02-11 14:09:20 +01:00
Miguel Barao
05dfcbc109 cli: Add command line option - to read from standard input
If YAML files are given as arguments, parses these files.
If yamllint is run with - option, stdin.
If no arguments are given, just fail.
2019-02-11 14:04:48 +01:00
Adrien Vergé
16b939958d yamllint version 1.14.0 2019-01-14 09:47:07 +01:00
Adrien Vergé
b4740dc1fb comments: Fix ignore-shebangs option on corner cases 2019-01-14 09:40:31 +01:00
Mattias Bengtsson
b77f78f677 Support ignoring shebangs
Some usages of YAML (like Ansible) supports running the file as a script.

Support (by default) an ignore-shebangs setting for the comments module.

Fixes #116 - comments rule with require-starting-space: true should special case shebang
2019-01-14 09:40:31 +01:00
Adrien Vergé
0f073f7a09 config: Do not require all rule options to be set
Before, it was required to specify all the options when customizing a
rule. For instance, one could use `empty-lines: enable` or `empty-lines:
{max: 1, max-start: 2, max-end: 2}`, but not just `empty-lines: {max:
1}` (it would fail with *invalid config: missing option "max-start" for
rule "empty-lines"*).

This was a minor problem for users, but it prevented the addition of new
options to existing rules, see [1] for an example. If a new option was
added, updating yamllint for all users that customize the rule would
produce a crash (*invalid config: missing option ...*).

To avoid that, let's embed default values inside the rules themselves,
instead of keeping them in `conf/default.yaml`.

This refactor should not have any impact on existing projects. I've
manually checked that it did not change the output of tests, on
different projects:
- ansible/ansible: `test/runner/ansible-test sanity --python 3.7 --test yamllint`
- ansible/molecule: `yamllint -s test/ molecule/`
- Neo23x0/sigma: `make test-yaml`
- markstory/lint-review: `yamllint .`

[1]: https://github.com/adrienverge/yamllint/pull/151
2019-01-10 10:01:31 +01:00
cclauss
bc7ac81707 Travis CI: Add Python 3.7 and 3.8a
* Adds Python 3.7.1 and a current nightly build of Python 3.8 alpha.
* Python 3.3 reached its end of life in 2017 https://devguide.python.org/#branchstatus
2018-12-10 17:12:50 +01:00
Adrien Vergé
a56a1015f0 style(docs): Fix RST lint errors reported by doc8 2018-12-08 11:21:02 +01:00
Adrien Vergé
6cf5eecdac chore(CI): Lint RST (reStructuredText) files 2018-12-08 11:21:02 +01:00
Hugo
f4c56b8216 Upgrade Python syntax with pyupgrade
https://github.com/asottile/pyupgrade
2018-11-26 19:09:47 +01:00
Hugo
5852566ff0 Upgrade unit tests to use more useful asserts 2018-11-26 19:09:47 +01:00
Hugo
4a7986b4cf Remove redundant parentheses 2018-11-26 19:09:47 +01:00
Hugo
3d1ad9a176 Add explicit Trove classifers for PyPI 2018-11-26 19:09:47 +01:00
Hugo
8da6e36bf1 Add python_requires to help pip 2018-11-26 19:09:47 +01:00
Hugo
c281d48507 Drop support for EOL Python 2.6 2018-11-26 19:09:47 +01:00
Adrien Vergé
8bdddf6e89 docs: Warn about Python 2 and problems with line-length
Closes #146.
2018-11-23 14:19:55 +01:00
Adrien Vergé
c8032c086b line-length: Add tests for lines containing unicode characters
Some unicode characters span accross multiple bytes. Python 3 is OK with
that, but Python 2 reports an incorrect number of characters.

Related to https://github.com/adrienverge/yamllint/issues/146
2018-11-23 14:19:55 +01:00
Adrien Vergé
ea045c41b7 CI: Drop Python 3.3 support
The `pkg_resources` package inside `setuptools` explicitly [disallows
Python 3.3](7392f01ffc (diff-81de4a30a55fcc3fb944f8387ea9ec94)):

    if (3, 0) < sys.version_info < (3, 4):
        raise RuntimeError("Python 3.4 or later is required")

It's time to drop support for 3.3.
2018-11-23 14:10:00 +01:00
Adrien Vergé
c803dd5f6d docs(CHANGELOG): Fix RST format for code snippets 2018-11-14 19:08:39 +01:00
Adrien Vergé
318a12bbe6 yamllint version 1.13.0 2018-11-14 19:04:58 +01:00
Adrien Vergé
66adaee66c docs: Add documentation on the new -f colored option 2018-11-14 19:02:52 +01:00
sedrubal
5062b91cac cli: Add -f colored to force colors
`-f standard` shows non-colored output,
`-f colored` shows colored output,
`-f auto` is the new default, it chooses `standard` or `colored`
depending on terminal capabilities.
2018-10-22 10:35:35 +02:00
sedrubal
3ef85739e3 Use isinstance(x, y) instead of type(x) == y
Fixes pylint C0123.
2018-10-20 10:02:16 +02:00
Adrien Vergé
dc4a9f4fff yamllint version 1.12.1 2018-10-17 10:22:32 +02:00
Adrien Vergé
8354d50016 quoted-strings: Fix broken rule
Original implementation was completely broken. Documentation and actual
behavior were different. Numbers and booleans were detected as wrong, as
well as explicit types.

Fixes #136 and #130.
2018-10-17 10:18:25 +02:00
Adrien Vergé
524d721f0d Update .gitignore to exclude build/ 2018-10-11 15:35:26 +02:00
Adrien Vergé
e864f57d37 docs: Fix missing quoted-strings module in documentation 2018-10-11 15:27:58 +02:00
Adrien Vergé
d41b64aa97 yamllint version 1.12.0 2018-10-04 16:10:56 +02:00
Guido Wischrop (mgm tp)
aaa8777f1d Add quoted-strings rule
* taken from https://github.com/adrienverge/yamllint/pull/110 (submitted by @jurajseffer)
* small fixes for generic and multi-line strings
* fixes for comments from @adrienverge
2018-10-04 16:09:56 +02:00
Adrien Vergé
479f580202 CI: Fix tests failing on Travis for Python 2.6
Because installing dependencies for `coveralls` now fails with:

    Collecting pycparser (from cffi>=1.7->cryptography>=1.3.4; python_version <= "2.7" and extra == "secure"->urllib3[secure]; python_version < "3"->coveralls)
    [...]
    pycparser requires Python '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' but the running Python is 2.6.9
2018-10-02 19:27:54 +02:00
Justin Foreman
e4e99f0aba docs: Update README for CentOS dependency 2018-05-31 11:06:22 +02:00
Adrien Vergé
203cfc20f0 docs: Remove sudo from pip installation instructions 2018-05-09 20:20:18 +02:00
Adrien Vergé
51c30505b5 docs: Add Mac OS installation instructions
See https://github.com/adrienverge/yamllint/issues/91 and
https://github.com/Homebrew/homebrew-core/blob/af2bbe9/Formula/yamllint.rb
2018-05-09 20:19:03 +02:00
Adrien Vergé
ff9ebde608 docs: Remove old Debian / Ubuntu installation instructions 2018-05-09 20:19:03 +02:00
Adrien Vergé
506e066410 yamllint version 1.11.1 2018-04-06 11:11:32 +02:00
Adrien Vergé
54f21c0514 parser: Fix crash with latest PyYAML
There is a backwards-incompatible change in PyYAML that induces a crash
if `check_token()` is not called before `peek_token()`. See commit
a02d17a in PyYAML or https://github.com/yaml/pyyaml/pull/150.

Closes #105.
2018-04-06 11:07:55 +02:00
Adam Johnson
36b4776778 Clarify documentation on the 'truthy' rule
I like the 'truthy' rule but its documentation and message have confused several of my colleagues. I've tried rewriting it to be clearer.
2018-04-06 11:07:46 +02:00
Adrien Vergé
3bdc1b6e1b CI: Don't install Sphinx if Python 2
Recently builds started to fail with:

    Collecting sphinx
      Downloading Sphinx-1.7.2-py2.py3-none-any.whl (1.9MB)
        100% |████████████████████████████████| 1.9MB 731kB/s
    Sphinx requires Python '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
    but the running Python is 2.6.9
2018-04-06 09:39:53 +02:00
Adrien Vergé
c16934117b CI: Remove Travis hack for enum34 crashing on Python 3.6
Revert commit 8b9eab3, it is not needed anymore.
2018-04-06 09:39:53 +02:00
Eimert
8ab680635b docs: Make ignore examples clearer
[Solved](https://github.com/metacloud/molecule/issues/1228), when
yamllint is used by molecule.
2018-04-02 20:06:17 +02:00
Anthony Sottile
503bde9e70 pre-commit is now served over https! 2018-03-03 08:01:24 +01:00
Nick Burke
1b379628d7 key-duplicates: Handle merge keys (<<)
Merge keys are described here: http://yaml.org/type/merge.html
They shouldn't be considered as duplicated keys.

Fixes https://github.com/adrienverge/yamllint/issues/88
2018-02-28 23:12:43 +01:00
Adrien Vergé
6a842229fd yamllint version 1.11.0 2018-02-21 13:42:06 +01:00
Adrien Vergé
8b9eab33bf CI: Fix failing tests for Python 3.6 because of flake8-import-order
See issue https://github.com/PyCQA/flake8-import-order/issues/149
2018-02-21 13:40:21 +01:00
xieenlong
22e792a433 Feature: checking octal numbers 2017-12-07 18:29:05 +01:00
Adrien Vergé
f713dc8be2 style: Fix E100 and E202 errors reported by pycodestyle 2017-12-07 18:28:53 +01:00
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
55 changed files with 1725 additions and 294 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ __pycache__
/docs/_build /docs/_build
/dist /dist
/yamllint.egg-info /yamllint.egg-info
/build

View File

@@ -1,11 +1,11 @@
--- ---
# For use with pre-commit. # For use with pre-commit.
# See usage instructions at http://pre-commit.com # See usage instructions at https://pre-commit.com
- id: yamllint - id: yamllint
name: yamllint name: yamllint
description: This hook runs yamllint. description: This hook runs yamllint.
entry: yamllint entry: yamllint
language: python language: python
files: \.(yaml|yml)$ types: [file, yaml]

View File

@@ -1,20 +1,24 @@
--- ---
dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069)
language: python language: python
python: python:
- 2.6
- 2.7 - 2.7
- 3.3
- 3.4 - 3.4
- 3.5 - 3.5
- 3.6 - 3.6
- 3.7
- nightly - nightly
install: install:
- pip install pyyaml flake8 flake8-import-order coveralls - pip install pyyaml coveralls flake8 flake8-import-order doc8
- if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi - if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then pip install sphinx; fi
- pip install . - pip install .
script: script:
- if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then flake8 .; fi - if [[ $TRAVIS_PYTHON_VERSION != nightly ]]; then flake8 .; 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 setup.py test
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then
python setup.py build_sphinx;
fi
after_success: after_success:
coveralls coveralls

View File

@@ -1,6 +1,76 @@
Changelog Changelog
========= =========
1.15.0 (2019-02-11)
-------------------
- Allow linting from standard input with ``yamllint -``
1.14.0 (2019-01-14)
-------------------
- Fix documentation code snippets
- Drop Python 2.6 and 3.3 support, add Python 3.7 support
- Update documentation and tests for ``line-length`` + Unicode + Python 2
- Allow rule configurations to lack options
- Add a new ``ignore-shebangs`` option for the ``comments`` rule
1.13.0 (2018-11-14)
-------------------
- Use ``isinstance(x, y)`` instead of ``type(x) == y``
- Add a new ``-f colored`` option
- Update documentation about colored output when run from CLI
1.12.1 (2018-10-17)
-------------------
- Fix the ``quoted-strings`` rule, broken implementation
- Fix missing documentation for the ``quoted-strings`` rule
1.12.0 (2018-10-04)
-------------------
- Add a new ``quoted-strings`` rule
- Update installation documentation for pip, CentOS, Debian, Ubuntu, Mac OS
1.11.1 (2018-04-06)
-------------------
- Handle merge keys (``<<``) in the ``key-duplicates`` rule
- Update documentation about pre-commit
- Make examples for ``ignore`` rule clearer
- Clarify documentation on the 'truthy' rule
- Fix crash in parser due to a change in PyYAML > 3.12
1.11.0 (2018-02-21)
-------------------
- Add a new ``octal-values`` rule
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) 1.8.1 (2017-07-04)
------------------ ------------------

View File

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

View File

@@ -38,7 +38,8 @@ Screenshot
Installation Installation
^^^^^^^^^^^^ ^^^^^^^^^^^^
On Fedora / CentOS: On Fedora / CentOS (note: `EPEL <https://fedoraproject.org/wiki/EPEL>`_ is
required on CentOS):
.. code:: bash .. code:: bash
@@ -50,11 +51,17 @@ On Debian 8+ / Ubuntu 16.04+:
sudo apt-get install yamllint sudo apt-get install yamllint
On Mac OS 10.11+:
.. code:: bash
brew install yamllint
Alternatively using pip, the Python package manager: Alternatively using pip, the Python package manager:
.. code:: bash .. code:: bash
sudo pip install yamllint pip install --user yamllint
Usage Usage
^^^^^ ^^^^^

View File

@@ -4,6 +4,7 @@
import sys import sys
import os import os
from unittest.mock import MagicMock
sys.path.insert(0, os.path.abspath('..')) # noqa sys.path.insert(0, os.path.abspath('..')) # noqa
@@ -40,3 +41,15 @@ htmlhelp_basename = 'yamllintdoc'
man_pages = [ man_pages = [
('index', 'yamllint', '', [u'Adrien Vergé'], 1) ('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

@@ -45,9 +45,9 @@ It can be chosen using:
Extending the default configuration Extending the default configuration
----------------------------------- -----------------------------------
When writing a custom configuration file, you don't need to redefine every rule. When writing a custom configuration file, you don't need to redefine every
Just extend the ``default`` configuration (or any already-existing configuration rule. Just extend the ``default`` configuration (or any already-existing
file). configuration file).
For instance, if you just want to disable the ``comments-indentation`` rule, For instance, if you just want to disable the ``comments-indentation`` rule,
your file could look like this: your file could look like this:
@@ -102,11 +102,11 @@ Errors and warnings
------------------- -------------------
Problems detected by yamllint can be raised either as errors or as warnings. Problems detected by yamllint can be raised either as errors or as warnings.
The CLI will output them (with different colors when using the ``standard`` The CLI will output them (with different colors when using the ``colored``
output format). output format, or ``auto`` when run from a terminal).
By default the script will exit with a return code ``1`` *only when* there is one or By default the script will exit with a return code ``1`` *only when* there is
more error(s). one or more error(s).
However if strict mode is enabled with the ``-s`` (or ``--strict``) option, the However if strict mode is enabled with the ``-s`` (or ``--strict``) option, the
return code will be: return code will be:
@@ -129,7 +129,7 @@ You can either totally ignore files (they won't be looked at):
ignore: | ignore: |
/this/specific/file.yaml /this/specific/file.yaml
/all/this/directory/ all/this/directory/
*.template.yaml *.template.yaml
or ignore paths only for specific rules: or ignore paths only for specific rules:
@@ -167,4 +167,4 @@ Here is a more complex example:
trailing-spaces: trailing-spaces:
ignore: | ignore: |
*.ignore-trailing-spaces.yaml *.ignore-trailing-spaces.yaml
/ascii-art/* ascii-art/*

View File

@@ -4,9 +4,9 @@ Disable with comments
Disabling checks for a specific line Disabling checks for a specific line
------------------------------------ ------------------------------------
To prevent yamllint from reporting problems for a specific line, add a directive To prevent yamllint from reporting problems for a specific line, add a
comment (``# yamllint disable-line ...``) on that line, or on the line above. directive comment (``# yamllint disable-line ...``) on that line, or on the
For instance: line above. For instance:
.. code-block:: yaml .. code-block:: yaml
@@ -46,9 +46,9 @@ If you need to disable multiple rules, it is allowed to chain rules like this:
Disabling checks for all (or part of) the file Disabling checks for all (or part of) the file
---------------------------------------------- ----------------------------------------------
To prevent yamllint from reporting problems for the whole file, or for a block of To prevent yamllint from reporting problems for the whole file, or for a block
lines within the file, use ``# yamllint disable ...`` and ``# yamllint enable of lines within the file, use ``# yamllint disable ...`` and ``# yamllint
...`` directive comments. For instance: enable ...`` directive comments. For instance:
.. code-block:: yaml .. code-block:: yaml

View File

@@ -26,3 +26,4 @@ Table of contents
disable_with_comments disable_with_comments
development development
text_editors 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

@@ -16,25 +16,24 @@ On Debian 8+ / Ubuntu 16.04+:
sudo apt-get install yamllint sudo apt-get install yamllint
On older Debian / Ubuntu versions: On Mac OS 10.11+:
.. code:: bash .. code:: bash
sudo add-apt-repository -y ppa:adrienverge/ppa && sudo apt-get update brew install yamllint
sudo apt-get install yamllint
Alternatively using pip, the Python package manager: Alternatively using pip, the Python package manager:
.. code:: bash .. code:: bash
sudo pip install yamllint pip install --user yamllint
If you prefer installing from source, you can run, from the source directory: If you prefer installing from source, you can run, from the source directory:
.. code:: bash .. code:: bash
python setup.py sdist python setup.py sdist
sudo pip install dist/yamllint-*.tar.gz pip install --user dist/yamllint-*.tar.gz
Running yamllint Running yamllint
---------------- ----------------
@@ -51,6 +50,12 @@ You can also lint all YAML files in a whole directory:
yamllint . yamllint .
Or lint a YAML stream from standard input:
.. code:: bash
echo -e 'this: is\nvalid: YAML' | yamllint -
The output will look like (colors are not displayed here): The output will look like (colors are not displayed here):
:: ::
@@ -69,6 +74,10 @@ The output will look like (colors are not displayed here):
10:1 error too many blank lines (4 > 2) (empty-lines) 10:1 error too many blank lines (4 > 2) (empty-lines)
11:4 error too many spaces inside braces (braces) 11:4 error too many spaces inside braces (braces)
By default, the output of yamllint is colored when run from a terminal, and
pure text in other cases. Add the ``-f standard`` arguments to force
non-colored output. Use the ``-f colored`` arguments to force colored output.
Add the ``-f parsable`` arguments if you need an output format parsable by a Add the ``-f parsable`` arguments if you need an output format parsable by a
machine (for instance for :doc:`syntax highlighting in text editors machine (for instance for :doc:`syntax highlighting in text editors
<text_editors>`). The output will then look like: <text_editors>`). The output will then look like:

View File

@@ -59,6 +59,11 @@ empty-lines
.. automodule:: yamllint.rules.empty_lines .. automodule:: yamllint.rules.empty_lines
empty-values
------------
.. automodule:: yamllint.rules.empty_values
hyphens hyphens
------- -------
@@ -74,6 +79,11 @@ key-duplicates
.. automodule:: yamllint.rules.key_duplicates .. automodule:: yamllint.rules.key_duplicates
key-ordering
--------------
.. automodule:: yamllint.rules.key_ordering
line-length line-length
----------- -----------
@@ -89,6 +99,16 @@ new-lines
.. automodule:: yamllint.rules.new_lines .. automodule:: yamllint.rules.new_lines
octal-values
------------
.. automodule:: yamllint.rules.octal_values
quoted-strings
--------------
.. automodule:: yamllint.rules.quoted_strings
trailing-spaces trailing-spaces
--------------- ---------------

View File

@@ -3,3 +3,10 @@ universal = 1
[flake8] [flake8]
import-order-style = pep8 import-order-style = pep8
application-import-names = yamllint
[build_sphinx]
all-files = 1
source-dir = docs
build-dir = docs/_build
warning-is-error = 1

View File

@@ -29,13 +29,19 @@ setup(
license=__license__, license=__license__,
keywords=['yaml', 'lint', 'linter', 'syntax', 'checker'], keywords=['yaml', 'lint', 'linter', 'syntax', 'checker'],
url='https://github.com/adrienverge/yamllint', url='https://github.com/adrienverge/yamllint',
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
classifiers=[ classifiers=[
'Development Status :: 5 - Production/Stable', 'Development Status :: 5 - Production/Stable',
'Environment :: Console', 'Environment :: Console',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3', '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',
'Topic :: Software Development :: Debuggers', 'Topic :: Software Development :: Debuggers',
'Topic :: Software Development :: Quality Assurance', 'Topic :: Software Development :: Quality Assurance',
@@ -44,8 +50,7 @@ setup(
packages=find_packages(exclude=['tests', 'tests.*']), packages=find_packages(exclude=['tests', 'tests.*']),
entry_points={'console_scripts': ['yamllint=yamllint.cli:run']}, entry_points={'console_scripts': ['yamllint=yamllint.cli:run']},
package_data={'yamllint': ['conf/*.yaml'], package_data={'yamllint': ['conf/*.yaml']},
'tests': ['yaml-1.2-spec-examples/*']},
install_requires=['pathspec >=0.5.3', 'pyyaml'], install_requires=['pathspec >=0.5.3', 'pyyaml'],
test_suite='tests', test_suite='tests',
) )

View File

@@ -16,12 +16,7 @@
import os import os
import tempfile import tempfile
import sys import unittest
try:
assert sys.version_info >= (2, 7)
import unittest
except:
import unittest2 as unittest
import yaml import yaml

View File

@@ -80,6 +80,48 @@ class CommentsTestCase(RuleTestCase):
problem3=(9, 2), problem4=(10, 4), problem3=(9, 2), problem4=(10, 4),
problem5=(15, 3)) problem5=(15, 3))
def test_shebang(self):
conf = ('comments:\n'
' require-starting-space: true\n'
' ignore-shebangs: false\n'
'comments-indentation: disable\n'
'document-start: disable\n')
self.check('#!/bin/env my-interpreter\n',
conf, problem1=(1, 2))
self.check('# comment\n'
'#!/bin/env my-interpreter\n', conf,
problem1=(2, 2))
self.check('#!/bin/env my-interpreter\n'
'---\n'
'#comment\n'
'#!/bin/env my-interpreter\n'
'', conf,
problem1=(1, 2), problem2=(3, 2), problem3=(4, 2))
self.check('#! not a shebang\n',
conf, problem1=(1, 2))
self.check('key: #!/not/a/shebang\n',
conf, problem1=(1, 8))
def test_ignore_shebang(self):
conf = ('comments:\n'
' require-starting-space: true\n'
' ignore-shebangs: true\n'
'comments-indentation: disable\n'
'document-start: disable\n')
self.check('#!/bin/env my-interpreter\n', conf)
self.check('# comment\n'
'#!/bin/env my-interpreter\n', conf,
problem1=(2, 2))
self.check('#!/bin/env my-interpreter\n'
'---\n'
'#comment\n'
'#!/bin/env my-interpreter\n', conf,
problem2=(3, 2), problem3=(4, 2))
self.check('#! not a shebang\n',
conf, problem1=(1, 2))
self.check('key: #!/not/a/shebang\n',
conf, problem1=(1, 8))
def test_spaces_from_content(self): def test_spaces_from_content(self):
conf = ('comments:\n' conf = ('comments:\n'
' require-starting-space: false\n' ' require-starting-space: false\n'

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

@@ -15,6 +15,7 @@
# 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 tests.common import RuleTestCase from tests.common import RuleTestCase
from yamllint.parser import token_or_comment_generator, Comment from yamllint.parser import token_or_comment_generator, Comment
from yamllint.rules.indentation import check from yamllint.rules.indentation import check
@@ -50,8 +51,8 @@ class IndentationStackTestCase(RuleTestCase):
.replace('Mapping', 'Map')) .replace('Mapping', 'Map'))
if token_type in ('StreamStart', 'StreamEnd'): if token_type in ('StreamStart', 'StreamEnd'):
continue continue
output += '%9s %s\n' % (token_type, output += '{:>9} {}\n'.format(token_type,
self.format_stack(context['stack'])) self.format_stack(context['stack']))
return output return output
def test_simple_mapping(self): def test_simple_mapping(self):
@@ -589,6 +590,9 @@ class IndentationTestCase(RuleTestCase):
' date: 1969\n' ' date: 1969\n'
' - name: Linux\n' ' - name: Linux\n'
' date: 1991\n' ' date: 1991\n'
' k4:\n'
' -\n'
' k5: v3\n'
'...\n', conf) '...\n', conf)
conf = 'indentation: {spaces: 2, indent-sequences: true}' conf = 'indentation: {spaces: 2, indent-sequences: true}'
self.check('---\n' self.check('---\n'
@@ -1208,6 +1212,20 @@ class IndentationTestCase(RuleTestCase):
' - name: Linux\n' ' - name: Linux\n'
' date: 1991\n' ' date: 1991\n'
'...\n', conf, problem=(5, 10, 'syntax')) '...\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): def test_over_indented(self):
conf = 'indentation: {spaces: 2, indent-sequences: consistent}' conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
@@ -1264,6 +1282,20 @@ class IndentationTestCase(RuleTestCase):
' - subel\n' ' - subel\n'
'...\n', conf, '...\n', conf,
problem=(2, 3)) 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): def test_multi_lines(self):
conf = 'indentation: {spaces: consistent, indent-sequences: true}' conf = 'indentation: {spaces: consistent, indent-sequences: true}'

View File

@@ -78,6 +78,15 @@ class KeyDuplicatesTestCase(RuleTestCase):
' duplicates with\n' ' duplicates with\n'
' many styles\n' ' many styles\n'
': 1\n', conf) ': 1\n', conf)
self.check('---\n'
'Merge Keys are OK:\n'
'anchor_one: &anchor_one\n'
' one: one\n'
'anchor_two: &anchor_two\n'
' two: two\n'
'anchor_reference:\n'
' <<: *anchor_one\n'
' <<: *anchor_two\n', conf)
def test_enabled(self): def test_enabled(self):
conf = 'key-duplicates: enable' conf = 'key-duplicates: enable'
@@ -147,6 +156,15 @@ class KeyDuplicatesTestCase(RuleTestCase):
': 1\n', conf, ': 1\n', conf,
problem1=(3, 1), problem2=(4, 1), problem3=(5, 3), problem1=(3, 1), problem2=(4, 1), problem3=(5, 3),
problem4=(7, 3)) problem4=(7, 3))
self.check('---\n'
'Merge Keys are OK:\n'
'anchor_one: &anchor_one\n'
' one: one\n'
'anchor_two: &anchor_two\n'
' two: two\n'
'anchor_reference:\n'
' <<: *anchor_one\n'
' <<: *anchor_two\n', conf)
def test_key_tokens_in_flow_sequences(self): def test_key_tokens_in_flow_sequences(self):
conf = 'key-duplicates: enable' conf = 'key-duplicates: enable'

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

@@ -14,6 +14,9 @@
# 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 sys
import unittest
from tests.common import RuleTestCase from tests.common import RuleTestCase
@@ -155,3 +158,16 @@ class LineLengthTestCase(RuleTestCase):
'content: |\n' 'content: |\n'
' {% this line is' + 99 * ' really' + ' long %}\n', ' {% this line is' + 99 * ' really' + ' long %}\n',
conf, problem=(3, 81)) conf, problem=(3, 81))
@unittest.skipIf(sys.version_info < (3, 0), 'Python 2 not supported')
def test_unicode(self):
conf = 'line-length: {max: 53}'
self.check('---\n'
'# This is a test to check if “line-length” works nice\n'
'with: “unicode characters” that span accross bytes! ↺\n',
conf)
conf = 'line-length: {max: 52}'
self.check('---\n'
'# This is a test to check if “line-length” works nice\n'
'with: “unicode characters” that span accross bytes! ↺\n',
conf, problem1=(2, 53), problem2=(3, 53))

View File

@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.common import RuleTestCase
class OctalValuesTestCase(RuleTestCase):
rule_id = 'octal-values'
def test_disabled(self):
conf = ('octal-values: disable\n'
'new-line-at-end-of-file: disable\n'
'document-start: disable\n')
self.check('user-city: 010', conf)
self.check('user-city: 0o10', conf)
def test_implicit_octal_values(self):
conf = ('octal-values: {forbid-implicit-octal: true}\n'
'new-line-at-end-of-file: disable\n'
'document-start: disable\n')
self.check('user-city: 010', conf, problem=(1, 15))
self.check('user-city: abc', conf)
self.check('user-city: 010,0571', conf)
self.check("user-city: '010'", conf)
self.check('user-city: "010"', conf)
self.check('user-city:\n'
' - 010', conf, problem=(2, 8))
self.check('user-city: [010]', conf, problem=(1, 16))
self.check('user-city: {beijing: 010}', conf, problem=(1, 25))
self.check('explicit-octal: 0o10', conf)
self.check('not-number: 0abc', conf)
self.check('zero: 0', conf)
self.check('hex-value: 0x10', conf)
self.check('number-values:\n'
' - 0.10\n'
' - .01\n'
' - 0e3\n', conf)
def test_explicit_octal_values(self):
conf = ('octal-values: {forbid-explicit-octal: true}\n'
'new-line-at-end-of-file: disable\n'
'document-start: disable\n')
self.check('user-city: 0o10', conf, problem=(1, 16))
self.check('user-city: abc', conf)
self.check('user-city: 0o10,0571', conf)
self.check("user-city: '0o10'", conf)
self.check('user-city:\n'
' - 0o10', conf, problem=(2, 9))
self.check('user-city: [0o10]', conf, problem=(1, 17))
self.check('user-city: {beijing: 0o10}', conf, problem=(1, 26))
self.check('implicit-octal: 010', conf)
self.check('not-number: 0oabc', conf)
self.check('zero: 0', conf)
self.check('hex-value: 0x10', conf)
self.check('number-values:\n'
' - 0.10\n'
' - .01\n'
' - 0e3\n', conf)
self.check('user-city: "010"', conf)

View File

@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 ClearScore
#
# 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 QuotedTestCase(RuleTestCase):
rule_id = 'quoted-strings'
def test_disabled(self):
conf = 'quoted-strings: disable'
self.check('---\n'
'foo: bar\n', conf)
self.check('---\n'
'foo: "bar"\n', conf)
self.check('---\n'
'foo: \'bar\'\n', conf)
self.check('---\n'
'bar: 123\n', conf)
def test_quote_type_any(self):
conf = 'quoted-strings: {quote-type: any}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n' # fails
'string2: "foo"\n'
'string3: \'bar\'\n'
'string4: !!str genericstring\n'
'string5: !!str 456\n'
'string6: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n',
conf, problem=(4, 10))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf, problem1=(9, 3))
def test_quote_type_single(self):
conf = 'quoted-strings: {quote-type: single}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n' # fails
'string2: "foo"\n' # fails
'string3: \'bar\'\n'
'string4: !!str genericstring\n'
'string5: !!str 456\n'
'string6: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n',
conf, problem1=(4, 10), problem2=(5, 10))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf, problem1=(9, 3), problem2=(12, 3))
def test_quote_type_double(self):
conf = 'quoted-strings: {quote-type: double}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n' # fails
'string2: "foo"\n'
'string3: \'bar\'\n' # fails
'string4: !!str genericstring\n'
'string5: !!str 456\n'
'string6: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n',
conf, problem1=(4, 10), problem2=(6, 10))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf, problem1=(9, 3))

View File

@@ -24,18 +24,13 @@ import os
import pty import pty
import shutil import shutil
import sys import sys
try: import unittest
assert sys.version_info >= (2, 7)
import unittest
except:
import unittest2 as unittest
from yamllint import cli
from tests.common import build_temp_workspace from tests.common import build_temp_workspace
from yamllint import cli
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
class CommandLineTestCase(unittest.TestCase): class CommandLineTestCase(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@@ -145,6 +140,17 @@ class CommandLineTestCase(unittest.TestCase):
r'not allowed with argument -c\/--config-file$' r'not allowed with argument -c\/--config-file$'
) )
# checks if reading from stdin and files are mutually exclusive
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-', 'file'))
self.assertNotEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^usage')
def test_run_with_bad_config(self): def test_run_with_bad_config(self):
sys.stdout, sys.stderr = StringIO(), StringIO() sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx: with self.assertRaises(SystemExit) as ctx:
@@ -299,7 +305,10 @@ class CommandLineTestCase(unittest.TestCase):
# Make sure the default localization conditions on this "system" # Make sure the default localization conditions on this "system"
# support UTF-8 encoding. # support UTF-8 encoding.
loc = locale.getlocale() 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() sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx: with self.assertRaises(SystemExit) as ctx:
@@ -348,7 +357,7 @@ class CommandLineTestCase(unittest.TestCase):
'\n' % file)) '\n' % file))
self.assertEqual(err, '') self.assertEqual(err, '')
def test_run_colored_output(self): def test_run_default_format_output_in_tty(self):
file = os.path.join(self.wd, 'a.yaml') file = os.path.join(self.wd, 'a.yaml')
# Create a pseudo-TTY and redirect stdout to it # Create a pseudo-TTY and redirect stdout to it
@@ -380,3 +389,78 @@ class CommandLineTestCase(unittest.TestCase):
'no new line character at the end of file ' 'no new line character at the end of file '
'\033[2m(new-line-at-end-of-file)\033[0m\n' '\033[2m(new-line-at-end-of-file)\033[0m\n'
'\n' % file)) '\n' % file))
def test_run_default_format_output_without_tty(self):
file = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run((file, ))
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'%s\n'
' 2:4 error trailing spaces (trailing-spaces)\n'
' 3:4 error no new line character at the end of file '
'(new-line-at-end-of-file)\n'
'\n' % file))
self.assertEqual(err, '')
def test_run_auto_output_without_tty_output(self):
file = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run((file, '--format', 'auto'))
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'%s\n'
' 2:4 error trailing spaces (trailing-spaces)\n'
' 3:4 error no new line character at the end of file '
'(new-line-at-end-of-file)\n'
'\n' % file))
self.assertEqual(err, '')
def test_run_format_colored(self):
file = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run((file, '--format', 'colored'))
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'\033[4m%s\033[0m\n'
' \033[2m2:4\033[0m \033[31merror\033[0m '
'trailing spaces \033[2m(trailing-spaces)\033[0m\n'
' \033[2m3:4\033[0m \033[31merror\033[0m '
'no new line character at the end of file '
'\033[2m(new-line-at-end-of-file)\033[0m\n'
'\n' % file))
self.assertEqual(err, '')
def test_run_read_from_stdin(self):
# prepares stdin with an invalid yaml string so that we can check
# for its specific error, and be assured that stdin was read
sys.stdout, sys.stderr = StringIO(), StringIO()
sys.stdin = StringIO(
'I am a string\n'
'therefore: I am an error\n')
with self.assertRaises(SystemExit) as ctx:
cli.run(('-', '-f', 'parsable'))
self.assertNotEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'stdin:2:10: [error] syntax error: '
'mapping values are not allowed here\n'))
self.assertEqual(err, '')

View File

@@ -21,17 +21,14 @@ except ImportError:
import os import os
import shutil import shutil
import sys import sys
try: import tempfile
assert sys.version_info >= (2, 7) import unittest
import unittest
except: from tests.common import build_temp_workspace
import unittest2 as unittest
from yamllint import cli from yamllint import cli
from yamllint import config from yamllint import config
from tests.common import build_temp_workspace
class SimpleConfigTestCase(unittest.TestCase): class SimpleConfigTestCase(unittest.TestCase):
def test_parse_config(self): def test_parse_config(self):
@@ -58,13 +55,16 @@ class SimpleConfigTestCase(unittest.TestCase):
' this-one-does-not-exist: enable\n') ' this-one-does-not-exist: enable\n')
def test_missing_option(self): def test_missing_option(self):
with self.assertRaisesRegexp( c = config.YamlLintConfig('rules:\n'
config.YamlLintConfigError, ' colons: enable\n')
'invalid config: missing option "max-spaces-before" ' self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
'for rule "colons"'): self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
config.YamlLintConfig('rules:\n'
c = config.YamlLintConfig('rules:\n'
' colons:\n' ' colons:\n'
' max-spaces-after: 1\n') ' max-spaces-before: 9\n')
self.assertEqual(c.rules['colons']['max-spaces-before'], 9)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
def test_unknown_option(self): def test_unknown_option(self):
with self.assertRaisesRegexp( with self.assertRaisesRegexp(
@@ -82,7 +82,7 @@ class SimpleConfigTestCase(unittest.TestCase):
' spaces: 2\n' ' spaces: 2\n'
' indent-sequences: true\n' ' indent-sequences: true\n'
' check-multi-line-strings: false\n') ' check-multi-line-strings: false\n')
self.assertEqual(c.rules['indentation']['indent-sequences'], True) self.assertTrue(c.rules['indentation']['indent-sequences'])
self.assertEqual(c.rules['indentation']['check-multi-line-strings'], self.assertEqual(c.rules['indentation']['check-multi-line-strings'],
False) False)
@@ -91,7 +91,7 @@ class SimpleConfigTestCase(unittest.TestCase):
' spaces: 2\n' ' spaces: 2\n'
' indent-sequences: yes\n' ' indent-sequences: yes\n'
' check-multi-line-strings: false\n') ' check-multi-line-strings: false\n')
self.assertEqual(c.rules['indentation']['indent-sequences'], True) self.assertTrue(c.rules['indentation']['indent-sequences'])
self.assertEqual(c.rules['indentation']['check-multi-line-strings'], self.assertEqual(c.rules['indentation']['check-multi-line-strings'],
False) False)
@@ -115,17 +115,22 @@ class SimpleConfigTestCase(unittest.TestCase):
' indent-sequences: YES!\n' ' indent-sequences: YES!\n'
' check-multi-line-strings: false\n') ' check-multi-line-strings: false\n')
def test_enable_disable_keywords(self):
c = config.YamlLintConfig('rules:\n'
' colons: enable\n'
' hyphens: disable\n')
self.assertEqual(c.rules['colons'], {'level': 'error',
'max-spaces-after': 1,
'max-spaces-before': 0})
self.assertEqual(c.rules['hyphens'], False)
def test_validate_rule_conf(self): def test_validate_rule_conf(self):
class Rule(object): class Rule(object):
ID = 'fake' ID = 'fake'
self.assertEqual(config.validate_rule_conf(Rule, False), False) self.assertFalse(config.validate_rule_conf(Rule, False))
self.assertEqual(config.validate_rule_conf(Rule, 'disable'), False)
self.assertEqual(config.validate_rule_conf(Rule, {}), self.assertEqual(config.validate_rule_conf(Rule, {}),
{'level': 'error'}) {'level': 'error'})
self.assertEqual(config.validate_rule_conf(Rule, 'enable'),
{'level': 'error'})
config.validate_rule_conf(Rule, {'level': 'error'}) config.validate_rule_conf(Rule, {'level': 'error'})
config.validate_rule_conf(Rule, {'level': 'warning'}) config.validate_rule_conf(Rule, {'level': 'warning'})
@@ -133,22 +138,22 @@ class SimpleConfigTestCase(unittest.TestCase):
config.validate_rule_conf, Rule, {'level': 'warn'}) config.validate_rule_conf, Rule, {'level': 'warn'})
Rule.CONF = {'length': int} Rule.CONF = {'length': int}
Rule.DEFAULT = {'length': 80}
config.validate_rule_conf(Rule, {'length': 8}) config.validate_rule_conf(Rule, {'length': 8})
self.assertRaises(config.YamlLintConfigError, config.validate_rule_conf(Rule, {})
config.validate_rule_conf, Rule, {})
self.assertRaises(config.YamlLintConfigError, self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'height': 8}) config.validate_rule_conf, Rule, {'height': 8})
Rule.CONF = {'a': bool, 'b': int} Rule.CONF = {'a': bool, 'b': int}
Rule.DEFAULT = {'a': True, 'b': -42}
config.validate_rule_conf(Rule, {'a': True, 'b': 0}) config.validate_rule_conf(Rule, {'a': True, 'b': 0})
self.assertRaises(config.YamlLintConfigError, config.validate_rule_conf(Rule, {'a': True})
config.validate_rule_conf, Rule, {'a': True}) config.validate_rule_conf(Rule, {'b': 0})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'b': 0})
self.assertRaises(config.YamlLintConfigError, self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'a': 1, 'b': 0}) config.validate_rule_conf, Rule, {'a': 1, 'b': 0})
Rule.CONF = {'choice': (True, 88, 'str')} Rule.CONF = {'choice': (True, 88, 'str')}
Rule.DEFAULT = {'choice': 88}
config.validate_rule_conf(Rule, {'choice': True}) config.validate_rule_conf(Rule, {'choice': True})
config.validate_rule_conf(Rule, {'choice': 88}) config.validate_rule_conf(Rule, {'choice': 88})
config.validate_rule_conf(Rule, {'choice': 'str'}) config.validate_rule_conf(Rule, {'choice': 'str'})
@@ -160,8 +165,10 @@ class SimpleConfigTestCase(unittest.TestCase):
config.validate_rule_conf, Rule, {'choice': 'abc'}) config.validate_rule_conf, Rule, {'choice': 'abc'})
Rule.CONF = {'choice': (int, 'hardcoded')} Rule.CONF = {'choice': (int, 'hardcoded')}
Rule.DEFAULT = {'choice': 1337}
config.validate_rule_conf(Rule, {'choice': 42}) config.validate_rule_conf(Rule, {'choice': 42})
config.validate_rule_conf(Rule, {'choice': 'hardcoded'}) config.validate_rule_conf(Rule, {'choice': 'hardcoded'})
config.validate_rule_conf(Rule, {})
self.assertRaises(config.YamlLintConfigError, self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': False}) config.validate_rule_conf, Rule, {'choice': False})
self.assertRaises(config.YamlLintConfigError, self.assertRaises(config.YamlLintConfigError,
@@ -169,7 +176,7 @@ class SimpleConfigTestCase(unittest.TestCase):
class ExtendedConfigTestCase(unittest.TestCase): class ExtendedConfigTestCase(unittest.TestCase):
def test_extend_add_rule(self): def test_extend_on_object(self):
old = config.YamlLintConfig('rules:\n' old = config.YamlLintConfig('rules:\n'
' colons:\n' ' colons:\n'
' max-spaces-before: 0\n' ' max-spaces-before: 0\n'
@@ -186,60 +193,130 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(len(new.enabled_rules(None)), 2) self.assertEqual(len(new.enabled_rules(None)), 2)
def test_extend_on_file(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' hyphens:\n'
' max-spaces-after: 2\n')
self.assertEqual(sorted(c.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
self.assertEqual(c.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(c.enabled_rules(None)), 2)
def test_extend_remove_rule(self): def test_extend_remove_rule(self):
old = config.YamlLintConfig('rules:\n' with tempfile.NamedTemporaryFile('w') as f:
' colons:\n' f.write('rules:\n'
' max-spaces-before: 0\n' ' colons:\n'
' max-spaces-after: 1\n' ' max-spaces-before: 0\n'
' hyphens:\n' ' max-spaces-after: 1\n'
' max-spaces-after: 2\n') ' hyphens:\n'
new = config.YamlLintConfig('rules:\n' ' max-spaces-after: 2\n')
' colons: disable\n') f.flush()
new.extend(old) c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' colons: disable\n')
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens']) self.assertEqual(sorted(c.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons'], False) self.assertFalse(c.rules['colons'])
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2) self.assertEqual(c.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules(None)), 1) self.assertEqual(len(c.enabled_rules(None)), 1)
def test_extend_edit_rule(self): def test_extend_edit_rule(self):
old = config.YamlLintConfig('rules:\n' with tempfile.NamedTemporaryFile('w') as f:
' colons:\n' f.write('rules:\n'
' max-spaces-before: 0\n' ' colons:\n'
' max-spaces-after: 1\n' ' max-spaces-before: 0\n'
' hyphens:\n' ' max-spaces-after: 1\n'
' max-spaces-after: 2\n') ' hyphens:\n'
new = config.YamlLintConfig('rules:\n' ' max-spaces-after: 2\n')
' colons:\n' f.flush()
' max-spaces-before: 3\n' c = config.YamlLintConfig('extends: ' + f.name + '\n'
' max-spaces-after: 4\n') 'rules:\n'
new.extend(old) ' colons:\n'
' max-spaces-before: 3\n'
' max-spaces-after: 4\n')
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens']) self.assertEqual(sorted(c.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons']['max-spaces-before'], 3) self.assertEqual(c.rules['colons']['max-spaces-before'], 3)
self.assertEqual(new.rules['colons']['max-spaces-after'], 4) self.assertEqual(c.rules['colons']['max-spaces-after'], 4)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2) self.assertEqual(c.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules(None)), 2) self.assertEqual(len(c.enabled_rules(None)), 2)
def test_extend_reenable_rule(self): def test_extend_reenable_rule(self):
old = config.YamlLintConfig('rules:\n' with tempfile.NamedTemporaryFile('w') as f:
' colons:\n' f.write('rules:\n'
' max-spaces-before: 0\n' ' colons:\n'
' max-spaces-after: 1\n' ' max-spaces-before: 0\n'
' hyphens: disable\n') ' max-spaces-after: 1\n'
new = config.YamlLintConfig('rules:\n' ' hyphens: disable\n')
' hyphens:\n' f.flush()
' max-spaces-after: 2\n') c = config.YamlLintConfig('extends: ' + f.name + '\n'
new.extend(old) 'rules:\n'
' hyphens:\n'
' max-spaces-after: 2\n')
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens']) self.assertEqual(sorted(c.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons']['max-spaces-before'], 0) self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
self.assertEqual(new.rules['colons']['max-spaces-after'], 1) self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2) self.assertEqual(c.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules(None)), 2) self.assertEqual(len(c.enabled_rules(None)), 2)
def test_extend_recursive_default_values(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('rules:\n'
' braces:\n'
' max-spaces-inside: 1248\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' braces:\n'
' min-spaces-inside-empty: 2357\n')
self.assertEqual(c.rules['braces']['min-spaces-inside'], 0)
self.assertEqual(c.rules['braces']['max-spaces-inside'], 1248)
self.assertEqual(c.rules['braces']['min-spaces-inside-empty'], 2357)
self.assertEqual(c.rules['braces']['max-spaces-inside-empty'], -1)
with tempfile.NamedTemporaryFile('w') as f:
f.write('rules:\n'
' colons:\n'
' max-spaces-before: 1337\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' colons: enable\n')
self.assertEqual(c.rules['colons']['max-spaces-before'], 1337)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
with tempfile.NamedTemporaryFile('w') as f1, \
tempfile.NamedTemporaryFile('w') as f2:
f1.write('rules:\n'
' colons:\n'
' max-spaces-before: 1337\n')
f1.flush()
f2.write('extends: ' + f1.name + '\n'
'rules:\n'
' colons: disable\n')
f2.flush()
c = config.YamlLintConfig('extends: ' + f2.name + '\n'
'rules:\n'
' colons: enable\n')
self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
class ExtendedLibraryConfigTestCase(unittest.TestCase): class ExtendedLibraryConfigTestCase(unittest.TestCase):
@@ -271,6 +348,9 @@ class ExtendedLibraryConfigTestCase(unittest.TestCase):
self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys())) self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys()))
for rule in new.rules: for rule in new.rules:
self.assertEqual(new.rules[rule], old.rules[rule]) self.assertEqual(new.rules[rule], old.rules[rule])
self.assertEqual(new.rules['empty-lines']['max'], 42)
self.assertEqual(new.rules['empty-lines']['max-start'], 43)
self.assertEqual(new.rules['empty-lines']['max-end'], 44)
def test_extend_config_override_rule_partly(self): def test_extend_config_override_rule_partly(self):
old = config.YamlLintConfig('extends: default') old = config.YamlLintConfig('extends: default')
@@ -284,6 +364,9 @@ class ExtendedLibraryConfigTestCase(unittest.TestCase):
self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys())) self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys()))
for rule in new.rules: for rule in new.rules:
self.assertEqual(new.rules[rule], old.rules[rule]) self.assertEqual(new.rules[rule], old.rules[rule])
self.assertEqual(new.rules['empty-lines']['max'], 2)
self.assertEqual(new.rules['empty-lines']['max-start'], 42)
self.assertEqual(new.rules['empty-lines']['max-end'], 0)
class IgnorePathConfigTestCase(unittest.TestCase): class IgnorePathConfigTestCase(unittest.TestCase):
@@ -338,7 +421,6 @@ class IgnorePathConfigTestCase(unittest.TestCase):
shutil.rmtree(cls.wd) shutil.rmtree(cls.wd)
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
def test_run_with_ignored_path(self): def test_run_with_ignored_path(self):
sys.stdout = StringIO() sys.stdout = StringIO()
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):

View File

@@ -15,12 +15,7 @@
# 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 io import io
import sys import unittest
try:
assert sys.version_info >= (2, 7)
import unittest
except:
import unittest2 as unittest
from yamllint.config import YamlLintConfig from yamllint.config import YamlLintConfig
from yamllint import linter from yamllint import linter

View File

@@ -19,14 +19,12 @@ import shutil
import subprocess import subprocess
import tempfile import tempfile
import sys import sys
try: import unittest
assert sys.version_info >= (2, 7)
import unittest
except: PYTHON = sys.executable or 'python'
import unittest2 as unittest
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
class ModuleTestCase(unittest.TestCase): class ModuleTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self.wd = tempfile.mkdtemp(prefix='yamllint-tests-') self.wd = tempfile.mkdtemp(prefix='yamllint-tests-')
@@ -46,7 +44,7 @@ class ModuleTestCase(unittest.TestCase):
def test_run_module_no_args(self): def test_run_module_no_args(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx: with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output(['python', '-m', 'yamllint'], subprocess.check_output([PYTHON, '-m', 'yamllint'],
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
self.assertEqual(ctx.exception.returncode, 2) self.assertEqual(ctx.exception.returncode, 2)
self.assertRegexpMatches(ctx.exception.output.decode(), self.assertRegexpMatches(ctx.exception.output.decode(),
@@ -54,7 +52,7 @@ class ModuleTestCase(unittest.TestCase):
def test_run_module_on_bad_dir(self): def test_run_module_on_bad_dir(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx: with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output(['python', '-m', 'yamllint', subprocess.check_output([PYTHON, '-m', 'yamllint',
'/does/not/exist'], '/does/not/exist'],
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
self.assertRegexpMatches(ctx.exception.output.decode(), self.assertRegexpMatches(ctx.exception.output.decode(),
@@ -62,7 +60,7 @@ class ModuleTestCase(unittest.TestCase):
def test_run_module_on_file(self): def test_run_module_on_file(self):
out = subprocess.check_output( 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() lines = out.decode().splitlines()
self.assertIn('/warn.yaml', lines[0]) self.assertIn('/warn.yaml', lines[0])
self.assertEqual('\n'.join(lines[1:]), self.assertEqual('\n'.join(lines[1:]),
@@ -71,7 +69,7 @@ class ModuleTestCase(unittest.TestCase):
def test_run_module_on_dir(self): def test_run_module_on_dir(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx: 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) self.assertEqual(ctx.exception.returncode, 1)
files = ctx.exception.output.decode().split('\n\n') files = ctx.exception.output.decode().split('\n\n')

View File

@@ -14,12 +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/>.
import sys import unittest
try:
assert sys.version_info >= (2, 7)
import unittest
except:
import unittest2 as unittest
import yaml import yaml
@@ -70,12 +65,12 @@ class ParserTestCase(unittest.TestCase):
def test_token_or_comment_generator(self): def test_token_or_comment_generator(self):
e = list(token_or_comment_generator('')) e = list(token_or_comment_generator(''))
self.assertEqual(len(e), 2) self.assertEqual(len(e), 2)
self.assertEqual(e[0].prev, None) self.assertIsNone(e[0].prev)
self.assertIsInstance(e[0].curr, yaml.Token) self.assertIsInstance(e[0].curr, yaml.Token)
self.assertIsInstance(e[0].next, yaml.Token) self.assertIsInstance(e[0].next, yaml.Token)
self.assertEqual(e[1].prev, e[0].curr) self.assertEqual(e[1].prev, e[0].curr)
self.assertEqual(e[1].curr, e[0].next) self.assertEqual(e[1].curr, e[0].next)
self.assertEqual(e[1].next, None) self.assertIsNone(e[1].next)
e = list(token_or_comment_generator('---\n' e = list(token_or_comment_generator('---\n'
'k: v\n')) 'k: v\n'))

View File

@@ -54,8 +54,8 @@ conf_general = ('document-start: disable\n'
'braces: {min-spaces-inside: 1, max-spaces-inside: 1}\n' 'braces: {min-spaces-inside: 1, max-spaces-inside: 1}\n'
'brackets: {min-spaces-inside: 1, max-spaces-inside: 1}\n') 'brackets: {min-spaces-inside: 1, max-spaces-inside: 1}\n')
conf_overrides = { conf_overrides = {
'example-2.2': ('colons: {max-spaces-after: 2}\n'), 'example-2.2': 'colons: {max-spaces-after: 2}\n',
'example-2.4': ('colons: {max-spaces-after: 3}\n'), 'example-2.4': 'colons: {max-spaces-after: 3}\n',
'example-2.5': ('empty-lines: {max-end: 2}\n' 'example-2.5': ('empty-lines: {max-end: 2}\n'
'brackets: {min-spaces-inside: 0, max-spaces-inside: 2}\n' 'brackets: {min-spaces-inside: 0, max-spaces-inside: 2}\n'
'commas: {max-spaces-before: -1}\n'), 'commas: {max-spaces-before: -1}\n'),
@@ -63,65 +63,65 @@ conf_overrides = {
'indentation: disable\n'), 'indentation: disable\n'),
'example-2.12': ('empty-lines: {max-end: 1}\n' 'example-2.12': ('empty-lines: {max-end: 1}\n'
'colons: {max-spaces-before: -1}\n'), 'colons: {max-spaces-before: -1}\n'),
'example-2.16': ('empty-lines: {max-end: 1}\n'), 'example-2.16': 'empty-lines: {max-end: 1}\n',
'example-2.18': ('empty-lines: {max-end: 1}\n'), 'example-2.18': 'empty-lines: {max-end: 1}\n',
'example-2.19': ('empty-lines: {max-end: 1}\n'), 'example-2.19': 'empty-lines: {max-end: 1}\n',
'example-2.28': ('empty-lines: {max-end: 3}\n'), 'example-2.28': 'empty-lines: {max-end: 3}\n',
'example-5.3': ('indentation: {indent-sequences: false}\n' 'example-5.3': ('indentation: {indent-sequences: false}\n'
'colons: {max-spaces-before: 1}\n'), 'colons: {max-spaces-before: 1}\n'),
'example-6.4': ('trailing-spaces: disable\n'), 'example-6.4': 'trailing-spaces: disable\n',
'example-6.5': ('trailing-spaces: disable\n'), 'example-6.5': 'trailing-spaces: disable\n',
'example-6.6': ('trailing-spaces: disable\n'), 'example-6.6': 'trailing-spaces: disable\n',
'example-6.7': ('trailing-spaces: disable\n'), 'example-6.7': 'trailing-spaces: disable\n',
'example-6.8': ('trailing-spaces: disable\n'), 'example-6.8': 'trailing-spaces: disable\n',
'example-6.10': ('empty-lines: {max-end: 2}\n' 'example-6.10': ('empty-lines: {max-end: 2}\n'
'trailing-spaces: disable\n' 'trailing-spaces: disable\n'
'comments-indentation: disable\n'), 'comments-indentation: disable\n'),
'example-6.11': ('empty-lines: {max-end: 1}\n' 'example-6.11': ('empty-lines: {max-end: 1}\n'
'comments-indentation: disable\n'), 'comments-indentation: disable\n'),
'example-6.13': ('comments-indentation: disable\n'), 'example-6.13': 'comments-indentation: disable\n',
'example-6.14': ('comments-indentation: disable\n'), 'example-6.14': 'comments-indentation: disable\n',
'example-6.23': ('colons: {max-spaces-before: 1}\n'), 'example-6.23': 'colons: {max-spaces-before: 1}\n',
'example-7.4': ('colons: {max-spaces-before: 1}\n' 'example-7.4': ('colons: {max-spaces-before: 1}\n'
'indentation: disable\n'), 'indentation: disable\n'),
'example-7.5': ('trailing-spaces: disable\n'), 'example-7.5': 'trailing-spaces: disable\n',
'example-7.6': ('trailing-spaces: disable\n'), 'example-7.6': 'trailing-spaces: disable\n',
'example-7.7': ('indentation: disable\n'), 'example-7.7': 'indentation: disable\n',
'example-7.8': ('colons: {max-spaces-before: 1}\n' 'example-7.8': ('colons: {max-spaces-before: 1}\n'
'indentation: disable\n'), 'indentation: disable\n'),
'example-7.9': ('trailing-spaces: disable\n'), 'example-7.9': 'trailing-spaces: disable\n',
'example-7.11': ('colons: {max-spaces-before: 1}\n' 'example-7.11': ('colons: {max-spaces-before: 1}\n'
'indentation: disable\n'), 'indentation: disable\n'),
'example-7.13': ('brackets: {min-spaces-inside: 0, max-spaces-inside: 1}\n' 'example-7.13': ('brackets: {min-spaces-inside: 0, max-spaces-inside: 1}\n'
'commas: {max-spaces-before: 1, min-spaces-after: 0}\n'), 'commas: {max-spaces-before: 1, min-spaces-after: 0}\n'),
'example-7.14': ('indentation: disable\n'), 'example-7.14': 'indentation: disable\n',
'example-7.15': ('braces: {min-spaces-inside: 0, max-spaces-inside: 1}\n' 'example-7.15': ('braces: {min-spaces-inside: 0, max-spaces-inside: 1}\n'
'commas: {max-spaces-before: 1, min-spaces-after: 0}\n' 'commas: {max-spaces-before: 1, min-spaces-after: 0}\n'
'colons: {max-spaces-before: 1}\n'), 'colons: {max-spaces-before: 1}\n'),
'example-7.16': ('indentation: disable\n'), 'example-7.16': 'indentation: disable\n',
'example-7.17': ('indentation: disable\n'), 'example-7.17': 'indentation: disable\n',
'example-7.18': ('indentation: disable\n'), 'example-7.18': 'indentation: disable\n',
'example-7.19': ('indentation: disable\n'), 'example-7.19': 'indentation: disable\n',
'example-7.20': ('colons: {max-spaces-before: 1}\n' 'example-7.20': ('colons: {max-spaces-before: 1}\n'
'indentation: disable\n'), 'indentation: disable\n'),
'example-8.1': ('empty-lines: {max-end: 1}\n'), 'example-8.1': 'empty-lines: {max-end: 1}\n',
'example-8.2': ('trailing-spaces: disable\n'), 'example-8.2': 'trailing-spaces: disable\n',
'example-8.5': ('comments-indentation: disable\n' 'example-8.5': ('comments-indentation: disable\n'
'trailing-spaces: disable\n'), 'trailing-spaces: disable\n'),
'example-8.6': ('empty-lines: {max-end: 1}\n'), 'example-8.6': 'empty-lines: {max-end: 1}\n',
'example-8.7': ('empty-lines: {max-end: 1}\n'), 'example-8.7': 'empty-lines: {max-end: 1}\n',
'example-8.8': ('trailing-spaces: disable\n'), 'example-8.8': 'trailing-spaces: disable\n',
'example-8.9': ('empty-lines: {max-end: 1}\n'), 'example-8.9': 'empty-lines: {max-end: 1}\n',
'example-8.14': ('colons: {max-spaces-before: 1}\n'), 'example-8.14': 'colons: {max-spaces-before: 1}\n',
'example-8.16': ('indentation: {spaces: 1}\n'), 'example-8.16': 'indentation: {spaces: 1}\n',
'example-8.17': ('indentation: disable\n'), 'example-8.17': 'indentation: disable\n',
'example-8.20': ('indentation: {indent-sequences: false}\n' 'example-8.20': ('indentation: {indent-sequences: false}\n'
'colons: {max-spaces-before: 1}\n'), 'colons: {max-spaces-before: 1}\n'),
'example-8.22': ('indentation: disable\n'), 'example-8.22': 'indentation: disable\n',
'example-10.1': ('colons: {max-spaces-before: 2}\n'), 'example-10.1': 'colons: {max-spaces-before: 2}\n',
'example-10.2': ('indentation: {indent-sequences: false}\n'), 'example-10.2': 'indentation: {indent-sequences: false}\n',
'example-10.8': ('truthy: disable\n'), 'example-10.8': 'truthy: disable\n',
'example-10.9': ('truthy: disable\n'), 'example-10.9': 'truthy: disable\n',
} }
files = os.listdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), files = os.listdir(os.path.join(os.path.dirname(os.path.realpath(__file__)),

View File

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

View File

@@ -16,9 +16,9 @@
from __future__ import print_function from __future__ import print_function
import os.path import os
import sys import sys
import platform
import argparse import argparse
from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION
@@ -38,6 +38,15 @@ def find_files_recursively(items):
yield item 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): class Format(object):
@staticmethod @staticmethod
def parsable(problem, filename): def parsable(problem, filename):
@@ -74,11 +83,41 @@ class Format(object):
return line return line
def show_problems(problems, file, args_format):
max_level = 0
first = True
for problem in problems:
if args_format == 'parsable':
print(Format.parsable(problem, file))
elif args_format == 'colored' or \
(args_format == 'auto' and supports_color()):
if first:
print('\033[4m%s\033[0m' % file)
first = False
print(Format.standard_color(problem, file))
else:
if first:
print(file)
first = False
print(Format.standard(problem, file))
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
if not first and args_format != 'parsable':
print('')
return max_level
def run(argv=None): def run(argv=None):
parser = argparse.ArgumentParser(prog=APP_NAME, parser = argparse.ArgumentParser(prog=APP_NAME,
description=APP_DESCRIPTION) description=APP_DESCRIPTION)
parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+', files_group = parser.add_mutually_exclusive_group(required=True)
help='files to check') files_group.add_argument('files', metavar='FILE_OR_DIR', nargs='*',
default=(),
help='files to check')
files_group.add_argument('-', action='store_true', dest='stdin',
help='read from standard input')
config_group = parser.add_mutually_exclusive_group() config_group = parser.add_mutually_exclusive_group()
config_group.add_argument('-c', '--config-file', dest='config_file', config_group.add_argument('-c', '--config-file', dest='config_file',
action='store', action='store',
@@ -87,16 +126,14 @@ 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'), default='standard', choices=('parsable', 'standard', 'colored', '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',
help='return non-zero exit code on warnings ' help='return non-zero exit code on warnings '
'as well as errors') 'as well as errors')
parser.add_argument('-v', '--version', action='version', parser.add_argument('-v', '--version', action='version',
version='%s %s' % (APP_NAME, APP_VERSION)) version='{} {}'.format(APP_NAME, APP_VERSION))
# TODO: read from stdin when no filename?
args = parser.parse_args(argv) args = parser.parse_args(argv)
@@ -129,31 +166,23 @@ def run(argv=None):
for file in find_files_recursively(args.files): for file in find_files_recursively(args.files):
filepath = file[2:] if file.startswith('./') else file filepath = file[2:] if file.startswith('./') else file
try: try:
first = True
with open(file) as f: with open(file) as f:
for problem in linter.run(f, conf, filepath): problems = linter.run(f, conf, filepath)
if args.format == 'parsable':
print(Format.parsable(problem, file))
elif sys.stdout.isatty():
if first:
print('\033[4m%s\033[0m' % file)
first = False
print(Format.standard_color(problem, file))
else:
if first:
print(file)
first = False
print(Format.standard(problem, file))
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
if not first and args.format != 'parsable':
print('')
except EnvironmentError as e: except EnvironmentError as e:
print(e, file=sys.stderr) print(e, file=sys.stderr)
sys.exit(-1) sys.exit(-1)
prob_level = show_problems(problems, file, args_format=args.format)
max_level = max(max_level, prob_level)
# read yaml from stdin
if args.stdin:
try:
problems = linter.run(sys.stdin, conf, '')
except EnvironmentError as e:
print(e, file=sys.stderr)
sys.exit(-1)
prob_level = show_problems(problems, 'stdin', args_format=args.format)
max_level = max(max_level, prob_level)
if max_level == PROBLEM_LEVELS['error']: if max_level == PROBLEM_LEVELS['error']:
return_code = 1 return_code = 1

View File

@@ -1,51 +1,28 @@
--- ---
rules: rules:
braces: braces: enable
min-spaces-inside: 0 brackets: enable
max-spaces-inside: 0 colons: enable
min-spaces-inside-empty: -1 commas: enable
max-spaces-inside-empty: -1
brackets:
min-spaces-inside: 0
max-spaces-inside: 0
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
colons:
max-spaces-before: 0
max-spaces-after: 1
commas:
max-spaces-before: 0
min-spaces-after: 1
max-spaces-after: 1
comments: comments:
level: warning level: warning
require-starting-space: true
min-spaces-from-content: 2
comments-indentation: comments-indentation:
level: warning level: warning
document-end: disable document-end: disable
document-start: document-start:
level: warning level: warning
present: true empty-lines: enable
empty-lines: empty-values: enable
max: 2 hyphens: enable
max-start: 0 indentation: enable
max-end: 0
hyphens:
max-spaces-after: 1
indentation:
spaces: consistent
indent-sequences: true
check-multi-line-strings: false
key-duplicates: enable key-duplicates: enable
line-length: key-ordering: disable
max: 80 line-length: enable
allow-non-breakable-words: true
allow-non-breakable-inline-mappings: false
new-line-at-end-of-file: enable new-line-at-end-of-file: enable
new-lines: new-lines: enable
type: unix octal-values: enable
quoted-strings: disable
trailing-spaces: enable trailing-spaces: enable
truthy: truthy:
level: warning level: warning

View File

@@ -52,7 +52,7 @@ class YamlLintConfig(object):
assert isinstance(base_config, YamlLintConfig) assert isinstance(base_config, YamlLintConfig)
for rule in self.rules: for rule in self.rules:
if (type(self.rules[rule]) == dict and if (isinstance(self.rules[rule], dict) and
rule in base_config.rules and rule in base_config.rules and
base_config.rules[rule] is not False): base_config.rules[rule] is not False):
base_config.rules[rule].update(self.rules[rule]) base_config.rules[rule].update(self.rules[rule])
@@ -70,10 +70,15 @@ class YamlLintConfig(object):
except Exception as e: except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e) raise YamlLintConfigError('invalid config: %s' % e)
if type(conf) != dict: if not isinstance(conf, dict):
raise YamlLintConfigError('invalid config: not a dict') raise YamlLintConfigError('invalid config: not a dict')
self.rules = conf.get('rules', {}) self.rules = conf.get('rules', {})
for rule in self.rules:
if self.rules[rule] == 'enable':
self.rules[rule] = {}
elif self.rules[rule] == 'disable':
self.rules[rule] = False
# Does this conf override another conf that we need to load? # Does this conf override another conf that we need to load?
if 'extends' in conf: if 'extends' in conf:
@@ -85,9 +90,9 @@ class YamlLintConfig(object):
raise YamlLintConfigError('invalid config: %s' % e) raise YamlLintConfigError('invalid config: %s' % e)
if 'ignore' in conf: if 'ignore' in conf:
if type(conf['ignore']) != str: if not isinstance(conf['ignore'], str):
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: ignore should be a list of patterns') 'invalid config: ignore should contain file patterns')
self.ignore = pathspec.PathSpec.from_lines( self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines()) 'gitwildmatch', conf['ignore'].splitlines())
@@ -102,17 +107,15 @@ class YamlLintConfig(object):
def validate_rule_conf(rule, conf): def validate_rule_conf(rule, conf):
if conf is False or conf == 'disable': if conf is False: # disable
return False return False
elif conf == 'enable':
conf = {}
if type(conf) == dict: if isinstance(conf, dict):
if ('ignore' in conf and if ('ignore' in conf and
type(conf['ignore']) != pathspec.pathspec.PathSpec): not isinstance(conf['ignore'], pathspec.pathspec.PathSpec)):
if type(conf['ignore']) != str: if not isinstance(conf['ignore'], str):
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: ignore should be a list of patterns') 'invalid config: ignore should contain file patterns')
conf['ignore'] = pathspec.PathSpec.from_lines( conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines()) 'gitwildmatch', conf['ignore'].splitlines())
@@ -123,6 +126,7 @@ def validate_rule_conf(rule, conf):
'invalid config: level should be "error" or "warning"') 'invalid config: level should be "error" or "warning"')
options = getattr(rule, 'CONF', {}) options = getattr(rule, 'CONF', {})
options_default = getattr(rule, 'DEFAULT', {})
for optkey in conf: for optkey in conf:
if optkey in ('ignore', 'level'): if optkey in ('ignore', 'level'):
continue continue
@@ -130,22 +134,20 @@ def validate_rule_conf(rule, conf):
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: unknown option "%s" for rule "%s"' % 'invalid config: unknown option "%s" for rule "%s"' %
(optkey, rule.ID)) (optkey, rule.ID))
if type(options[optkey]) == tuple: if isinstance(options[optkey], tuple):
if (conf[optkey] not in options[optkey] and if (conf[optkey] not in options[optkey] and
type(conf[optkey]) not in options[optkey]): type(conf[optkey]) not in options[optkey]):
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be in %s' 'invalid config: option "%s" of "%s" should be in %s'
% (optkey, rule.ID, options[optkey])) % (optkey, rule.ID, options[optkey]))
else: else:
if type(conf[optkey]) != options[optkey]: if not isinstance(conf[optkey], options[optkey]):
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be %s' 'invalid config: option "%s" of "%s" should be %s'
% (optkey, rule.ID, options[optkey].__name__)) % (optkey, rule.ID, options[optkey].__name__))
for optkey in options: for optkey in options:
if optkey not in conf: if optkey not in conf:
raise YamlLintConfigError( conf[optkey] = options_default[optkey]
'invalid config: missing option "%s" for rule "%s"' %
(optkey, rule.ID))
else: else:
raise YamlLintConfigError(('invalid config: rule "%s": should be ' raise YamlLintConfigError(('invalid config: rule "%s": should be '
'either "enable", "disable" or a dict') 'either "enable", "disable" or a dict')

View File

@@ -47,7 +47,7 @@ class LintProblem(object):
@property @property
def message(self): def message(self):
if self.rule is not None: if self.rule is not None:
return '%s (%s)' % (self.desc, self.rule) return '{} ({})'.format(self.desc, self.rule)
return self.desc return self.desc
def __eq__(self, other): def __eq__(self, other):
@@ -75,10 +75,10 @@ def get_cosmetic_problems(buffer, conf, filepath):
for rule in token_rules: for rule in token_rules:
context[rule.ID] = {} context[rule.ID] = {}
class DisableDirective(): class DisableDirective:
def __init__(self): def __init__(self):
self.rules = set() self.rules = set()
self.all_rules = set([r.ID for r in rules]) self.all_rules = {r.ID for r in rules}
def process_comment(self, comment): def process_comment(self, comment):
try: try:
@@ -226,7 +226,7 @@ def run(input, conf, filepath=None):
if conf.is_file_ignored(filepath): if conf.is_file_ignored(filepath):
return () return ()
if type(input) in (type(b''), type(u'')): # compat with Python 2 & 3 if isinstance(input, (type(b''), type(u''))): # compat with Python 2 & 3
return _run(input, conf, filepath) return _run(input, conf, filepath)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
# We need to have everything in memory to parse correctly # We need to have everything in memory to parse correctly

View File

@@ -125,7 +125,8 @@ def token_or_comment_generator(buffer):
curr = yaml_loader.get_token() curr = yaml_loader.get_token()
while curr is not None: while curr is not None:
next = yaml_loader.get_token() next = yaml_loader.get_token()
nextnext = yaml_loader.peek_token() nextnext = (yaml_loader.peek_token()
if yaml_loader.check_token() else None)
yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext) yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext)

View File

@@ -24,12 +24,16 @@ from yamllint.rules import (
document_end, document_end,
document_start, document_start,
empty_lines, empty_lines,
empty_values,
hyphens, hyphens,
indentation, indentation,
key_duplicates, key_duplicates,
key_ordering,
line_length, line_length,
new_line_at_end_of_file, new_line_at_end_of_file,
new_lines, new_lines,
octal_values,
quoted_strings,
trailing_spaces, trailing_spaces,
truthy, truthy,
) )
@@ -44,12 +48,16 @@ _RULES = {
document_end.ID: document_end, document_end.ID: document_end,
document_start.ID: document_start, document_start.ID: document_start,
empty_lines.ID: empty_lines, empty_lines.ID: empty_lines,
empty_values.ID: empty_values,
hyphens.ID: hyphens, hyphens.ID: hyphens,
indentation.ID: indentation, indentation.ID: indentation,
key_duplicates.ID: key_duplicates, key_duplicates.ID: key_duplicates,
key_ordering.ID: key_ordering,
line_length.ID: line_length, line_length.ID: line_length,
new_line_at_end_of_file.ID: new_line_at_end_of_file, new_line_at_end_of_file.ID: new_line_at_end_of_file,
new_lines.ID: new_lines, new_lines.ID: new_lines,
octal_values.ID: octal_values,
quoted_strings.ID: quoted_strings,
trailing_spaces.ID: trailing_spaces, trailing_spaces.ID: trailing_spaces,
truthy.ID: truthy, truthy.ID: truthy,
} }

View File

@@ -101,6 +101,10 @@ CONF = {'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,
'max-spaces-inside': 0,
'min-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):

View File

@@ -102,6 +102,10 @@ CONF = {'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,
'max-spaces-inside': 0,
'min-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):

View File

@@ -79,6 +79,8 @@ ID = 'colons'
TYPE = 'token' TYPE = 'token'
CONF = {'max-spaces-before': int, CONF = {'max-spaces-before': int,
'max-spaces-after': int} 'max-spaces-after': int}
DEFAULT = {'max-spaces-before': 0,
'max-spaces-after': 1}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):

View File

@@ -103,6 +103,9 @@ TYPE = 'token'
CONF = {'max-spaces-before': int, CONF = {'max-spaces-before': int,
'min-spaces-after': int, 'min-spaces-after': int,
'max-spaces-after': int} 'max-spaces-after': int}
DEFAULT = {'max-spaces-before': 0,
'min-spaces-after': 1,
'max-spaces-after': 1}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):

View File

@@ -21,6 +21,9 @@ Use this rule to control the position and formatting of comments.
* Use ``require-starting-space`` to require a space character right after the * Use ``require-starting-space`` to require a space character right after the
``#``. Set to ``true`` to enable, ``false`` to disable. ``#``. Set to ``true`` to enable, ``false`` to disable.
* Use ``ignore-shebangs`` to ignore a
`shebang <https://en.wikipedia.org/wiki/Shebang_(Unix)>`_ at the beginning of
the file when ``require-starting-space`` is set.
* ``min-spaces-from-content`` is used to visually separate inline comments from * ``min-spaces-from-content`` is used to visually separate inline comments from
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.
@@ -61,13 +64,19 @@ Use this rule to control the position and formatting of comments.
""" """
import re
from yamllint.linter import LintProblem from yamllint.linter import LintProblem
ID = 'comments' ID = 'comments'
TYPE = 'comment' TYPE = 'comment'
CONF = {'require-starting-space': bool, CONF = {'require-starting-space': bool,
'ignore-shebangs': bool,
'min-spaces-from-content': int} 'min-spaces-from-content': int}
DEFAULT = {'require-starting-space': True,
'ignore-shebangs': True,
'min-spaces-from-content': 2}
def check(conf, comment): def check(conf, comment):
@@ -82,8 +91,14 @@ def check(conf, comment):
while (comment.buffer[text_start] == '#' and while (comment.buffer[text_start] == '#' and
text_start < len(comment.buffer)): text_start < len(comment.buffer)):
text_start += 1 text_start += 1
if (text_start < len(comment.buffer) and if text_start < len(comment.buffer):
comment.buffer[text_start] not in (' ', '\n', '\0')): if (conf['ignore-shebangs'] and
yield LintProblem(comment.line_no, comment.line_no == 1 and
comment.column_no + text_start - comment.pointer, comment.column_no == 1 and
'missing starting space in comment') re.match(r'^!\S', comment.buffer[text_start:])):
return
elif comment.buffer[text_start] not in (' ', '\n', '\0'):
column = comment.column_no + text_start - comment.pointer
yield LintProblem(comment.line_no,
column,
'missing starting space in comment')

View File

@@ -82,18 +82,21 @@ from yamllint.linter import LintProblem
ID = 'document-end' ID = 'document-end'
TYPE = 'token' TYPE = 'token'
CONF = {'present': bool} CONF = {'present': bool}
DEFAULT = {'present': True}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):
if conf['present']: if conf['present']:
if (isinstance(token, yaml.StreamEndToken) and is_stream_end = isinstance(token, yaml.StreamEndToken)
not (isinstance(prev, yaml.DocumentEndToken) or is_start = isinstance(token, yaml.DocumentStartToken)
isinstance(prev, yaml.StreamStartToken))): prev_is_end_or_stream_start = isinstance(
prev, (yaml.DocumentEndToken, yaml.StreamStartToken)
)
if is_stream_end and not prev_is_end_or_stream_start:
yield LintProblem(token.start_mark.line, 1, yield LintProblem(token.start_mark.line, 1,
'missing document end "..."') 'missing document end "..."')
elif (isinstance(token, yaml.DocumentStartToken) and elif is_start and not prev_is_end_or_stream_start:
not (isinstance(prev, yaml.DocumentEndToken) or
isinstance(prev, yaml.StreamStartToken))):
yield LintProblem(token.start_mark.line + 1, 1, yield LintProblem(token.start_mark.line + 1, 1,
'missing document end "..."') 'missing document end "..."')

View File

@@ -72,6 +72,7 @@ from yamllint.linter import LintProblem
ID = 'document-start' ID = 'document-start'
TYPE = 'token' TYPE = 'token'
CONF = {'present': bool} CONF = {'present': bool}
DEFAULT = {'present': True}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):

View File

@@ -58,6 +58,9 @@ TYPE = 'line'
CONF = {'max': int, CONF = {'max': int,
'max-start': int, 'max-start': int,
'max-end': int} 'max-end': int}
DEFAULT = {'max': 2,
'max-start': 0,
'max-end': 0}
def check(conf, line): def check(conf, line):

View File

@@ -0,0 +1,96 @@
# -*- 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}
DEFAULT = {'forbid-in-block-mappings': False,
'forbid-in-flow-mappings': False}
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

@@ -76,6 +76,7 @@ from yamllint.rules.common import spaces_after
ID = 'hyphens' ID = 'hyphens'
TYPE = 'token' TYPE = 'token'
CONF = {'max-spaces-after': int} CONF = {'max-spaces-after': int}
DEFAULT = {'max-spaces-after': 1}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):

View File

@@ -201,6 +201,9 @@ TYPE = 'token'
CONF = {'spaces': (int, 'consistent'), CONF = {'spaces': (int, 'consistent'),
'indent-sequences': (bool, 'whatever', 'consistent'), 'indent-sequences': (bool, 'whatever', 'consistent'),
'check-multi-line-strings': bool} 'check-multi-line-strings': bool}
DEFAULT = {'spaces': 'consistent',
'indent-sequences': True,
'check-multi-line-strings': False}
ROOT, B_MAP, F_MAP, B_SEQ, F_SEQ, B_ENT, KEY, VAL = range(8) ROOT, B_MAP, F_MAP, B_SEQ, F_SEQ, B_ENT, KEY, VAL = range(8)
labels = ('ROOT', 'B_MAP', 'F_MAP', 'B_SEQ', 'F_SEQ', 'B_ENT', 'KEY', 'VAL') labels = ('ROOT', 'B_MAP', 'F_MAP', 'B_SEQ', 'F_SEQ', 'B_ENT', 'KEY', 'VAL')
@@ -224,7 +227,7 @@ def check_scalar_indentation(conf, token, context):
def compute_expected_indent(found_indent): def compute_expected_indent(found_indent):
def detect_indent(base_indent): def detect_indent(base_indent):
if type(context['spaces']) is not int: if not isinstance(context['spaces'], int):
context['spaces'] = found_indent - base_indent context['spaces'] = found_indent - base_indent
return base_indent + context['spaces'] return base_indent + context['spaces']
@@ -312,7 +315,7 @@ def _check(conf, token, prev, next, nextnext, context):
token.start_mark.line + 1 > context['cur_line']) token.start_mark.line + 1 > context['cur_line'])
def detect_indent(base_indent, next): def detect_indent(base_indent, next):
if type(context['spaces']) is not int: if not isinstance(context['spaces'], int):
context['spaces'] = next.start_mark.column - base_indent context['spaces'] = next.start_mark.column - base_indent
return base_indent + context['spaces'] return base_indent + context['spaces']
@@ -399,6 +402,10 @@ def _check(conf, token, prev, next, nextnext, context):
# - item 1 # - item 1
# - item 2 # - item 2
indent = next.start_mark.column indent = next.start_mark.column
elif next.start_mark.column == token.start_mark.column:
# -
# key: value
indent = next.start_mark.column
else: else:
# - # -
# item 1 # item 1

View File

@@ -61,7 +61,6 @@ from yamllint.linter import LintProblem
ID = 'key-duplicates' ID = 'key-duplicates'
TYPE = 'token' TYPE = 'token'
CONF = {}
MAP, SEQ = range(2) MAP, SEQ = range(2)
@@ -91,7 +90,9 @@ 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 next.value in context['stack'][-1].keys: if (next.value in context['stack'][-1].keys and
# `<<` is "merge key", see http://yaml.org/type/merge.html
next.value != '<<'):
yield LintProblem( yield LintProblem(
next.start_mark.line + 1, next.start_mark.column + 1, next.start_mark.line + 1, next.start_mark.column + 1,
'duplication of key "%s" in mapping' % next.value) 'duplication of key "%s" in mapping' % next.value)

View File

@@ -0,0 +1,109 @@
# -*- 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'
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)

View File

@@ -17,6 +17,10 @@
""" """
Use this rule to set a limit to lines length. Use this rule to set a limit to lines length.
Note: with Python 2, the ``line-length`` rule may not work properly with
unicode characters because of the way strings are represented in bytes. We
recommend running yamllint with Python 3.
.. rubric:: Options .. rubric:: Options
* ``max`` defines the maximal (inclusive) length of lines. * ``max`` defines the maximal (inclusive) length of lines.
@@ -98,6 +102,9 @@ TYPE = 'line'
CONF = {'max': int, CONF = {'max': int,
'allow-non-breakable-words': bool, 'allow-non-breakable-words': bool,
'allow-non-breakable-inline-mappings': bool} 'allow-non-breakable-inline-mappings': bool}
DEFAULT = {'max': 80,
'allow-non-breakable-words': True,
'allow-non-breakable-inline-mappings': False}
def check_inline_mapping(line): def check_inline_mapping(line):

View File

@@ -30,6 +30,7 @@ from yamllint.linter import LintProblem
ID = 'new-lines' ID = 'new-lines'
TYPE = 'line' TYPE = 'line'
CONF = {'type': ('unix', 'dos')} CONF = {'type': ('unix', 'dos')}
DEFAULT = {'type': 'unix'}
def check(conf, line): def check(conf, line):

View File

@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 ScienJus
#
# 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 values with octal numbers. In YAML, numbers that
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
converted to ``8``.
.. rubric:: Examples
#. With ``octal-values: {forbid-implicit-octal: true}``
the following code snippets would **PASS**:
::
user:
city-code: '010'
the following code snippets would **PASS**:
::
user:
city-code: 010,021
the following code snippets would **FAIL**:
::
user:
city-code: 010
#. With ``octal-values: {forbid-explicit-octal: true}``
the following code snippets would **PASS**:
::
user:
city-code: '0o10'
the following code snippets would **FAIL**:
::
user:
city-code: 0o10
"""
import yaml
from yamllint.linter import LintProblem
ID = 'octal-values'
TYPE = 'token'
CONF = {'forbid-implicit-octal': bool,
'forbid-explicit-octal': bool}
DEFAULT = {'forbid-implicit-octal': False,
'forbid-explicit-octal': False}
def check(conf, token, prev, next, nextnext, context):
if prev and isinstance(prev, yaml.tokens.TagToken):
return
if conf['forbid-implicit-octal']:
if isinstance(token, yaml.tokens.ScalarToken):
if not token.style:
val = token.value
if val.isdigit() and len(val) > 1 and val[0] == '0':
yield LintProblem(
token.start_mark.line + 1, token.end_mark.column + 1,
'forbidden implicit octal value "%s"' %
token.value)
if conf['forbid-explicit-octal']:
if isinstance(token, yaml.tokens.ScalarToken):
if not token.style:
val = token.value
if len(val) > 2 and val[:2] == '0o' and val[2:].isdigit():
yield LintProblem(
token.start_mark.line + 1, token.end_mark.column + 1,
'forbidden explicit octal value "%s"' %
token.value)

View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 ClearScore
#
# 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 forbid any string values that are not quoted.
You can also enforce the type of the quote used using the ``quote-type`` option
(``single``, ``double`` or ``any``).
**Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
.. rubric:: Examples
#. With ``quoted-strings: {quote-type: any}``
the following code snippet would **PASS**:
::
foo: "bar"
bar: 'foo'
number: 123
boolean: true
the following code snippet would **FAIL**:
::
foo: bar
"""
import yaml
from yamllint.linter import LintProblem
ID = 'quoted-strings'
TYPE = 'token'
CONF = {'quote-type': ('any', 'single', 'double')}
DEFAULT = {'quote-type': 'any'}
def check(conf, token, prev, next, nextnext, context):
quote_type = conf['quote-type']
if (isinstance(token, yaml.tokens.ScalarToken) and
isinstance(prev, (yaml.ValueToken, yaml.TagToken))):
# Ignore explicit types, e.g. !!str testtest or !!int 42
if (prev and isinstance(prev, yaml.tokens.TagToken) and
prev.value[0] == '!!'):
return
# Ignore numbers, booleans, etc.
resolver = yaml.resolver.Resolver()
if resolver.resolve(yaml.nodes.ScalarNode, token.value,
(True, False)) != 'tag:yaml.org,2002:str':
return
# Ignore multi-line strings
if (not token.plain) and (token.style == "|" or token.style == ">"):
return
if ((quote_type == 'single' and token.style != "'") or
(quote_type == 'double' and token.style != '"') or
(quote_type == 'any' and token.style is None)):
yield LintProblem(
token.start_mark.line + 1,
token.start_mark.column + 1,
"string value is not quoted with %s quotes" % (quote_type))

View File

@@ -15,11 +15,12 @@
# 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 forbid truthy values that are not quoted nor explicitly typed. Use this rule to forbid non-explictly typed truthy values other than ``true``
and ``false``, for example ``YES``, ``False`` and ``off``.
This would prevent YAML parsers from transforming ``[yes, FALSE, Off]`` into This can be useful to prevent surprises from YAML parsers transforming
``[true, false, false]`` or ``{y: 1, yes: 2, on: 3, true: 4, True: 5}`` into ``[yes, FALSE, Off]`` into ``[true, false, false]`` or
``{y: 1, true: 5}``. ``{y: 1, yes: 2, on: 3, true: 4, True: 5}`` into ``{y: 1, true: 5}``.
.. rubric:: Examples .. rubric:: Examples
@@ -34,8 +35,7 @@ This would prevent YAML parsers from transforming ``[yes, FALSE, Off]`` into
"yes": 1 "yes": 1
"on": 2 "on": 2
"true": 3 "True": 3
"True": 4
explicit: explicit:
string1: !!str True string1: !!str True
@@ -62,8 +62,7 @@ This would prevent YAML parsers from transforming ``[yes, FALSE, Off]`` into
yes: 1 yes: 1
on: 2 on: 2
true: 3 True: 3
True: 4
""" """
import yaml import yaml
@@ -72,7 +71,6 @@ from yamllint.linter import LintProblem
ID = 'truthy' ID = 'truthy'
TYPE = 'token' TYPE = 'token'
CONF = {}
TRUTHY = ['YES', 'Yes', 'yes', TRUTHY = ['YES', 'Yes', 'yes',
'NO', 'No', 'no', 'NO', 'No', 'no',
@@ -90,4 +88,4 @@ def check(conf, token, prev, next, nextnext, context):
if token.value in TRUTHY and token.style is None: if token.value in TRUTHY and token.style is None:
yield LintProblem(token.start_mark.line + 1, yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1, token.start_mark.column + 1,
"truthy value is not quoted") "truthy value should be true or false")