Compare commits

...

57 Commits

Author SHA1 Message Date
Adrien Vergé
8e6e851c5b yamllint version 1.2.2 2016-06-24 08:40:45 +02:00
Adrien Vergé
edd4cca02f Merge pull request #9 from michelebariani/master
Patch allow-non-breakable-words on '-'
2016-06-15 20:06:04 +02:00
Michele Bariani
867970258e Patch allow-non-breakable-words on '-' 2016-06-15 18:07:42 +02:00
Adrien Vergé
d0cb5998c4 Merge pull request #7 from jwilk/spelling
Fix typos
2016-05-13 16:19:58 +02:00
Jakub Wilk
a5c97220e7 Fix typos 2016-05-13 15:47:56 +02:00
Adrien Vergé
598e5e4370 Doc: Fix typo on configuration page intro 2016-04-21 22:39:46 +02:00
Adrien Vergé
03076ee214 Doc: Add a pointer to rules on configuration page intro 2016-04-21 22:37:48 +02:00
Adrien Vergé
eabd349902 Config: Allow a user-global configuration file
Instead of just looking for `.yamllint` in the current working
directory, also look for `~/.config/yamllint/config` (using
`$XDG_CONFIG_HOME` or `$HOME`, see [1] and [2] for information).

[1]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.6.html
[2]: https://wiki.archlinux.org/index.php/XDG_Base_Directory_support

Closes: #6
2016-04-21 22:24:24 +02:00
Adrien Vergé
1f1757ced4 yamllint version 1.2.1 2016-03-25 13:55:19 +01:00
Adrien Vergé
59d5bffbec Tests: cli: Detect and handle the -d '' case 2016-03-25 13:46:19 +01:00
Adrien Vergé
53da21934d Tests: Add many cli.run test cases 2016-03-25 13:46:19 +01:00
Adrien Vergé
0c36d0175c cli: Print EnvironmentErrors on stderr
Errors such as "no such file or directory" should not be printed on
standard output.
2016-03-25 13:46:19 +01:00
Adrien Vergé
20545febe5 CI: Lint project's *.yaml files as well as *.yml 2016-03-25 13:46:19 +01:00
Adrien Vergé
88ebcbbb93 Tests: Test cli.find_files_recursively 2016-03-25 13:46:19 +01:00
Adrien Vergé
08615ec4f8 Tests: config: Check the non-valid-yaml-config case 2016-03-25 13:46:19 +01:00
Adrien Vergé
29aceb430a Tests: indentation: Increase coverage 2016-03-25 13:46:19 +01:00
Adrien Vergé
159e29ea6a Rules: indentation: Remove non-existing case
A BlockMappingStartToken should always be followed by a KeyToken, on the
same line.
2016-03-25 13:46:19 +01:00
Adrien Vergé
f9198b7a9b Rules: indentation: Fix B_SEQ instead of B_ENT
For example in this case, the scalar's parent is a B_ENT (only its
grandparent is a B_SEQ):

    - >
        multi
        line
2016-03-25 13:46:19 +01:00
Adrien Vergé
44236077dd Merge pull request #4 from adrienverge/indentation-imbricated-flows
Indentation: imbricated flows
2016-03-25 13:45:05 +01:00
Adrien Vergé
76f47e91ca Rules: indentation: Handle imbricated flows correctly
The following source -- although not loadable by pyyaml -- is valid
YAML:

    {{key}}: value

This was processed badly by yamllint. The same for `[[value]]`,
`{{{{{moustaches}}}}}` or:

    {[val,
      {{key: val,
        key2}}]}

This patch corrects it and add corresponding test cases.

Related-to: #3
2016-03-22 14:34:07 +01:00
Adrien Vergé
f98bed1085 Rules: indentation: Do not crash on unexpected token
Previously, when the indentation rule blocked on an unexpected token,
the program crashed with something like:

    File "/usr/lib/python3/dist-packages/yamllint/rules/indentation.py",
    line 434, in check
      assert context['stack'][-1].type == KEY
    AssertionError

Instead, we prefer report the error as a regular `LintProblem` and
continue processing.

Fixes: #3
2016-03-22 14:34:07 +01:00
Adrien Vergé
a483524b63 Doc: Update installing section
Packages are now also available in Debian and Ubuntu.
2016-03-15 10:04:24 +01:00
Adrien Vergé
3a017a5a22 Doc: Update Neovim integration documentation
Since it has been merged into Neomake:
https://github.com/benekastah/neomake/commit/45dfc5
https://github.com/benekastah/neomake/pull/289
2016-03-10 08:59:31 +01:00
Adrien Vergé
bab8137e2b Update .gitignore 2016-03-08 09:48:17 +01:00
Adrien Vergé
41733fc7a5 Use '.yaml' extension as default, not '.yml'
As someone said [1] on the internet:

    Say ".yaml" not ".yml".
    This is not MS-DOS, and YML is a Yahoo XML dialect.

Similarly, we use '.json', not '.jsn'.

[1]: https://github.com/ceph/s3-tests/commit/e17c56a
2016-03-07 11:15:04 +01:00
Adrien Vergé
688858e639 Doc: Reference Fedora and Ubuntu packages 2016-03-07 11:05:29 +01:00
Adrien Vergé
dca3a54e63 yamllint version 1.2.0 2016-03-06 17:04:05 +01:00
Adrien Vergé
2dcfbd7e0d Conf: relaxed: Remove unneeded lines 2016-03-06 17:04:05 +01:00
Adrien Vergé
73d7a608e8 Conf: relaxed: Re-enable hyphens (in warning) 2016-03-06 17:04:05 +01:00
Adrien Vergé
1c0f164fbf Conf: relaxed: Set indentation's indent-sequences=consistent 2016-03-06 17:01:18 +01:00
Adrien Vergé
46e9108419 Rules: indentation: Add 'consistent' option for 'indent-sequences'
Using `indent-sequences: consistent` allows block sequences to be
indented or not to be, as long as it remains the same within the file.
2016-03-06 15:42:16 +01:00
Adrien Vergé
2f9e3cc71b Conf: relaxed: Set indentation to warning level 2016-03-06 08:26:09 +01:00
Adrien Vergé
b13a03815a Conf: default: Use spaces: consistent for indentation 2016-03-06 08:26:09 +01:00
Adrien Vergé
9a7eec34b1 Rules: indentation: Fix spaces: consitent with broken flows 2016-03-06 08:26:09 +01:00
Adrien Vergé
5b62548ece Tests: indentation: Use 'spaces: consistent' by default 2016-03-06 08:26:09 +01:00
Adrien Vergé
8fca8a7a33 Config: Allow 'enable' keyword for rules
In the same manner as 'disable', 'enable' allows setting a rule on
without worrying about its options.
2016-03-06 08:00:25 +01:00
Adrien Vergé
69ef9a7272 Conf: relaxed: Set max line-length back to 80
Because 80 has been the default for years. But keep it as a warning, not
an error.
2016-03-06 07:42:49 +01:00
Adrien Vergé
d8d1d92545 yamllint version 1.1.0 2016-03-04 17:03:38 +01:00
Adrien Vergé
7688567faa cli: Add the -d option to provide inline conf 2016-03-04 16:53:26 +01:00
Adrien Vergé
4e188f8801 Conf: Add a new pre-defined conf 'relaxed'
It is more tolerant than 'default'.
2016-03-04 16:50:40 +01:00
Adrien Vergé
5693b1dddf Rules: indentation: Add 'consistent' option for 'spaces'
Using `spaces: consistent` allows any number of spaces, as long as it
remains the same within the file.
2016-03-04 16:03:53 +01:00
Adrien Vergé
fa420499c7 Config: Allow types in multiple choices
For instance, allow rules with:

    CONF = {'choice': (int, 'hardcoded-string'),
            'string-or-bool': (str, bool)}
2016-03-04 16:03:46 +01:00
Adrien Vergé
adefe38a0d yamllint version 1.0.4 2016-03-04 12:48:31 +01:00
Adrien Vergé
7e11082353 Distribution: Restore spec examples in package_data
Put `tests/yaml-1.2-spec-examples/*` back in `setup.py`'s `package_data`
because they need to be installed when running `python setup.py build`,
so Debian packaging script `dh_auto_test -O--buildsystem=pybuild`
doesn't fail.

See also commit e6dc67f.
2016-03-04 12:33:56 +01:00
Adrien Vergé
29c1c60143 Tests: Use absolute path to spec examples 2016-03-04 12:15:26 +01:00
Adrien Vergé
b879e9a98f Distribution: Add LICENSE and README to manifest 2016-02-26 09:57:06 +01:00
Adrien Vergé
5956b20545 yamllint version 1.0.3 2016-02-25 14:48:13 +01:00
Adrien Vergé
10ad302e2f Tests: Explicit encoding for spec examples
YAML specification examples contain unusual characters, let's explicit
`encoding='utf-8'` to prevent bugs.
2016-02-25 10:44:05 +01:00
Adrien Vergé
73d9322813 linter: Test run on str, unicode, bytes and stream
Previously it was not tested, and broke on Python 2 `unicode` inputs.
2016-02-25 10:41:17 +01:00
Adrien Vergé
ca0ebe4583 yamllint version 1.0.2 2016-02-24 21:21:02 +01:00
Adrien Vergé
e6dc67fd0a Distribution: Add MANIFEST.in
`yamllint/conf/*.yml` remains in `setup.py`'s `package_data` because it
needs to be installed when running `pip install .`.

`docs/*` and `tests/yaml-1.2-spec-examples/*` just need to be packaged,
they can go in the manifest.
2016-02-24 21:18:48 +01:00
Adrien Vergé
611a560082 yamllint version 1.0.1 2016-02-19 19:39:52 +01:00
Adrien Vergé
83384fa4cf Doc: Fix man page redundant description 2016-02-19 19:34:20 +01:00
Adrien Vergé
3ab3784a75 cli: Remove shebang
A shebang is present at the beginning of file, it dates from the time
when `yamllint/cli.py` was `bin/yamllint`, i.e. an executable launcher.
Since this is not the case anymore (see `entry_points` section in
`setup.py`), let's remove it.
2016-02-19 19:17:49 +01:00
Adrien Vergé
2f75e92a66 Doc: Add a configuration example in README 2016-02-19 10:37:52 +01:00
Adrien Vergé
64caa95b6a yamllint version 1.0.0 2016-02-19 10:15:23 +01:00
Adrien Vergé
fff09fa2df Distribution: Ship example files from spec in sdist
Closes: #1
2016-02-19 10:14:59 +01:00
28 changed files with 1239 additions and 174 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
__pycache__ __pycache__
*.py[cod] *.py[cod]
/docs/_build /docs/_build
/dist
/yamllint.egg-info

View File

@@ -11,7 +11,7 @@ install:
- pip install . - pip install .
script: script:
- flake8 . - flake8 .
- yamllint $(git ls-files '*.yml') - yamllint $(git ls-files '*.yaml' '*.yml')
- coverage run --source=yamllint setup.py test - coverage run --source=yamllint setup.py test
after_success: after_success:
coveralls coveralls

3
MANIFEST.in Normal file
View File

@@ -0,0 +1,3 @@
include LICENSE
include README.rst
include docs/*

View File

@@ -38,6 +38,27 @@ Screenshot
Installation Installation
^^^^^^^^^^^^ ^^^^^^^^^^^^
On Fedora / CentOS:
.. code:: bash
sudo dnf install yamllint
On Debian 9+ / Ubuntu 16.04+:
.. code:: bash
sudo apt-get install yamllint
On older Debian / Ubuntu versions:
.. code:: bash
sudo add-apt-repository -y ppa:adrienverge/ppa && sudo apt-get update
sudo apt-get install yamllint
Alternatively using pip, the Python package manager:
.. code:: bash .. code:: bash
sudo pip install yamllint sudo pip install yamllint
@@ -57,10 +78,29 @@ Usage
.. code:: bash .. code:: bash
# Use a pre-defined lint configuration
yamllint -d relaxed file.yaml
# Use a custom lint configuration # Use a custom lint configuration
yamllint -c ~/myconfig file.yml yamllint -c /path/to/myconfig file-to-lint.yaml
.. code:: bash .. code:: bash
# Output a parsable format (for syntax checking in editors like Vim, emacs...) # Output a parsable format (for syntax checking in editors like Vim, emacs...)
yamllint -f parsable file.yml yamllint -f parsable file.yaml
Configuration example
^^^^^^^^^^^^^^^^^^^^^
.. code:: yaml
extends: default
rules:
# 80 chars should be enough, but don't fail if a line is longer
line-length:
max: 80
level: warning
# don't bother me with this rule
indentation: disable

View File

@@ -38,6 +38,5 @@ htmlhelp_basename = 'yamllintdoc'
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
('index', 'yamllint', u'yamllint Documentation', ('index', 'yamllint', '', [u'Adrien Vergé'], 1)
[u'Adrien Vergé'], 1)
] ]

View File

@@ -1,27 +1,44 @@
Configuration Configuration
============= =============
yamllint uses a set of *rules* to check sources files for problems. Each rule is yamllint uses a set of :doc:`rules <rules>` to check source files for problems.
independent from the others, and can be enabled, disabled or tweaked. All these Each rule is independent from the others, and can be enabled, disabled or
settings can be gathered in a configuration file. tweaked. All these settings can be gathered in a configuration file.
To use a custom configuration file, either name it ``.yamllint`` in your working To use a custom configuration file, use the ``-c`` option:
directory, or use the ``-c`` option:
:: .. code:: bash
yamllint -c ~/myconfig file.yml yamllint -c /path/to/myconfig file-to-lint.yaml
If ``-c`` is not provided, yamllint will look for a configuration file in the
following locations (by order of preference):
- ``.yamllint`` in the current working directory
- ``$XDG_CONFIG_HOME/yamllint/config``
- ``$HOME/.config/yamllint/config``
Finally if no config file is found, the default configuration is applied.
Default configuration Default configuration
--------------------- ---------------------
Unless told otherwise, yamllint uses its ``default`` configuration: Unless told otherwise, yamllint uses its ``default`` configuration:
.. literalinclude:: ../yamllint/conf/default.yml .. literalinclude:: ../yamllint/conf/default.yaml
:language: yaml :language: yaml
Details on rules can be found on :doc:`the rules page <rules>`. Details on rules can be found on :doc:`the rules page <rules>`.
There is another pre-defined configuration named ``relaxed``. As its name
suggests, it is more tolerant.
It can be chosen using:
.. code:: bash
yamllint -d relaxed file.yml
Extending the default configuration Extending the default configuration
----------------------------------- -----------------------------------
@@ -50,7 +67,7 @@ strict on block sequences indentation:
extends: default extends: default
rules: rules:
# 80 should be enough, but don't fail if a line is longer # 80 chars should be enough, but don't fail if a line is longer
line-length: line-length:
max: 80 max: 80
level: warning level: warning
@@ -63,6 +80,21 @@ strict on block sequences indentation:
indentation: indentation:
indent-sequences: whatever indent-sequences: whatever
Custom configuration without a config file
------------------------------------------
It is possible -- although not recommended -- to pass custom configuration
options to yamllint with the ``-d`` (short for ``--config-data``) option.
Its content can either be the name of a pre-defined conf (example: ``default``
or ``relaxed``) or a serialized YAML object describing the configuration.
For instance:
.. code:: bash
yamllint -d "{extends: relaxed, rules: {line-length: {max: 120}}}" file.yaml
Errors and warnings Errors and warnings
------------------- -------------------

View File

@@ -4,16 +4,34 @@ Quickstart
Installing yamllint Installing yamllint
------------------- -------------------
First, install yamllint. The easiest way is to use pip, the Python package On Fedora / CentOS:
manager:
:: .. code:: bash
sudo dnf install yamllint
On Debian 9+ / Ubuntu 16.04+:
.. code:: bash
sudo apt-get install yamllint
On older Debian / Ubuntu versions:
.. code:: bash
sudo add-apt-repository -y ppa:adrienverge/ppa && sudo apt-get update
sudo apt-get install yamllint
Alternatively using pip, the Python package manager:
.. code:: bash
sudo pip install yamllint sudo pip install 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
python setup.py sdist python setup.py sdist
sudo pip install dist/yamllint-*.tar.gz sudo pip install dist/yamllint-*.tar.gz
@@ -23,13 +41,13 @@ Running yamllint
Basic usage: Basic usage:
:: .. code:: bash
yamllint file.yml other-file.yaml yamllint file.yml other-file.yaml
You can also lint all YAML files in a whole directory: You can also lint all YAML files in a whole directory:
:: .. code:: bash
yamllint . yamllint .
@@ -65,9 +83,9 @@ If you have a custom linting configuration file (see :doc:`how to configure
yamllint <configuration>`), it can be passed to yamllint using the ``-c`` yamllint <configuration>`), it can be passed to yamllint using the ``-c``
option: option:
:: .. code:: bash
yamllint -c ~/myconfig file.yml yamllint -c ~/myconfig file.yaml
.. note:: .. note::

View File

@@ -20,16 +20,8 @@ Neovim
------ ------
Assuming that the `neomake <https://github.com/benekastah/neomake>`_ plugin is Assuming that the `neomake <https://github.com/benekastah/neomake>`_ plugin is
installed, add to your ``.config/nvim/init.vim``: installed, yamllint is supported by default. It is automatically enabled when
editing YAML files.
::
if executable('yamllint')
let g:neomake_yaml_yamllint_maker = {
\ 'args': ['-f', 'parsable'],
\ 'errorformat': '%E%f:%l:%c: [error] %m,%W%f:%l:%c: [warning] %m' }
let g:neomake_yaml_enabled_makers = ['yamllint']
endif
Other text editors Other text editors
------------------ ------------------

View File

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

View File

@@ -49,7 +49,7 @@ class CommentsIndentationTestCase(RuleTestCase):
'...\n', conf) '...\n', conf)
def test_enabled(self): def test_enabled(self):
conf = 'comments-indentation: {}' conf = 'comments-indentation: enable'
self.check('---\n' self.check('---\n'
'# line 1\n' '# line 1\n'
'# line 2\n', conf) '# line 2\n', conf)
@@ -123,18 +123,18 @@ class CommentsIndentationTestCase(RuleTestCase):
'...\n', conf) '...\n', conf)
def test_first_line(self): def test_first_line(self):
conf = 'comments-indentation: {}' conf = 'comments-indentation: enable'
self.check('# comment\n', conf) self.check('# comment\n', conf)
self.check(' # comment\n', conf, problem=(1, 3)) self.check(' # comment\n', conf, problem=(1, 3))
def test_no_newline_at_end(self): def test_no_newline_at_end(self):
conf = ('comments-indentation: {}\n' conf = ('comments-indentation: enable\n'
'new-line-at-end-of-file: disable\n') 'new-line-at-end-of-file: disable\n')
self.check('# comment', conf) self.check('# comment', conf)
self.check(' # comment', conf, problem=(1, 3)) self.check(' # comment', conf, problem=(1, 3))
def test_empty_comment(self): def test_empty_comment(self):
conf = 'comments-indentation: {}' conf = 'comments-indentation: enable'
self.check('---\n' self.check('---\n'
'# hey\n' '# hey\n'
'# normal\n' '# normal\n'

View File

@@ -381,6 +381,141 @@ class IndentationStackTestCase(RuleTestCase):
# missing BEnd here # missing BEnd here
' BEnd \n') ' BEnd \n')
def test_flows_imbrication(self):
self.assertMultiLineEqual(
self.full_stack('[[val]]\n'),
'FSeqStart F_SEQ:1\n'
'FSeqStart F_SEQ:1 F_SEQ:2\n'
' Scalar F_SEQ:1 F_SEQ:2\n'
' FSeqEnd F_SEQ:1\n'
' FSeqEnd \n')
self.assertMultiLineEqual(
self.full_stack('[[val], [val2]]\n'),
'FSeqStart F_SEQ:1\n'
'FSeqStart F_SEQ:1 F_SEQ:2\n'
' Scalar F_SEQ:1 F_SEQ:2\n'
' FSeqEnd F_SEQ:1\n'
' FEntry F_SEQ:1\n'
'FSeqStart F_SEQ:1 F_SEQ:9\n'
' Scalar F_SEQ:1 F_SEQ:9\n'
' FSeqEnd F_SEQ:1\n'
' FSeqEnd \n')
self.assertMultiLineEqual(
self.full_stack('{{key}}\n'),
'FMapStart F_MAP:1\n'
'FMapStart F_MAP:1 F_MAP:2\n'
' Scalar F_MAP:1 F_MAP:2\n'
' FMapEnd F_MAP:1\n'
' FMapEnd \n')
self.assertMultiLineEqual(
self.full_stack('[key]: value\n'),
'BMapStart B_MAP:0\n'
' Key B_MAP:0 KEY:0\n'
'FSeqStart B_MAP:0 KEY:0 F_SEQ:1\n'
' Scalar B_MAP:0 KEY:0 F_SEQ:1\n'
' FSeqEnd B_MAP:0 KEY:0\n'
' Value B_MAP:0 KEY:0 VAL:7\n'
' Scalar B_MAP:0\n'
' BEnd \n')
self.assertMultiLineEqual(
self.full_stack('[[key]]: value\n'),
'BMapStart B_MAP:0\n'
' Key B_MAP:0 KEY:0\n'
'FSeqStart B_MAP:0 KEY:0 F_SEQ:1\n'
'FSeqStart B_MAP:0 KEY:0 F_SEQ:1 F_SEQ:2\n'
' Scalar B_MAP:0 KEY:0 F_SEQ:1 F_SEQ:2\n'
' FSeqEnd B_MAP:0 KEY:0 F_SEQ:1\n'
' FSeqEnd B_MAP:0 KEY:0\n'
' Value B_MAP:0 KEY:0 VAL:9\n'
' Scalar B_MAP:0\n'
' BEnd \n')
self.assertMultiLineEqual(
self.full_stack('{key}: value\n'),
'BMapStart B_MAP:0\n'
' Key B_MAP:0 KEY:0\n'
'FMapStart B_MAP:0 KEY:0 F_MAP:1\n'
' Scalar B_MAP:0 KEY:0 F_MAP:1\n'
' FMapEnd B_MAP:0 KEY:0\n'
' Value B_MAP:0 KEY:0 VAL:7\n'
' Scalar B_MAP:0\n'
' BEnd \n')
self.assertMultiLineEqual(
self.full_stack('{key: value}: value\n'),
'BMapStart B_MAP:0\n'
' Key B_MAP:0 KEY:0\n'
'FMapStart B_MAP:0 KEY:0 F_MAP:1\n'
' Key B_MAP:0 KEY:0 F_MAP:1 KEY:1\n'
' Scalar B_MAP:0 KEY:0 F_MAP:1 KEY:1\n'
' Value B_MAP:0 KEY:0 F_MAP:1 KEY:1 VAL:6\n'
' Scalar B_MAP:0 KEY:0 F_MAP:1\n'
' FMapEnd B_MAP:0 KEY:0\n'
' Value B_MAP:0 KEY:0 VAL:14\n'
' Scalar B_MAP:0\n'
' BEnd \n')
self.assertMultiLineEqual(
self.full_stack('{{key}}: value\n'),
'BMapStart B_MAP:0\n'
' Key B_MAP:0 KEY:0\n'
'FMapStart B_MAP:0 KEY:0 F_MAP:1\n'
'FMapStart B_MAP:0 KEY:0 F_MAP:1 F_MAP:2\n'
' Scalar B_MAP:0 KEY:0 F_MAP:1 F_MAP:2\n'
' FMapEnd B_MAP:0 KEY:0 F_MAP:1\n'
' FMapEnd B_MAP:0 KEY:0\n'
' Value B_MAP:0 KEY:0 VAL:9\n'
' Scalar B_MAP:0\n'
' BEnd \n')
self.assertMultiLineEqual(
self.full_stack('{{key}: val, {key2}: {val2}}\n'),
'FMapStart F_MAP:1\n'
' Key F_MAP:1 KEY:1\n'
'FMapStart F_MAP:1 KEY:1 F_MAP:2\n'
' Scalar F_MAP:1 KEY:1 F_MAP:2\n'
' FMapEnd F_MAP:1 KEY:1\n'
' Value F_MAP:1 KEY:1 VAL:8\n'
' Scalar F_MAP:1\n'
' FEntry F_MAP:1\n'
' Key F_MAP:1 KEY:1\n'
'FMapStart F_MAP:1 KEY:1 F_MAP:14\n'
' Scalar F_MAP:1 KEY:1 F_MAP:14\n'
' FMapEnd F_MAP:1 KEY:1\n'
' Value F_MAP:1 KEY:1 VAL:21\n'
'FMapStart F_MAP:1 KEY:1 VAL:21 F_MAP:22\n'
' Scalar F_MAP:1 KEY:1 VAL:21 F_MAP:22\n'
' FMapEnd F_MAP:1\n'
' FMapEnd \n')
self.assertMultiLineEqual(
self.full_stack('{[{{[val]}}, [{[key]: val2}]]}\n'),
'FMapStart F_MAP:1\n'
'FSeqStart F_MAP:1 F_SEQ:2\n'
'FMapStart F_MAP:1 F_SEQ:2 F_MAP:3\n'
'FMapStart F_MAP:1 F_SEQ:2 F_MAP:3 F_MAP:4\n'
'FSeqStart F_MAP:1 F_SEQ:2 F_MAP:3 F_MAP:4 F_SEQ:5\n'
' Scalar F_MAP:1 F_SEQ:2 F_MAP:3 F_MAP:4 F_SEQ:5\n'
' FSeqEnd F_MAP:1 F_SEQ:2 F_MAP:3 F_MAP:4\n'
' FMapEnd F_MAP:1 F_SEQ:2 F_MAP:3\n'
' FMapEnd F_MAP:1 F_SEQ:2\n'
' FEntry F_MAP:1 F_SEQ:2\n'
'FSeqStart F_MAP:1 F_SEQ:2 F_SEQ:14\n'
'FMapStart F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15\n'
' Key F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15\n'
'FSeqStart F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15 F_SEQ:16\n'
' Scalar F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15 F_SEQ:16\n'
' FSeqEnd F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15\n'
' Value F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15 VAL:22\n'
' Scalar F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15\n'
' FMapEnd F_MAP:1 F_SEQ:2 F_SEQ:14\n'
' FSeqEnd F_MAP:1 F_SEQ:2\n'
' FSeqEnd F_MAP:1\n'
' FMapEnd \n')
class IndentationTestCase(RuleTestCase): class IndentationTestCase(RuleTestCase):
rule_id = 'indentation' rule_id = 'indentation'
@@ -496,6 +631,87 @@ class IndentationTestCase(RuleTestCase):
' date: 1991\n' ' date: 1991\n'
'...\n', conf) '...\n', conf)
def test_consistent(self):
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: whatever}\n'
'document-start: disable\n')
self.check('---\n'
'object:\n'
' k1:\n'
' - a\n'
' - b\n'
' k2: v2\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
self.check('---\n'
'object:\n'
' k1:\n'
' - a\n'
' - b\n'
' k2: v2\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
self.check('---\n'
'object:\n'
' k1:\n'
' - a\n'
' - b\n'
' k2: v2\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
self.check('first is not indented:\n'
' value is indented\n', conf)
self.check('first is not indented:\n'
' value:\n'
' is indented\n', conf)
self.check('- first is already indented:\n'
' value is indented too\n', conf)
self.check('- first is already indented:\n'
' value:\n'
' is indented too\n', conf)
self.check('- first is already indented:\n'
' value:\n'
' is indented too\n', conf, problem=(3, 14))
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem=(7, 5))
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf)
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf)
def test_indent_sequences_whatever(self): def test_indent_sequences_whatever(self):
conf = 'indentation: {spaces: 4, indent-sequences: whatever}' conf = 'indentation: {spaces: 4, indent-sequences: whatever}'
self.check('---\n' self.check('---\n'
@@ -534,10 +750,60 @@ class IndentationTestCase(RuleTestCase):
'- b\n' '- b\n'
'- c\n', conf, problem=(6, 1, 'syntax')) '- c\n', conf, problem=(6, 1, 'syntax'))
def test_indent_sequences_consistent(self):
conf = 'indentation: {spaces: 4, indent-sequences: consistent}'
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list:\n'
' two:\n'
' - a\n'
' - b\n'
' - c\n', conf)
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list:\n'
' two:\n'
' - a\n'
' - b\n'
' - c\n', conf)
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem=(7, 5))
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf, problem=(7, 1))
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf, problem1=(3, 2), problem2=(7, 1))
def test_direct_flows(self): def test_direct_flows(self):
# flow: [ ... # flow: [ ...
# ] # ]
conf = 'indentation: {spaces: 2}' conf = 'indentation: {spaces: consistent}'
self.check('---\n' self.check('---\n'
'a: {x: 1,\n' 'a: {x: 1,\n'
' y,\n' ' y,\n'
@@ -589,7 +855,7 @@ class IndentationTestCase(RuleTestCase):
# flow: [ # flow: [
# ... # ...
# ] # ]
conf = 'indentation: {spaces: 2}' conf = 'indentation: {spaces: consistent}'
self.check('---\n' self.check('---\n'
'a: {\n' 'a: {\n'
' x: 1,\n' ' x: 1,\n'
@@ -603,7 +869,7 @@ class IndentationTestCase(RuleTestCase):
'a: {\n' 'a: {\n'
' x: 1,\n' ' x: 1,\n'
' y, z: 1\n' ' y, z: 1\n'
'}\n', conf, problem=(3, 4)) '}\n', conf, problem=(4, 3))
self.check('---\n' self.check('---\n'
'a: {\n' 'a: {\n'
' x: 1,\n' ' x: 1,\n'
@@ -622,7 +888,7 @@ class IndentationTestCase(RuleTestCase):
'a: [\n' 'a: [\n'
' x,\n' ' x,\n'
' y, z\n' ' y, z\n'
']\n', conf, problem=(3, 4)) ']\n', conf, problem=(4, 3))
self.check('---\n' self.check('---\n'
'a: [\n' 'a: [\n'
' x,\n' ' x,\n'
@@ -671,13 +937,24 @@ class IndentationTestCase(RuleTestCase):
' foo: 1,\n' ' foo: 1,\n'
' bar: 2\n' ' bar: 2\n'
' }\n', conf, problem1=(7, 7), problem2=(11, 3)) ' }\n', conf, problem1=(7, 7), problem2=(11, 3))
conf = 'indentation: {spaces: 2}'
self.check('---\n'
'a: {\n'
' x: 1,\n'
' y, z: 1\n'
'}\n', conf, problem=(3, 4))
self.check('---\n'
'a: [\n'
' x,\n'
' y, z\n'
']\n', conf, problem=(3, 4))
def test_cleared_flows(self): def test_cleared_flows(self):
# flow: # flow:
# [ # [
# ... # ...
# ] # ]
conf = 'indentation: {spaces: 2}' conf = 'indentation: {spaces: consistent}'
self.check('---\n' self.check('---\n'
'top:\n' 'top:\n'
' rules:\n' ' rules:\n'
@@ -727,7 +1004,7 @@ class IndentationTestCase(RuleTestCase):
'top:\n' 'top:\n'
' [\n' ' [\n'
' a, b, c\n' ' a, b, c\n'
' ]\n', conf, problem=(3, 4)) ' ]\n', conf, problem=(4, 6))
self.check('---\n' self.check('---\n'
'top:\n' 'top:\n'
' [\n' ' [\n'
@@ -762,7 +1039,7 @@ class IndentationTestCase(RuleTestCase):
problem3=(9, 9), problem4=(11, 7), problem5=(13, 1)) problem3=(9, 9), problem4=(11, 7), problem5=(13, 1))
def test_under_indented(self): def test_under_indented(self):
conf = 'indentation: {spaces: 2, indent-sequences: yes}' conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
self.check('---\n' self.check('---\n'
'object:\n' 'object:\n'
' val: 1\n' ' val: 1\n'
@@ -778,7 +1055,7 @@ class IndentationTestCase(RuleTestCase):
' - name: Unix\n' ' - name: Unix\n'
' date: 1969\n' ' date: 1969\n'
'...\n', conf, problem=(5, 6, 'syntax')) '...\n', conf, problem=(5, 6, 'syntax'))
conf = 'indentation: {spaces: 4, indent-sequences: yes}' conf = 'indentation: {spaces: 4, indent-sequences: consistent}'
self.check('---\n' self.check('---\n'
'object:\n' 'object:\n'
' val: 1\n' ' val: 1\n'
@@ -796,7 +1073,7 @@ class IndentationTestCase(RuleTestCase):
'...\n', conf, problem=(5, 10, 'syntax')) '...\n', conf, problem=(5, 10, 'syntax'))
def test_over_indented(self): def test_over_indented(self):
conf = 'indentation: {spaces: 2, indent-sequences: yes}' conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
self.check('---\n' self.check('---\n'
'object:\n' 'object:\n'
' val: 1\n' ' val: 1\n'
@@ -812,7 +1089,7 @@ class IndentationTestCase(RuleTestCase):
' - name: Unix\n' ' - name: Unix\n'
' date: 1969\n' ' date: 1969\n'
'...\n', conf, problem=(5, 12, 'syntax')) '...\n', conf, problem=(5, 12, 'syntax'))
conf = 'indentation: {spaces: 4, indent-sequences: yes}' conf = 'indentation: {spaces: 4, indent-sequences: consistent}'
self.check('---\n' self.check('---\n'
'object:\n' 'object:\n'
' val: 1\n' ' val: 1\n'
@@ -852,7 +1129,7 @@ class IndentationTestCase(RuleTestCase):
problem=(2, 3)) problem=(2, 3))
def test_multi_lines(self): def test_multi_lines(self):
conf = 'indentation: {spaces: 2, indent-sequences: yes}' conf = 'indentation: {spaces: consistent, indent-sequences: yes}'
self.check('---\n' self.check('---\n'
'long_string: >\n' 'long_string: >\n'
' bla bla blah\n' ' bla bla blah\n'
@@ -871,7 +1148,7 @@ class IndentationTestCase(RuleTestCase):
'...\n', conf) '...\n', conf)
def test_empty_value(self): def test_empty_value(self):
conf = 'indentation: {spaces: 2}' conf = 'indentation: {spaces: consistent}'
self.check('---\n' self.check('---\n'
'key1:\n' 'key1:\n'
'key2: not empty\n' 'key2: not empty\n'
@@ -926,7 +1203,7 @@ class IndentationTestCase(RuleTestCase):
'...\n', conf, problem=(2, 2)) '...\n', conf, problem=(2, 2))
def test_return(self): def test_return(self):
conf = 'indentation: {spaces: 2}' conf = 'indentation: {spaces: consistent}'
self.check('---\n' self.check('---\n'
'a:\n' 'a:\n'
' b:\n' ' b:\n'
@@ -950,18 +1227,52 @@ class IndentationTestCase(RuleTestCase):
'...\n', conf, problem=(5, 2, 'syntax')) '...\n', conf, problem=(5, 2, 'syntax'))
def test_first_line(self): def test_first_line(self):
conf = ('indentation: {spaces: 2}\n' conf = ('indentation: {spaces: consistent}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check(' a: 1\n', conf, problem=(1, 3)) self.check(' a: 1\n', conf, problem=(1, 3))
def test_explicit_block_mappings(self): def test_explicit_block_mappings(self):
conf = 'indentation: {spaces: 4}' conf = 'indentation: {spaces: consistent}'
self.check('---\n'
'object:\n'
' ? key\n'
' : value\n', conf)
self.check('---\n' self.check('---\n'
'object:\n' 'object:\n'
' ? key\n' ' ? key\n'
' :\n' ' :\n'
' value\n' ' value\n'
'...\n', conf) '...\n', conf)
self.check('---\n'
'object:\n'
' ?\n'
' key\n'
' : value\n', conf)
self.check('---\n'
'object:\n'
' ?\n'
' key\n'
' :\n'
' value\n'
'...\n', conf)
self.check('---\n'
'- ? key\n'
' : value\n', conf)
self.check('---\n'
'- ? key\n'
' :\n'
' value\n'
'...\n', conf)
self.check('---\n'
'- ?\n'
' key\n'
' : value\n', conf)
self.check('---\n'
'- ?\n'
' key\n'
' :\n'
' value\n'
'...\n', conf)
self.check('---\n' self.check('---\n'
'object:\n' 'object:\n'
' ? key\n' ' ? key\n'
@@ -969,12 +1280,11 @@ class IndentationTestCase(RuleTestCase):
' value\n' ' value\n'
'...\n', conf, problem=(5, 8)) '...\n', conf, problem=(5, 8))
self.check('---\n' self.check('---\n'
'object:\n' '- - ?\n'
' ?\n' ' key\n'
' key\n'
' :\n' ' :\n'
' value\n' ' value\n'
'...\n', conf) '...\n', conf, problem=(5, 7))
self.check('---\n' self.check('---\n'
'object:\n' 'object:\n'
' ?\n' ' ?\n'
@@ -991,7 +1301,7 @@ class IndentationTestCase(RuleTestCase):
'...\n', conf, problem1=(4, 10), problem2=(6, 8)) '...\n', conf, problem1=(4, 10), problem2=(6, 8))
def test_clear_sequence_item(self): def test_clear_sequence_item(self):
conf = 'indentation: {spaces: 2}' conf = 'indentation: {spaces: consistent}'
self.check('---\n' self.check('---\n'
'-\n' '-\n'
' string\n' ' string\n'
@@ -1006,6 +1316,35 @@ class IndentationTestCase(RuleTestCase):
' multi\n' ' multi\n'
' line\n' ' line\n'
'...\n', conf) '...\n', conf)
self.check('---\n'
'-\n'
' string\n'
'-\n'
' string\n', conf, problem=(5, 4))
self.check('---\n'
'-\n'
' map: ping\n'
'-\n'
' map: ping\n', conf, problem=(5, 4))
self.check('---\n'
'-\n'
' - sequence\n'
'-\n'
' - sequence\n', conf, problem=(5, 4))
self.check('---\n'
'-\n'
' -\n'
' nested\n'
' -\n'
' nested\n', conf, problem1=(4, 4), problem2=(6, 6))
self.check('---\n'
'-\n'
' -\n'
' >\n'
' multi\n'
' line\n'
'...\n', conf, problem=(4, 6))
conf = 'indentation: {spaces: 2}'
self.check('---\n' self.check('---\n'
'-\n' '-\n'
' string\n' ' string\n'
@@ -1027,16 +1366,9 @@ class IndentationTestCase(RuleTestCase):
' nested\n' ' nested\n'
' -\n' ' -\n'
' nested\n', conf, problem1=(4, 4), problem2=(6, 6)) ' nested\n', conf, problem1=(4, 4), problem2=(6, 6))
self.check('---\n'
'-\n'
' -\n'
' >\n'
' multi\n'
' line\n'
'...\n', conf, problem=(4, 6))
def test_anchors(self): def test_anchors(self):
conf = 'indentation: {spaces: 2}' conf = 'indentation: {spaces: consistent}'
self.check('---\n' self.check('---\n'
'key: &anchor value\n', conf) 'key: &anchor value\n', conf)
self.check('---\n' self.check('---\n'
@@ -1096,7 +1428,7 @@ class IndentationTestCase(RuleTestCase):
' - k: *a\n', conf) ' - k: *a\n', conf)
def test_tags(self): def test_tags(self):
conf = 'indentation: {spaces: 2}' conf = 'indentation: {spaces: consistent}'
self.check('---\n' self.check('---\n'
'-\n' '-\n'
' "flow in block"\n' ' "flow in block"\n'
@@ -1105,7 +1437,7 @@ class IndentationTestCase(RuleTestCase):
'- !!map # Block collection\n' '- !!map # Block collection\n'
' foo: bar\n', conf) ' foo: bar\n', conf)
conf = 'indentation: {spaces: 2, indent-sequences: no}' conf = 'indentation: {spaces: consistent, indent-sequences: no}'
self.check('---\n' self.check('---\n'
'sequence: !!seq\n' 'sequence: !!seq\n'
'- entry\n' '- entry\n'
@@ -1124,12 +1456,55 @@ class IndentationTestCase(RuleTestCase):
'- Clark Evans\n' '- Clark Evans\n'
'- Ingy döt Net\n', conf) '- Ingy döt Net\n', conf)
def test_flows_imbrication(self):
conf = 'indentation: {spaces: consistent}'
self.check('---\n'
'[val]: value\n', conf)
self.check('---\n'
'{key}: value\n', conf)
self.check('---\n'
'{key: val}: value\n', conf)
self.check('---\n'
'[[val]]: value\n', conf)
self.check('---\n'
'{{key}}: value\n', conf)
self.check('---\n'
'{{key: val1}: val2}: value\n', conf)
self.check('---\n'
'- [val, {{key: val}: val}]: value\n'
'- {[val,\n'
' {{key: val}: val}]}\n'
'- {[val,\n'
' {{key: val,\n'
' key2}}]}\n'
'- {{{{{moustaches}}}}}\n'
'- {{{{{moustache,\n'
' moustache},\n'
' moustache}},\n'
' moustache}}\n', conf)
self.check('---\n'
'- {[val,\n'
' {{key: val}: val}]}\n',
conf, problem=(3, 6))
self.check('---\n'
'- {[val,\n'
' {{key: val,\n'
' key2}}]}\n',
conf, problem=(4, 6))
self.check('---\n'
'- {{{{{moustache,\n'
' moustache},\n'
' moustache}},\n'
' moustache}}\n',
conf, problem1=(4, 8), problem2=(5, 4))
class ScalarIndentationTestCase(RuleTestCase): class ScalarIndentationTestCase(RuleTestCase):
rule_id = 'indentation' rule_id = 'indentation'
def test_basics_plain(self): def test_basics_plain(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: no}\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: no}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('multi\n' self.check('multi\n'
'line\n', conf) 'line\n', conf)
@@ -1157,7 +1532,8 @@ class ScalarIndentationTestCase(RuleTestCase):
' }\n', conf) ' }\n', conf)
def test_check_multi_line_plain(self): def test_check_multi_line_plain(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('multi\n' self.check('multi\n'
' line\n', conf, problem=(2, 2)) ' line\n', conf, problem=(2, 2))
@@ -1179,7 +1555,8 @@ class ScalarIndentationTestCase(RuleTestCase):
' }\n', conf, problem=(3, 9)) ' }\n', conf, problem=(3, 9))
def test_basics_quoted(self): def test_basics_quoted(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: no}\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: no}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('"multi\n' self.check('"multi\n'
' line"\n', conf) ' line"\n', conf)
@@ -1209,7 +1586,8 @@ class ScalarIndentationTestCase(RuleTestCase):
' line 2"]\n', conf) ' line 2"]\n', conf)
def test_check_multi_line_quoted(self): def test_check_multi_line_quoted(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('"multi\n' self.check('"multi\n'
'line"\n', conf, problem=(2, 1)) 'line"\n', conf, problem=(2, 1))
@@ -1264,7 +1642,8 @@ class ScalarIndentationTestCase(RuleTestCase):
' line 2"]\n', conf, problem=(3, 14)) ' line 2"]\n', conf, problem=(3, 14))
def test_basics_folded_style(self): def test_basics_folded_style(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: no}\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: no}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('>\n' self.check('>\n'
' multi\n' ' multi\n'
@@ -1301,7 +1680,8 @@ class ScalarIndentationTestCase(RuleTestCase):
' {% endif %}\n', conf) ' {% endif %}\n', conf)
def test_check_multi_line_folded_style(self): def test_check_multi_line_folded_style(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('>\n' self.check('>\n'
' multi\n' ' multi\n'
@@ -1341,7 +1721,8 @@ class ScalarIndentationTestCase(RuleTestCase):
problem1=(3, 7), problem2=(5, 7)) problem1=(3, 7), problem2=(5, 7))
def test_basics_literal_style(self): def test_basics_literal_style(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: no}\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: no}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('|\n' self.check('|\n'
' multi\n' ' multi\n'
@@ -1378,7 +1759,8 @@ class ScalarIndentationTestCase(RuleTestCase):
' {% endif %}\n', conf) ' {% endif %}\n', conf)
def test_check_multi_line_literal_style(self): def test_check_multi_line_literal_style(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('|\n' self.check('|\n'
' multi\n' ' multi\n'
@@ -1421,7 +1803,8 @@ class ScalarIndentationTestCase(RuleTestCase):
# http://stackoverflow.com/questions/3790454/in-yaml-how-do-i-break-a-string-over-multiple-lines # http://stackoverflow.com/questions/3790454/in-yaml-how-do-i-break-a-string-over-multiple-lines
def test_paragraph_plain(self): def test_paragraph_plain(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('- long text: very "long"\n' self.check('- long text: very "long"\n'
' \'string\' with\n' ' \'string\' with\n'
@@ -1442,7 +1825,8 @@ class ScalarIndentationTestCase(RuleTestCase):
' spaces.\n', conf) ' spaces.\n', conf)
def test_paragraph_double_quoted(self): def test_paragraph_double_quoted(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('- long text: "very \\"long\\"\n' self.check('- long text: "very \\"long\\"\n'
' \'string\' with\n' ' \'string\' with\n'
@@ -1469,7 +1853,8 @@ class ScalarIndentationTestCase(RuleTestCase):
' spaces."\n', conf) ' spaces."\n', conf)
def test_paragraph_single_quoted(self): def test_paragraph_single_quoted(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('- long text: \'very "long"\n' self.check('- long text: \'very "long"\n'
' \'\'string\'\' with\n' ' \'\'string\'\' with\n'
@@ -1496,7 +1881,8 @@ class ScalarIndentationTestCase(RuleTestCase):
' spaces.\'\n', conf) ' spaces.\'\n', conf)
def test_paragraph_folded(self): def test_paragraph_folded(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('- long text: >\n' self.check('- long text: >\n'
' very "long"\n' ' very "long"\n'
@@ -1513,7 +1899,8 @@ class ScalarIndentationTestCase(RuleTestCase):
problem1=(3, 6), problem2=(5, 7), problem3=(6, 8)) problem1=(3, 6), problem2=(5, 7), problem3=(6, 8))
def test_paragraph_literal(self): def test_paragraph_literal(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('- long text: |\n' self.check('- long text: |\n'
' very "long"\n' ' very "long"\n'
@@ -1528,3 +1915,39 @@ class ScalarIndentationTestCase(RuleTestCase):
' paragraph gap, \\n and\n' ' paragraph gap, \\n and\n'
' spaces.\n', conf, ' spaces.\n', conf,
problem1=(3, 6), problem2=(5, 7), problem3=(6, 8)) problem1=(3, 6), problem2=(5, 7), problem3=(6, 8))
def test_consistent(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
'document-start: disable\n')
self.check('multi\n'
'line\n', conf)
self.check('multi\n'
' line\n', conf, problem=(2, 2))
self.check('- multi\n'
' line\n', conf)
self.check('- multi\n'
' line\n', conf, problem=(2, 4))
self.check('a key: multi\n'
' line\n', conf, problem=(2, 3))
self.check('a key: multi\n'
' line\n', conf, problem=(2, 9))
self.check('a key:\n'
' multi\n'
' line\n', conf, problem=(3, 4))
self.check('- C code: void main() {\n'
' printf("foo");\n'
' }\n', conf, problem=(2, 15))
self.check('- C code:\n'
' void main() {\n'
' printf("foo");\n'
' }\n', conf, problem=(3, 9))
self.check('>\n'
' multi\n'
' line\n', conf)
self.check('>\n'
' multi\n'
' line\n', conf)
self.check('>\n'
' multi\n'
' line\n', conf, problem=(3, 7))

View File

@@ -80,7 +80,7 @@ class KeyDuplicatesTestCase(RuleTestCase):
': 1\n', conf) ': 1\n', conf)
def test_enabled(self): def test_enabled(self):
conf = 'key-duplicates: {}' conf = 'key-duplicates: enable'
self.check('---\n' self.check('---\n'
'block mapping:\n' 'block mapping:\n'
' key: a\n' ' key: a\n'
@@ -149,7 +149,7 @@ class KeyDuplicatesTestCase(RuleTestCase):
problem4=(7, 3)) problem4=(7, 3))
def test_key_tokens_in_flow_sequences(self): def test_key_tokens_in_flow_sequences(self):
conf = 'key-duplicates: {}' conf = 'key-duplicates: enable'
self.check('---\n' self.check('---\n'
'[\n' '[\n'
' flow: sequence, with, key: value, mappings\n' ' flow: sequence, with, key: value, mappings\n'

View File

@@ -78,6 +78,12 @@ class LineLengthTestCase(RuleTestCase):
' # http://localhost/very/long/url\n' ' # http://localhost/very/long/url\n'
' comment\n' ' comment\n'
'...\n', conf) '...\n', conf)
self.check('---\n'
'this:\n'
'is:\n'
'another:\n'
' - https://localhost/very/very/long/url\n'
'...\n', conf)
conf = 'line-length: {max: 20, allow-non-breakable-words: no}' conf = 'line-length: {max: 20, allow-non-breakable-words: no}'
self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21)) self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21))
@@ -94,3 +100,9 @@ class LineLengthTestCase(RuleTestCase):
' # http://localhost/very/long/url\n' ' # http://localhost/very/long/url\n'
' comment\n' ' comment\n'
'...\n', conf, problem=(5, 21)) '...\n', conf, problem=(5, 21))
self.check('---\n'
'this:\n'
'is:\n'
'another:\n'
' - https://localhost/very/very/long/url\n'
'...\n', conf, problem=(5, 21))

View File

@@ -30,7 +30,7 @@ class NewLineAtEndOfFileTestCase(RuleTestCase):
self.check('Sentence.\n', conf) self.check('Sentence.\n', conf)
def test_enabled(self): def test_enabled(self):
conf = ('new-line-at-end-of-file: {}\n' conf = ('new-line-at-end-of-file: enable\n'
'empty-lines: disable\n' 'empty-lines: disable\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('', conf) self.check('', conf)

View File

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

299
tests/test_cli.py Normal file
View File

@@ -0,0 +1,299 @@
# -*- 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/>.
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
import os
import shutil
import tempfile
import unittest
import sys
from yamllint import cli
class CommandLineTestCase(unittest.TestCase):
def setUp(self):
self.wd = tempfile.mkdtemp(prefix='yamllint-tests-')
# .yaml file at root
with open(os.path.join(self.wd, 'a.yaml'), 'w') as f:
f.write('---\n'
'- 1 \n'
'- 2')
# .yml file at root
open(os.path.join(self.wd, 'empty.yml'), 'w').close()
# file in dir
os.mkdir(os.path.join(self.wd, 'sub'))
with open(os.path.join(self.wd, 'sub', 'ok.yaml'), 'w') as f:
f.write('---\n'
'key: value\n')
# file in very nested dir
dir = self.wd
for i in range(15):
dir = os.path.join(dir, 's')
os.mkdir(dir)
with open(os.path.join(dir, 'file.yaml'), 'w') as f:
f.write('---\n'
'key: value\n'
'key: other value\n')
# empty dir
os.mkdir(os.path.join(self.wd, 'empty-dir'))
# non-YAML file
with open(os.path.join(self.wd, 'no-yaml.json'), 'w') as f:
f.write('---\n'
'key: value\n')
def tearDown(self):
shutil.rmtree(self.wd)
def test_find_files_recursively(self):
self.assertEqual(
sorted(cli.find_files_recursively([self.wd])),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/ok.yaml')],
)
items = [os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'empty-dir')]
self.assertEqual(
sorted(cli.find_files_recursively(items)),
[os.path.join(self.wd, 'sub/ok.yaml')],
)
items = [os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 's')]
self.assertEqual(
sorted(cli.find_files_recursively(items)),
[os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml')],
)
items = [os.path.join(self.wd, 'sub'),
os.path.join(self.wd, '/etc/another/file')]
self.assertEqual(
sorted(cli.find_files_recursively(items)),
[os.path.join(self.wd, '/etc/another/file'),
os.path.join(self.wd, 'sub/ok.yaml')],
)
def test_run_with_bad_arguments(self):
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(())
self.assertNotEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^usage')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('--unknown-arg', ))
self.assertNotEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^usage')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-c', './conf.yaml', '-d', 'relaxed', 'file'))
self.assertNotEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^Options --config-file and '
r'--config-data cannot be used')
def test_run_with_bad_config(self):
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-d', 'rules: {a: b}', 'file'))
self.assertEqual(ctx.exception.code, -1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^invalid config: no such rule')
def test_run_with_empty_config(self):
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-d', '', 'file'))
self.assertEqual(ctx.exception.code, -1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^invalid config: not a dict')
def test_run_with_config_file(self):
with open(os.path.join(self.wd, 'config'), 'w') as f:
f.write('rules: {trailing-spaces: disable}')
with self.assertRaises(SystemExit) as ctx:
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
self.assertEqual(ctx.exception.code, 0)
with open(os.path.join(self.wd, 'config'), 'w') as f:
f.write('rules: {trailing-spaces: enable}')
with self.assertRaises(SystemExit) as ctx:
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
self.assertEqual(ctx.exception.code, 1)
def test_run_with_user_global_config_file(self):
home = os.path.join(self.wd, 'fake-home')
os.mkdir(home)
dir = os.path.join(home, '.config')
os.mkdir(dir)
dir = os.path.join(dir, 'yamllint')
os.mkdir(dir)
config = os.path.join(dir, 'config')
temp = os.environ['HOME']
os.environ['HOME'] = home
with open(config, 'w') as f:
f.write('rules: {trailing-spaces: disable}')
with self.assertRaises(SystemExit) as ctx:
cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.exception.code, 0)
with open(config, 'w') as f:
f.write('rules: {trailing-spaces: enable}')
with self.assertRaises(SystemExit) as ctx:
cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.exception.code, 1)
os.environ['HOME'] = temp
def test_run_version(self):
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('--version', ))
self.assertEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertRegexpMatches(out + err, r'yamllint \d+\.\d+')
def test_run_non_existing_file(self):
file = os.path.join(self.wd, 'i-do-not-exist.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
self.assertEqual(ctx.exception.code, -1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'No such file or directory')
def test_run_one_problem_file(self):
file = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'%s:2:4: [error] trailing spaces (trailing-spaces)\n'
'%s:3:4: [error] no new line character at the end of file '
'(new-line-at-end-of-file)\n') % (file, file))
self.assertEqual(err, '')
def test_run_one_ok_file(self):
file = os.path.join(self.wd, 'sub', 'ok.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
self.assertEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertEqual(err, '')
def test_run_empty_file(self):
file = os.path.join(self.wd, 'empty.yml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
self.assertEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertEqual(err, '')
def test_run_multiple_files(self):
items = [os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 's')]
file = items[1] + '/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(['-f', 'parsable'] + items)
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'%s:3:1: [error] duplication of key "key" in mapping '
'(key-duplicates)\n') % file)
self.assertEqual(err, '')
def test_run_colored_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, ))
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, '')

View File

@@ -32,12 +32,16 @@ class SimpleConfigTestCase(unittest.TestCase):
self.assertEqual(len(new.enabled_rules()), 1) self.assertEqual(len(new.enabled_rules()), 1)
def test_invalid_conf(self):
with self.assertRaises(config.YamlLintConfigError):
config.YamlLintConfig('not: valid: yaml')
def test_unknown_rule(self): def test_unknown_rule(self):
with self.assertRaisesRegexp( with self.assertRaisesRegexp(
config.YamlLintConfigError, config.YamlLintConfigError,
'invalid config: no such rule: "this-one-does-not-exist"'): 'invalid config: no such rule: "this-one-does-not-exist"'):
config.YamlLintConfig('rules:\n' config.YamlLintConfig('rules:\n'
' this-one-does-not-exist: {}\n') ' this-one-does-not-exist: enable\n')
def test_missing_option(self): def test_missing_option(self):
with self.assertRaisesRegexp( with self.assertRaisesRegexp(
@@ -58,6 +62,58 @@ class SimpleConfigTestCase(unittest.TestCase):
' max-spaces-after: 1\n' ' max-spaces-after: 1\n'
' abcdef: yes\n') ' abcdef: yes\n')
def test_validate_rule_conf(self):
class Rule(object):
ID = 'fake'
self.assertEqual(config.validate_rule_conf(Rule, False), False)
self.assertEqual(config.validate_rule_conf(Rule, 'disable'), False)
self.assertEqual(config.validate_rule_conf(Rule, {}),
{'level': 'error'})
self.assertEqual(config.validate_rule_conf(Rule, 'enable'),
{'level': 'error'})
config.validate_rule_conf(Rule, {'level': 'error'})
config.validate_rule_conf(Rule, {'level': 'warning'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'level': 'warn'})
Rule.CONF = {'length': int}
config.validate_rule_conf(Rule, {'length': 8})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'height': 8})
Rule.CONF = {'a': bool, 'b': int}
config.validate_rule_conf(Rule, {'a': True, 'b': 0})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'a': True})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'b': 0})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'a': 1, 'b': 0})
Rule.CONF = {'choice': (True, 88, 'str')}
config.validate_rule_conf(Rule, {'choice': True})
config.validate_rule_conf(Rule, {'choice': 88})
config.validate_rule_conf(Rule, {'choice': 'str'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': False})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 99})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 'abc'})
Rule.CONF = {'choice': (int, 'hardcoded')}
config.validate_rule_conf(Rule, {'choice': 42})
config.validate_rule_conf(Rule, {'choice': 'hardcoded'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': False})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 'abc'})
class ExtendedConfigTestCase(unittest.TestCase): class ExtendedConfigTestCase(unittest.TestCase):
def test_extend_add_rule(self): def test_extend_add_rule(self):

46
tests/test_linter.py Normal file
View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import io
import unittest
from yamllint.config import YamlLintConfig
from yamllint import linter
class LinterTestCase(unittest.TestCase):
def fake_config(self):
return YamlLintConfig('extends: default')
def test_run_on_string(self):
linter.run('test: document', self.fake_config())
def test_run_on_bytes(self):
linter.run(b'test: document', self.fake_config())
def test_run_on_unicode(self):
linter.run(u'test: document', self.fake_config())
def test_run_on_stream(self):
linter.run(io.StringIO(u'hello'), self.fake_config())
def test_run_on_int(self):
self.assertRaises(TypeError, linter.run, 42, self.fake_config())
def test_run_on_list(self):
self.assertRaises(TypeError, linter.run,
['h', 'e', 'l', 'l', 'o'], self.fake_config())

View File

@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from io import open
import os import os
from tests.common import RuleTestCase from tests.common import RuleTestCase
@@ -120,7 +121,8 @@ conf_overrides = {
'example-10.2': ('indentation: {indent-sequences: no}\n'), 'example-10.2': ('indentation: {indent-sequences: no}\n'),
} }
files = os.listdir('tests/yaml-1.2-spec-examples') files = os.listdir(os.path.join(os.path.dirname(os.path.realpath(__file__)),
'yaml-1.2-spec-examples'))
assert len(files) == 132 assert len(files) == 132
@@ -178,7 +180,7 @@ for file in files:
if file in pyyaml_blacklist: if file in pyyaml_blacklist:
continue continue
with open('tests/yaml-1.2-spec-examples/' + file) as f: with open('tests/yaml-1.2-spec-examples/' + file, encoding='utf-8') as f:
conf = conf_general + conf_overrides.get(file, '') conf = conf_general + conf_overrides.get(file, '')
setattr(SpecificationTestCase, 'test_' + file, setattr(SpecificationTestCase, 'test_' + file,
_gen_test(f.read(), conf)) _gen_test(f.read(), conf))

View File

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

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé # Copyright (C) 2016 Adrien Vergé
# #
@@ -67,8 +66,11 @@ def run(argv=None):
description=APP_DESCRIPTION) description=APP_DESCRIPTION)
parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+', parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+',
help='files to check') help='files to check')
parser.add_argument('-c', '--config', dest='config_file', action='store', parser.add_argument('-c', '--config-file', dest='config_file',
help='path to a custom configuration') action='store', help='path to a custom configuration')
parser.add_argument('-d', '--config-data', dest='config_data',
action='store',
help='custom configuration (as YAML source)')
parser.add_argument('-f', '--format', parser.add_argument('-f', '--format',
choices=('parsable', 'standard'), default='standard', choices=('parsable', 'standard'), default='standard',
help='format for parsing output') help='format for parsing output')
@@ -79,11 +81,31 @@ def run(argv=None):
args = parser.parse_args(argv) args = parser.parse_args(argv)
if args.config_file is not None and args.config_data is not None:
print('Options --config-file and --config-data cannot be used '
'simultaneously.', file=sys.stderr)
sys.exit(-1)
# User-global config is supposed to be in ~/.config/yamllint/config
user_global_config = None
if 'XDG_CONFIG_HOME' in os.environ:
user_global_config = os.path.join(
os.environ['XDG_CONFIG_HOME'], 'yamllint', 'config')
elif 'HOME' in os.environ:
user_global_config = os.path.join(
os.environ['HOME'], '.config', 'yamllint', 'config')
try: try:
if args.config_file is not None: if args.config_data is not None:
if args.config_data != '' and ':' not in args.config_data:
args.config_data = 'extends: ' + args.config_data
conf = YamlLintConfig(content=args.config_data)
elif args.config_file is not None:
conf = YamlLintConfig(file=args.config_file) conf = YamlLintConfig(file=args.config_file)
elif os.path.isfile('.yamllint'): elif os.path.isfile('.yamllint'):
conf = YamlLintConfig(file='.yamllint') conf = YamlLintConfig(file='.yamllint')
elif user_global_config and os.path.isfile(user_global_config):
conf = YamlLintConfig(file=user_global_config)
else: else:
conf = YamlLintConfig('extends: default') conf = YamlLintConfig('extends: default')
except YamlLintConfigError as e: except YamlLintConfigError as e:
@@ -112,7 +134,7 @@ def run(argv=None):
if not first and args.format != 'parsable': if not first and args.format != 'parsable':
print('') print('')
except EnvironmentError as e: except EnvironmentError as e:
print(e) print(e, file=sys.stderr)
return_code = -1 return_code = -1
sys.exit(return_code) sys.exit(return_code)

View File

@@ -31,14 +31,14 @@ rules:
hyphens: hyphens:
max-spaces-after: 1 max-spaces-after: 1
indentation: indentation:
spaces: 2 spaces: consistent
indent-sequences: yes indent-sequences: yes
check-multi-line-strings: no check-multi-line-strings: no
key-duplicates: {} key-duplicates: enable
line-length: line-length:
max: 80 max: 80
allow-non-breakable-words: yes allow-non-breakable-words: yes
new-line-at-end-of-file: {level: error} new-line-at-end-of-file: enable
new-lines: new-lines:
type: unix type: unix
trailing-spaces: {} trailing-spaces: enable

View File

@@ -0,0 +1,27 @@
---
extends: default
rules:
braces:
level: warning
max-spaces-inside: 1
brackets:
level: warning
max-spaces-inside: 1
colons:
level: warning
commas:
level: warning
comments: disable
comments-indentation: disable
document-start: disable
empty-lines:
level: warning
hyphens:
level: warning
indentation:
level: warning
indent-sequences: consistent
line-length:
level: warning

View File

@@ -59,6 +59,9 @@ 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:
raise YamlLintConfigError('invalid config: not a dict')
self.rules = conf.get('rules', {}) self.rules = conf.get('rules', {})
# Does this conf override another conf that we need to load? # Does this conf override another conf that we need to load?
@@ -83,6 +86,8 @@ class YamlLintConfig(object):
def validate_rule_conf(rule, conf): def validate_rule_conf(rule, conf):
if conf is False or conf == 'disable': if conf is False or conf == 'disable':
return False return False
elif conf == 'enable':
conf = {}
if type(conf) == dict: if type(conf) == dict:
if 'level' not in conf: if 'level' not in conf:
@@ -100,7 +105,8 @@ def validate_rule_conf(rule, conf):
'invalid config: unknown option "%s" for rule "%s"' % 'invalid config: unknown option "%s" for rule "%s"' %
(optkey, rule.ID)) (optkey, rule.ID))
if type(options[optkey]) == tuple: if type(options[optkey]) == tuple:
if conf[optkey] not in options[optkey]: if (conf[optkey] not in options[optkey] and
type(conf[optkey]) not in options[optkey]):
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be in %s' 'invalid config: option "%s" of "%s" should be in %s'
% (optkey, rule.ID, options[optkey])) % (optkey, rule.ID, options[optkey]))
@@ -116,7 +122,8 @@ def validate_rule_conf(rule, conf):
(optkey, rule.ID)) (optkey, rule.ID))
else: else:
raise YamlLintConfigError(('invalid config: rule "%s": should be ' raise YamlLintConfigError(('invalid config: rule "%s": should be '
'either "disable" or a dict') % rule.ID) 'either "enable", "disable" or a dict')
% rule.ID)
return conf return conf
@@ -125,7 +132,7 @@ def get_extended_config_file(name):
# Is it a standard conf shipped with yamllint... # Is it a standard conf shipped with yamllint...
if '/' not in name: if '/' not in name:
std_conf = os.path.join(os.path.dirname(os.path.realpath(__file__)), std_conf = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'conf', name + '.yml') 'conf', name + '.yaml')
if os.path.isfile(std_conf): if os.path.isfile(std_conf):
return std_conf return std_conf

View File

@@ -128,7 +128,7 @@ def run(input, conf):
:param input: buffer, string or stream to read from :param input: buffer, string or stream to read from
:param conf: yamllint configuration object :param conf: yamllint configuration object
""" """
if type(input) == str: if type(input) in (type(b''), type(u'')): # compat with Python 2 & 3
return _run(input, conf) return _run(input, conf)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
# We need to have everything in memory to parse correctly # We need to have everything in memory to parse correctly

View File

@@ -62,7 +62,7 @@ CONF = {'max': int,
def check(conf, line): def check(conf, line):
if line.start == line.end and line.end < len(line.buffer): if line.start == line.end and line.end < len(line.buffer):
# Only alert on the last blank line of a serie # Only alert on the last blank line of a series
if (line.end < len(line.buffer) - 1 and if (line.end < len(line.buffer) - 1 and
line.buffer[line.end + 1] == '\n'): line.buffer[line.end + 1] == '\n'):
return return

View File

@@ -19,12 +19,16 @@ Use this rule to control the indentation.
.. rubric:: Options .. rubric:: Options
* ``spaces`` defines the number of spaces that represent an indentation level. * ``spaces`` defines the indentation width, in spaces. Set either to an integer
(e.g. ``2`` or ``4``, representing the number of spaces in an indentation
level) or to ``consistent`` to allow any number, as long as it remains the
same within the file.
* ``indent-sequences`` defines whether block sequences should be indented or * ``indent-sequences`` defines whether block sequences should be indented or
not (when in a mapping, this indentation is not mandatory -- some people not (when in a mapping, this indentation is not mandatory -- some people
perceive the ``-`` as part of the indentation). Possible values: ``yes``, perceive the ``-`` as part of the indentation). Possible values: ``yes``,
``no`` and ``whatever`` (the latter means either indenting or not indenting ``no``, ``whatever`` and ``consistent``. ``consistent`` requires either all
block sequences is OK. block sequences to be indented, or none to be. ``whatever`` means either
indenting or not indenting individual block sequences is OK.
* ``check-multi-line-strings`` defines whether to lint indentation in * ``check-multi-line-strings`` defines whether to lint indentation in
multi-line strings. Set to ``yes`` to enable, ``no`` to disable. multi-line strings. Set to ``yes`` to enable, ``no`` to disable.
@@ -73,6 +77,28 @@ Use this rule to control the indentation.
- haystack: - haystack:
needle needle
#. With ``indentation: {spaces: consistent}``
the following code snippet would **PASS**:
::
history:
- name: Unix
date: 1969
- name: Linux
date: 1991
nest:
recurse:
- haystack:
needle
the following code snippet would **FAIL**:
::
some:
Russian:
dolls
#. With ``indentation: {spaces: 2, indent-sequences: no}`` #. With ``indentation: {spaces: 2, indent-sequences: no}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
@@ -104,6 +130,28 @@ Use this rule to control the indentation.
- spaghetti - spaghetti
- sauce - sauce
#. With ``indentation: {spaces: 2, indent-sequences: consistent}``
the following code snippet would **PASS**:
::
- flying:
- spaghetti
- monster
- not flying:
- spaghetti
- sauce
the following code snippet would **FAIL**:
::
- flying:
- spaghetti
- monster
- not flying:
- spaghetti
- sauce
#. With ``indentation: {spaces: 4, check-multi-line-strings: yes}`` #. With ``indentation: {spaces: 4, check-multi-line-strings: yes}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
@@ -150,8 +198,8 @@ from yamllint.rules.common import is_explicit_key, get_real_end_line
ID = 'indentation' ID = 'indentation'
TYPE = 'token' TYPE = 'token'
CONF = {'spaces': int, CONF = {'spaces': (int, 'consistent'),
'indent-sequences': (True, False, 'whatever'), 'indent-sequences': (bool, 'whatever', 'consistent'),
'check-multi-line-strings': bool} 'check-multi-line-strings': bool}
ROOT, 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)
@@ -174,45 +222,53 @@ def check_scalar_indentation(conf, token, context):
if token.start_mark.line == token.end_mark.line: if token.start_mark.line == token.end_mark.line:
return return
if token.plain: def compute_expected_indent(found_indent):
expected_indent = token.start_mark.column def detect_indent(base_indent):
elif token.style in ('"', "'"): if type(context['spaces']) is not int:
expected_indent = token.start_mark.column + 1 context['spaces'] = found_indent - base_indent
elif token.style in ('>', '|'): return base_indent + context['spaces']
if context['stack'][-1].type == B_SEQ:
# - > if token.plain:
# multi return token.start_mark.column
# line elif token.style in ('"', "'"):
expected_indent = token.start_mark.column + conf['spaces'] return token.start_mark.column + 1
elif context['stack'][-1].type == KEY: elif token.style in ('>', '|'):
assert context['stack'][-1].explicit_key if context['stack'][-1].type == B_ENT:
# - ? > # - >
# multi-line # multi
# key # line
# : > return detect_indent(token.start_mark.column)
# multi-line elif context['stack'][-1].type == KEY:
# value assert context['stack'][-1].explicit_key
expected_indent = token.start_mark.column + conf['spaces'] # - ? >
elif context['stack'][-1].type == VAL: # multi-line
if token.start_mark.line + 1 > context['cur_line']: # key
# - key:
# >
# multi
# line
expected_indent = context['stack'][-1].indent + conf['spaces']
elif context['stack'][-2].explicit_key:
# - ? key
# : > # : >
# multi-line # multi-line
# value # value
expected_indent = token.start_mark.column + conf['spaces'] return detect_indent(token.start_mark.column)
elif context['stack'][-1].type == VAL:
if token.start_mark.line + 1 > context['cur_line']:
# - key:
# >
# multi
# line
return detect_indent(context['stack'][-1].indent)
elif context['stack'][-2].explicit_key:
# - ? key
# : >
# multi-line
# value
return detect_indent(token.start_mark.column)
else:
# - key: >
# multi
# line
return detect_indent(context['stack'][-2].indent)
else: else:
# - key: > return detect_indent(context['stack'][-1].indent)
# multi
# line expected_indent = None
expected_indent = context['stack'][-2].indent + conf['spaces']
else:
expected_indent = context['stack'][-1].indent + conf['spaces']
line_no = token.start_mark.line + 1 line_no = token.start_mark.line + 1
@@ -230,16 +286,21 @@ def check_scalar_indentation(conf, token, context):
if token.start_mark.buffer[line_start + indent] == '\n': if token.start_mark.buffer[line_start + indent] == '\n':
continue continue
if expected_indent is None:
expected_indent = compute_expected_indent(indent)
if indent != expected_indent: if indent != expected_indent:
yield LintProblem(line_no, indent + 1, yield LintProblem(line_no, indent + 1,
'wrong indentation: expected %d but found %d' % 'wrong indentation: expected %d but found %d' %
(expected_indent, indent)) (expected_indent, indent))
def check(conf, token, prev, next, nextnext, context): def _check(conf, token, prev, next, nextnext, context):
if 'stack' not in context: if 'stack' not in context:
context['stack'] = [Parent(ROOT, 0)] context['stack'] = [Parent(ROOT, 0)]
context['cur_line'] = -1 context['cur_line'] = -1
context['spaces'] = conf['spaces']
context['indent-sequences'] = conf['indent-sequences']
# Step 1: Lint # Step 1: Lint
@@ -250,6 +311,11 @@ def check(conf, token, prev, next, nextnext, context):
first_in_line = (is_visible and first_in_line = (is_visible and
token.start_mark.line + 1 > context['cur_line']) token.start_mark.line + 1 > context['cur_line'])
def detect_indent(base_indent, next):
if type(context['spaces']) is not int:
context['spaces'] = next.start_mark.column - base_indent
return base_indent + context['spaces']
if first_in_line: if first_in_line:
found_indentation = token.start_mark.column found_indentation = token.start_mark.column
expected = context['stack'][-1].indent expected = context['stack'][-1].indent
@@ -260,7 +326,7 @@ def check(conf, token, prev, next, nextnext, context):
elif (context['stack'][-1].type == KEY and elif (context['stack'][-1].type == KEY and
context['stack'][-1].explicit_key and context['stack'][-1].explicit_key and
not isinstance(token, yaml.ValueToken)): not isinstance(token, yaml.ValueToken)):
expected += conf['spaces'] expected = detect_indent(expected, token)
if found_indentation != expected: if found_indentation != expected:
yield LintProblem(token.start_mark.line + 1, found_indentation + 1, yield LintProblem(token.start_mark.line + 1, found_indentation + 1,
@@ -282,19 +348,18 @@ def check(conf, token, prev, next, nextnext, context):
# Step 2.b: Update state # Step 2.b: Update state
if isinstance(token, yaml.BlockMappingStartToken): if isinstance(token, yaml.BlockMappingStartToken):
# - a: 1
# or
# - ? a
# : 1
# or
# - ?
# a
# : 1
assert isinstance(next, yaml.KeyToken) assert isinstance(next, yaml.KeyToken)
if next.start_mark.line == token.start_mark.line: assert next.start_mark.line == token.start_mark.line
# - a: 1
# b: 2 indent = token.start_mark.column
# or
# - ? a
# : 1
indent = token.start_mark.column
else:
# - ?
# a
# : 1
indent = token.start_mark.column + conf['spaces']
context['stack'].append(Parent(B_MAP, indent)) context['stack'].append(Parent(B_MAP, indent))
@@ -306,7 +371,7 @@ def check(conf, token, prev, next, nextnext, context):
# - { # - {
# a: 1, b: 2 # a: 1, b: 2
# } # }
indent = context['cur_line_indent'] + conf['spaces'] indent = detect_indent(context['cur_line_indent'], next)
context['stack'].append(Parent(F_MAP, indent, context['stack'].append(Parent(F_MAP, indent,
line_indent=context['cur_line_indent'])) line_indent=context['cur_line_indent']))
@@ -314,8 +379,8 @@ def check(conf, token, prev, next, nextnext, context):
elif isinstance(token, yaml.BlockSequenceStartToken): elif isinstance(token, yaml.BlockSequenceStartToken):
# - - a # - - a
# - b # - b
assert next.start_mark.line == token.start_mark.line
assert isinstance(next, yaml.BlockEntryToken) assert isinstance(next, yaml.BlockEntryToken)
assert next.start_mark.line == token.start_mark.line
indent = token.start_mark.column indent = token.start_mark.column
@@ -340,7 +405,7 @@ def check(conf, token, prev, next, nextnext, context):
# - # -
# key: # key:
# value # value
indent = token.start_mark.column + conf['spaces'] indent = detect_indent(token.start_mark.column, next)
context['stack'].append(Parent(B_ENT, indent)) context['stack'].append(Parent(B_ENT, indent))
@@ -352,7 +417,7 @@ def check(conf, token, prev, next, nextnext, context):
# - [ # - [
# a, b # a, b
# ] # ]
indent = context['cur_line_indent'] + conf['spaces'] indent = detect_indent(context['cur_line_indent'], next)
context['stack'].append(Parent(F_SEQ, indent, context['stack'].append(Parent(F_SEQ, indent,
line_indent=context['cur_line_indent'])) line_indent=context['cur_line_indent']))
@@ -390,7 +455,7 @@ def check(conf, token, prev, next, nextnext, context):
# ? k # ? k
# : # :
# value # value
indent = context['stack'][-1].indent + conf['spaces'] indent = detect_indent(context['stack'][-1].indent, next)
elif next.start_mark.line == prev.start_mark.line: elif next.start_mark.line == prev.start_mark.line:
# k: value # k: value
indent = next.start_mark.column indent = next.start_mark.column
@@ -401,37 +466,46 @@ def check(conf, token, prev, next, nextnext, context):
# yaml.scan()ning this: # yaml.scan()ning this:
# '- lib:\n' # '- lib:\n'
# ' - var\n' # ' - var\n'
if conf['indent-sequences'] is False: if context['indent-sequences'] is False:
indent = context['stack'][-1].indent indent = context['stack'][-1].indent
elif conf['indent-sequences'] is True: elif context['indent-sequences'] is True:
indent = context['stack'][-1].indent + conf['spaces'] indent = detect_indent(context['stack'][-1].indent, next)
else: # 'whatever' else: # 'whatever' or 'consistent'
if next.start_mark.column == context['stack'][-1].indent: if next.start_mark.column == context['stack'][-1].indent:
# key: # key:
# - e1 # - e1
# - e2 # - e2
if context['indent-sequences'] == 'consistent':
context['indent-sequences'] = False
indent = context['stack'][-1].indent indent = context['stack'][-1].indent
else: else:
if context['indent-sequences'] == 'consistent':
context['indent-sequences'] = True
# key: # key:
# - e1 # - e1
# - e2 # - e2
indent = context['stack'][-1].indent + conf['spaces'] indent = detect_indent(context['stack'][-1].indent,
next)
else: else:
# k: # k:
# value # value
indent = context['stack'][-1].indent + conf['spaces'] indent = detect_indent(context['stack'][-1].indent, next)
context['stack'].append(Parent(VAL, indent)) context['stack'].append(Parent(VAL, indent))
consumed_current_token = False consumed_current_token = False
while True: while True:
if (context['stack'][-1].type == F_SEQ and if (context['stack'][-1].type == F_SEQ and
isinstance(token, yaml.FlowSequenceEndToken)): isinstance(token, yaml.FlowSequenceEndToken) and
not consumed_current_token):
context['stack'].pop() context['stack'].pop()
consumed_current_token = True
elif (context['stack'][-1].type == F_MAP and elif (context['stack'][-1].type == F_MAP and
isinstance(token, yaml.FlowMappingEndToken)): isinstance(token, yaml.FlowMappingEndToken) and
not consumed_current_token):
context['stack'].pop() context['stack'].pop()
consumed_current_token = True
elif (context['stack'][-1].type in (B_MAP, B_SEQ) and elif (context['stack'][-1].type in (B_MAP, B_SEQ) and
isinstance(token, yaml.BlockEndToken) and isinstance(token, yaml.BlockEndToken) and
@@ -470,3 +544,13 @@ def check(conf, token, prev, next, nextnext, context):
else: else:
break break
def check(conf, token, prev, next, nextnext, context):
try:
for problem in _check(conf, token, prev, next, nextnext, context):
yield problem
except AssertionError:
yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1,
'cannot infer indentation: unexpected token')

View File

@@ -59,7 +59,7 @@ Use this rule to set a limit to lines length.
the following code snippet would **FAIL**: the following code snippet would **FAIL**:
:: ::
- this line is waaaaaaaaaaaaaay too long but could be easily splitted... - this line is waaaaaaaaaaaaaay too long but could be easily split...
#. With ``line-length: {max: 60, allow-non-breakable-words: no}`` #. With ``line-length: {max: 60, allow-non-breakable-words: no}``
@@ -90,7 +90,7 @@ def check(conf, line):
start += 1 start += 1
if start != line.end: if start != line.end:
if line.buffer[start] == '#': if line.buffer[start] in ('#', '-'):
start += 2 start += 2
if line.buffer.find(' ', start, line.end) == -1: if line.buffer.find(' ', start, line.end) == -1: