Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
542ae758f5 | ||
|
|
3a6a09b7b6 | ||
|
|
15aea73fbe | ||
|
|
91763f5476 | ||
|
|
5b049e4229 | ||
|
|
044c7f0248 | ||
|
|
734d5d5f73 | ||
|
|
fd86455076 | ||
|
|
13a0f11e7c | ||
|
|
43b95e99d1 | ||
|
|
8fa9eb3ced | ||
|
|
da3788e95a | ||
|
|
fb400dc64b |
@@ -3,10 +3,10 @@ dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069)
|
|||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- 2.7
|
- 2.7
|
||||||
- 3.4
|
|
||||||
- 3.5
|
- 3.5
|
||||||
- 3.6
|
- 3.6
|
||||||
- 3.7
|
- 3.7
|
||||||
|
- 3.8
|
||||||
- nightly
|
- nightly
|
||||||
install:
|
install:
|
||||||
- pip install pyyaml coveralls flake8 flake8-import-order doc8
|
- pip install pyyaml coveralls flake8 flake8-import-order doc8
|
||||||
|
|||||||
@@ -1,6 +1,24 @@
|
|||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
1.21.0 (2020-03-24)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Fix ``new-lines`` rule on Python 3 with DOS line endings
|
||||||
|
- Fix ``quoted-strings`` rule not working for string values matching scalars
|
||||||
|
- Add ``required: only-when-needed`` option to the ``quoted-strings`` rule
|
||||||
|
|
||||||
|
1.20.0 (2019-12-26)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Add --no-warnings option to suppress warning messages
|
||||||
|
- Use 'syntax' as rule name upon syntax errors
|
||||||
|
|
||||||
|
1.19.0 (2019-11-19)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Allow disabling all checks for a file with ``# yamllint disable-file``
|
||||||
|
|
||||||
1.18.0 (2019-10-15)
|
1.18.0 (2019-10-15)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,9 @@ return code will be:
|
|||||||
* ``1`` if one or more errors occur
|
* ``1`` if one or more errors occur
|
||||||
* ``2`` if no errors occur, but one or more warnings occur
|
* ``2`` if no errors occur, but one or more warnings occur
|
||||||
|
|
||||||
|
If the script is invoked with the ``--no-warnings`` option, it won't output
|
||||||
|
warning level problems, only error level ones.
|
||||||
|
|
||||||
YAML files extensions
|
YAML files extensions
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
|
|||||||
@@ -73,3 +73,31 @@ It is possible, although not recommend, to disabled **all** rules:
|
|||||||
|
|
||||||
If you need to disable multiple rules, it is allowed to chain rules like this:
|
If you need to disable multiple rules, it is allowed to chain rules like this:
|
||||||
``# yamllint disable rule:hyphens rule:commas rule:indentation``.
|
``# yamllint disable rule:hyphens rule:commas rule:indentation``.
|
||||||
|
|
||||||
|
Disabling all checks for a file
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
To prevent yamllint from reporting problems for a specific file, add the
|
||||||
|
directive comment ``# yamllint disable-file`` as the first line of the file.
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# yamllint disable-file
|
||||||
|
# The following mapping contains the same key twice, but I know what I'm doing:
|
||||||
|
key: value 1
|
||||||
|
key: value 2
|
||||||
|
|
||||||
|
- This line is waaaaaaaaaay too long but yamllint will not report anything about it.
|
||||||
|
This line will be checked by yamllint.
|
||||||
|
|
||||||
|
or:
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
# yamllint disable-file
|
||||||
|
# This file is not valid YAML because it is a Jinja template
|
||||||
|
{% if extra_info %}
|
||||||
|
key1: value1
|
||||||
|
{% endif %}
|
||||||
|
key2: value2
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class QuotedTestCase(RuleTestCase):
|
|||||||
|
|
||||||
def test_disabled(self):
|
def test_disabled(self):
|
||||||
conf = 'quoted-strings: disable'
|
conf = 'quoted-strings: disable'
|
||||||
|
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'foo: bar\n', conf)
|
'foo: bar\n', conf)
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
@@ -30,18 +31,23 @@ class QuotedTestCase(RuleTestCase):
|
|||||||
'foo: \'bar\'\n', conf)
|
'foo: \'bar\'\n', conf)
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'bar: 123\n', conf)
|
'bar: 123\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'bar: "123"\n', conf)
|
||||||
|
|
||||||
def test_quote_type_any(self):
|
def test_quote_type_any(self):
|
||||||
conf = 'quoted-strings: {quote-type: any}\n'
|
conf = 'quoted-strings: {quote-type: any}\n'
|
||||||
|
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'boolean1: true\n'
|
'boolean1: true\n'
|
||||||
'number1: 123\n'
|
'number1: 123\n'
|
||||||
'string1: foo\n' # fails
|
'string1: foo\n' # fails
|
||||||
'string2: "foo"\n'
|
'string2: "foo"\n'
|
||||||
'string3: \'bar\'\n'
|
'string3: "true"\n'
|
||||||
'string4: !!str genericstring\n'
|
'string4: "123"\n'
|
||||||
'string5: !!str 456\n'
|
'string5: \'bar\'\n'
|
||||||
'string6: !!str "quotedgenericstring"\n'
|
'string6: !!str genericstring\n'
|
||||||
|
'string7: !!str 456\n'
|
||||||
|
'string8: !!str "quotedgenericstring"\n'
|
||||||
'binary: !!binary binstring\n'
|
'binary: !!binary binstring\n'
|
||||||
'integer: !!int intstring\n'
|
'integer: !!int intstring\n'
|
||||||
'boolean2: !!bool boolstring\n'
|
'boolean2: !!bool boolstring\n'
|
||||||
@@ -55,7 +61,7 @@ class QuotedTestCase(RuleTestCase):
|
|||||||
' word 1\n'
|
' word 1\n'
|
||||||
' word 2\n'
|
' word 2\n'
|
||||||
'multiline string 3:\n'
|
'multiline string 3:\n'
|
||||||
' word 1\n'
|
' word 1\n' # fails
|
||||||
' word 2\n'
|
' word 2\n'
|
||||||
'multiline string 4:\n'
|
'multiline string 4:\n'
|
||||||
' "word 1\\\n'
|
' "word 1\\\n'
|
||||||
@@ -64,20 +70,24 @@ class QuotedTestCase(RuleTestCase):
|
|||||||
|
|
||||||
def test_quote_type_single(self):
|
def test_quote_type_single(self):
|
||||||
conf = 'quoted-strings: {quote-type: single}\n'
|
conf = 'quoted-strings: {quote-type: single}\n'
|
||||||
|
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'boolean1: true\n'
|
'boolean1: true\n'
|
||||||
'number1: 123\n'
|
'number1: 123\n'
|
||||||
'string1: foo\n' # fails
|
'string1: foo\n' # fails
|
||||||
'string2: "foo"\n' # fails
|
'string2: "foo"\n' # fails
|
||||||
'string3: \'bar\'\n'
|
'string3: "true"\n' # fails
|
||||||
'string4: !!str genericstring\n'
|
'string4: "123"\n' # fails
|
||||||
'string5: !!str 456\n'
|
'string5: \'bar\'\n'
|
||||||
'string6: !!str "quotedgenericstring"\n'
|
'string6: !!str genericstring\n'
|
||||||
|
'string7: !!str 456\n'
|
||||||
|
'string8: !!str "quotedgenericstring"\n'
|
||||||
'binary: !!binary binstring\n'
|
'binary: !!binary binstring\n'
|
||||||
'integer: !!int intstring\n'
|
'integer: !!int intstring\n'
|
||||||
'boolean2: !!bool boolstring\n'
|
'boolean2: !!bool boolstring\n'
|
||||||
'boolean3: !!bool "quotedboolstring"\n',
|
'boolean3: !!bool "quotedboolstring"\n',
|
||||||
conf, problem1=(4, 10), problem2=(5, 10))
|
conf, problem1=(4, 10), problem2=(5, 10),
|
||||||
|
problem3=(6, 10), problem4=(7, 10))
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'multiline string 1: |\n'
|
'multiline string 1: |\n'
|
||||||
' line 1\n'
|
' line 1\n'
|
||||||
@@ -86,7 +96,7 @@ class QuotedTestCase(RuleTestCase):
|
|||||||
' word 1\n'
|
' word 1\n'
|
||||||
' word 2\n'
|
' word 2\n'
|
||||||
'multiline string 3:\n'
|
'multiline string 3:\n'
|
||||||
' word 1\n'
|
' word 1\n' # fails
|
||||||
' word 2\n'
|
' word 2\n'
|
||||||
'multiline string 4:\n'
|
'multiline string 4:\n'
|
||||||
' "word 1\\\n'
|
' "word 1\\\n'
|
||||||
@@ -95,20 +105,162 @@ class QuotedTestCase(RuleTestCase):
|
|||||||
|
|
||||||
def test_quote_type_double(self):
|
def test_quote_type_double(self):
|
||||||
conf = 'quoted-strings: {quote-type: double}\n'
|
conf = 'quoted-strings: {quote-type: double}\n'
|
||||||
|
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'boolean1: true\n'
|
'boolean1: true\n'
|
||||||
'number1: 123\n'
|
'number1: 123\n'
|
||||||
'string1: foo\n' # fails
|
'string1: foo\n' # fails
|
||||||
'string2: "foo"\n'
|
'string2: "foo"\n'
|
||||||
'string3: \'bar\'\n' # fails
|
'string3: "true"\n'
|
||||||
'string4: !!str genericstring\n'
|
'string4: "123"\n'
|
||||||
'string5: !!str 456\n'
|
'string5: \'bar\'\n' # fails
|
||||||
'string6: !!str "quotedgenericstring"\n'
|
'string6: !!str genericstring\n'
|
||||||
|
'string7: !!str 456\n'
|
||||||
|
'string8: !!str "quotedgenericstring"\n'
|
||||||
'binary: !!binary binstring\n'
|
'binary: !!binary binstring\n'
|
||||||
'integer: !!int intstring\n'
|
'integer: !!int intstring\n'
|
||||||
'boolean2: !!bool boolstring\n'
|
'boolean2: !!bool boolstring\n'
|
||||||
'boolean3: !!bool "quotedboolstring"\n',
|
'boolean3: !!bool "quotedboolstring"\n',
|
||||||
conf, problem1=(4, 10), problem2=(6, 10))
|
conf, problem1=(4, 10), problem2=(8, 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' # fails
|
||||||
|
' word 2\n'
|
||||||
|
'multiline string 4:\n'
|
||||||
|
' "word 1\\\n'
|
||||||
|
' word 2"\n',
|
||||||
|
conf, problem1=(9, 3))
|
||||||
|
|
||||||
|
def test_disallow_redundant_quotes(self):
|
||||||
|
conf = 'quoted-strings: {required: only-when-needed}\n'
|
||||||
|
|
||||||
|
self.check('---\n'
|
||||||
|
'boolean1: true\n'
|
||||||
|
'number1: 123\n'
|
||||||
|
'string1: foo\n'
|
||||||
|
'string2: "foo"\n' # fails
|
||||||
|
'string3: "true"\n'
|
||||||
|
'string4: "123"\n'
|
||||||
|
'string5: \'bar\'\n' # fails
|
||||||
|
'string6: !!str genericstring\n'
|
||||||
|
'string7: !!str 456\n'
|
||||||
|
'string8: !!str "quotedgenericstring"\n'
|
||||||
|
'binary: !!binary binstring\n'
|
||||||
|
'integer: !!int intstring\n'
|
||||||
|
'boolean2: !!bool boolstring\n'
|
||||||
|
'boolean3: !!bool "quotedboolstring"\n',
|
||||||
|
conf, problem1=(5, 10), problem2=(8, 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' # fails
|
||||||
|
' word 2"\n',
|
||||||
|
conf, problem1=(12, 3))
|
||||||
|
|
||||||
|
def test_disallow_redundant_single_quotes(self):
|
||||||
|
conf = 'quoted-strings: {quote-type: single, ' + \
|
||||||
|
'required: only-when-needed}\n'
|
||||||
|
|
||||||
|
self.check('---\n'
|
||||||
|
'boolean1: true\n'
|
||||||
|
'number1: 123\n'
|
||||||
|
'string1: foo\n'
|
||||||
|
'string2: "foo"\n' # fails
|
||||||
|
'string3: "true"\n' # fails
|
||||||
|
'string4: "123"\n' # fails
|
||||||
|
'string5: \'bar\'\n' # fails
|
||||||
|
'string6: !!str genericstring\n'
|
||||||
|
'string7: !!str 456\n'
|
||||||
|
'string8: !!str "quotedgenericstring"\n'
|
||||||
|
'binary: !!binary binstring\n'
|
||||||
|
'integer: !!int intstring\n'
|
||||||
|
'boolean2: !!bool boolstring\n'
|
||||||
|
'boolean3: !!bool "quotedboolstring"\n',
|
||||||
|
conf, problem1=(5, 10), problem2=(6, 10),
|
||||||
|
problem3=(7, 10), problem4=(8, 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' # fails
|
||||||
|
' word 2"\n',
|
||||||
|
conf, problem1=(12, 3))
|
||||||
|
|
||||||
|
def test_single_quotes_required(self):
|
||||||
|
conf = 'quoted-strings: {quote-type: single, required: true}\n'
|
||||||
|
|
||||||
|
self.check('---\n'
|
||||||
|
'boolean1: true\n'
|
||||||
|
'number1: 123\n'
|
||||||
|
'string1: foo\n' # fails
|
||||||
|
'string2: "foo"\n' # fails
|
||||||
|
'string3: "true"\n' # fails
|
||||||
|
'string4: "123"\n' # fails
|
||||||
|
'string5: \'bar\'\n'
|
||||||
|
'string6: !!str genericstring\n'
|
||||||
|
'string7: !!str 456\n'
|
||||||
|
'string8: !!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),
|
||||||
|
problem3=(6, 10), problem4=(7, 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' # fails
|
||||||
|
' word 2\n'
|
||||||
|
'multiline string 4:\n'
|
||||||
|
' "word 1\\\n' # fails
|
||||||
|
' word 2"\n',
|
||||||
|
conf, problem1=(9, 3), problem2=(12, 3))
|
||||||
|
|
||||||
|
def test_any_quotes_relaxed(self):
|
||||||
|
conf = 'quoted-strings: {quote-type: any, required: false}\n'
|
||||||
|
|
||||||
|
self.check('---\n'
|
||||||
|
'boolean1: true\n'
|
||||||
|
'number1: 123\n'
|
||||||
|
'string1: foo\n'
|
||||||
|
'string2: "foo"\n'
|
||||||
|
'string3: "true"\n'
|
||||||
|
'string4: "123"\n'
|
||||||
|
'string5: \'bar\'\n'
|
||||||
|
'string6: !!str genericstring\n'
|
||||||
|
'string7: !!str 456\n'
|
||||||
|
'string8: !!str "quotedgenericstring"\n'
|
||||||
|
'binary: !!binary binstring\n'
|
||||||
|
'integer: !!int intstring\n'
|
||||||
|
'boolean2: !!bool boolstring\n'
|
||||||
|
'boolean3: !!bool "quotedboolstring"\n',
|
||||||
|
conf)
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'multiline string 1: |\n'
|
'multiline string 1: |\n'
|
||||||
' line 1\n'
|
' line 1\n'
|
||||||
@@ -122,4 +274,73 @@ class QuotedTestCase(RuleTestCase):
|
|||||||
'multiline string 4:\n'
|
'multiline string 4:\n'
|
||||||
' "word 1\\\n'
|
' "word 1\\\n'
|
||||||
' word 2"\n',
|
' word 2"\n',
|
||||||
|
conf)
|
||||||
|
|
||||||
|
def test_single_quotes_relaxed(self):
|
||||||
|
conf = 'quoted-strings: {quote-type: single, required: false}\n'
|
||||||
|
|
||||||
|
self.check('---\n'
|
||||||
|
'boolean1: true\n'
|
||||||
|
'number1: 123\n'
|
||||||
|
'string1: foo\n'
|
||||||
|
'string2: "foo"\n' # fails
|
||||||
|
'string3: "true"\n' # fails
|
||||||
|
'string4: "123"\n' # fails
|
||||||
|
'string5: \'bar\'\n'
|
||||||
|
'string6: !!str genericstring\n'
|
||||||
|
'string7: !!str 456\n'
|
||||||
|
'string8: !!str "quotedgenericstring"\n'
|
||||||
|
'binary: !!binary binstring\n'
|
||||||
|
'integer: !!int intstring\n'
|
||||||
|
'boolean2: !!bool boolstring\n'
|
||||||
|
'boolean3: !!bool "quotedboolstring"\n',
|
||||||
|
conf, problem2=(5, 10),
|
||||||
|
problem3=(6, 10), problem4=(7, 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' # fails
|
||||||
|
' word 2"\n',
|
||||||
|
conf, problem1=(12, 3))
|
||||||
|
|
||||||
|
def test_quotes_required(self):
|
||||||
|
conf = 'quoted-strings: {quote-type: any, required: true}\n'
|
||||||
|
|
||||||
|
self.check('---\n'
|
||||||
|
'boolean1: true\n'
|
||||||
|
'number1: 123\n'
|
||||||
|
'string1: foo\n' # fails
|
||||||
|
'string2: "foo"\n'
|
||||||
|
'string3: "true"\n'
|
||||||
|
'string4: "123"\n'
|
||||||
|
'string5: \'bar\'\n'
|
||||||
|
'string6: !!str genericstring\n'
|
||||||
|
'string7: !!str 456\n'
|
||||||
|
'string8: !!str "quotedgenericstring"\n'
|
||||||
|
'binary: !!binary binstring\n'
|
||||||
|
'integer: !!int intstring\n'
|
||||||
|
'boolean2: !!bool boolstring\n'
|
||||||
|
'boolean3: !!bool "quotedboolstring"\n',
|
||||||
|
conf, problem2=(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' # fails
|
||||||
|
' word 2\n'
|
||||||
|
'multiline string 4:\n'
|
||||||
|
' "word 1\\\n'
|
||||||
|
' word 2"\n',
|
||||||
conf, problem1=(9, 3))
|
conf, problem1=(9, 3))
|
||||||
|
|||||||
@@ -32,6 +32,29 @@ from yamllint import cli
|
|||||||
from yamllint import config
|
from yamllint import config
|
||||||
|
|
||||||
|
|
||||||
|
class RunContext(object):
|
||||||
|
"""Context manager for ``cli.run()`` to capture exit code and streams."""
|
||||||
|
|
||||||
|
def __init__(self, case):
|
||||||
|
self.stdout = self.stderr = None
|
||||||
|
self._raises_ctx = case.assertRaises(SystemExit)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self._raises_ctx.__enter__()
|
||||||
|
sys.stdout = self.outstream = StringIO()
|
||||||
|
sys.stderr = self.errstream = StringIO()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *exc_info):
|
||||||
|
self.stdout, sys.stdout = self.outstream.getvalue(), sys.__stdout__
|
||||||
|
self.stderr, sys.stderr = self.errstream.getvalue(), sys.__stderr__
|
||||||
|
return self._raises_ctx.__exit__(*exc_info)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def returncode(self):
|
||||||
|
return self._raises_ctx.exception.code
|
||||||
|
|
||||||
|
|
||||||
class CommandLineTestCase(unittest.TestCase):
|
class CommandLineTestCase(unittest.TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
@@ -59,12 +82,15 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
'no-yaml.json': '---\n'
|
'no-yaml.json': '---\n'
|
||||||
'key: value\n',
|
'key: value\n',
|
||||||
# non-ASCII chars
|
# non-ASCII chars
|
||||||
'non-ascii/utf-8': (
|
'non-ascii/éçäγλνπ¥/utf-8': (
|
||||||
u'---\n'
|
u'---\n'
|
||||||
u'- hétérogénéité\n'
|
u'- hétérogénéité\n'
|
||||||
u'# 19.99 €\n'
|
u'# 19.99 €\n'
|
||||||
u'- お早う御座います。\n'
|
u'- お早う御座います。\n'
|
||||||
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
|
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
|
||||||
|
# dos line endings yaml
|
||||||
|
'dos.yml': '---\r\n'
|
||||||
|
'dos: true',
|
||||||
})
|
})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -78,6 +104,7 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sorted(cli.find_files_recursively([self.wd], conf)),
|
sorted(cli.find_files_recursively([self.wd], conf)),
|
||||||
[os.path.join(self.wd, 'a.yaml'),
|
[os.path.join(self.wd, 'a.yaml'),
|
||||||
|
os.path.join(self.wd, 'dos.yml'),
|
||||||
os.path.join(self.wd, 'empty.yml'),
|
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, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
|
||||||
os.path.join(self.wd, 'sub/ok.yaml'),
|
os.path.join(self.wd, 'sub/ok.yaml'),
|
||||||
@@ -123,7 +150,8 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
' - \'*.yml\'\n')
|
' - \'*.yml\'\n')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sorted(cli.find_files_recursively([self.wd], conf)),
|
sorted(cli.find_files_recursively([self.wd], conf)),
|
||||||
[os.path.join(self.wd, 'empty.yml')]
|
[os.path.join(self.wd, 'dos.yml'),
|
||||||
|
os.path.join(self.wd, 'empty.yml')]
|
||||||
)
|
)
|
||||||
|
|
||||||
conf = config.YamlLintConfig('extends: default\n'
|
conf = config.YamlLintConfig('extends: default\n'
|
||||||
@@ -140,9 +168,10 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sorted(cli.find_files_recursively([self.wd], conf)),
|
sorted(cli.find_files_recursively([self.wd], conf)),
|
||||||
[os.path.join(self.wd, 'a.yaml'),
|
[os.path.join(self.wd, 'a.yaml'),
|
||||||
|
os.path.join(self.wd, 'dos.yml'),
|
||||||
os.path.join(self.wd, 'empty.yml'),
|
os.path.join(self.wd, 'empty.yml'),
|
||||||
os.path.join(self.wd, 'no-yaml.json'),
|
os.path.join(self.wd, 'no-yaml.json'),
|
||||||
os.path.join(self.wd, 'non-ascii/utf-8'),
|
os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8'),
|
||||||
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
|
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
|
||||||
os.path.join(self.wd, 'sub/ok.yaml'),
|
os.path.join(self.wd, 'sub/ok.yaml'),
|
||||||
os.path.join(self.wd, 'warn.yaml')]
|
os.path.join(self.wd, 'warn.yaml')]
|
||||||
@@ -156,9 +185,10 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sorted(cli.find_files_recursively([self.wd], conf)),
|
sorted(cli.find_files_recursively([self.wd], conf)),
|
||||||
[os.path.join(self.wd, 'a.yaml'),
|
[os.path.join(self.wd, 'a.yaml'),
|
||||||
|
os.path.join(self.wd, 'dos.yml'),
|
||||||
os.path.join(self.wd, 'empty.yml'),
|
os.path.join(self.wd, 'empty.yml'),
|
||||||
os.path.join(self.wd, 'no-yaml.json'),
|
os.path.join(self.wd, 'no-yaml.json'),
|
||||||
os.path.join(self.wd, 'non-ascii/utf-8'),
|
os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8'),
|
||||||
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
|
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
|
||||||
os.path.join(self.wd, 'sub/ok.yaml'),
|
os.path.join(self.wd, 'sub/ok.yaml'),
|
||||||
os.path.join(self.wd, 'warn.yaml')]
|
os.path.join(self.wd, 'warn.yaml')]
|
||||||
@@ -170,205 +200,148 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
' - \'**/utf-8\'\n')
|
' - \'**/utf-8\'\n')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sorted(cli.find_files_recursively([self.wd], conf)),
|
sorted(cli.find_files_recursively([self.wd], conf)),
|
||||||
[os.path.join(self.wd, 'non-ascii/utf-8')]
|
[os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8')]
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_run_with_bad_arguments(self):
|
def test_run_with_bad_arguments(self):
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
|
||||||
cli.run(())
|
cli.run(())
|
||||||
|
self.assertNotEqual(ctx.returncode, 0)
|
||||||
|
self.assertEqual(ctx.stdout, '')
|
||||||
|
self.assertRegexpMatches(ctx.stderr, r'^usage')
|
||||||
|
|
||||||
self.assertNotEqual(ctx.exception.code, 0)
|
with RunContext(self) as ctx:
|
||||||
|
|
||||||
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', ))
|
cli.run(('--unknown-arg', ))
|
||||||
|
self.assertNotEqual(ctx.returncode, 0)
|
||||||
|
self.assertEqual(ctx.stdout, '')
|
||||||
|
self.assertRegexpMatches(ctx.stderr, r'^usage')
|
||||||
|
|
||||||
self.assertNotEqual(ctx.exception.code, 0)
|
with RunContext(self) as ctx:
|
||||||
|
|
||||||
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'))
|
cli.run(('-c', './conf.yaml', '-d', 'relaxed', 'file'))
|
||||||
|
self.assertNotEqual(ctx.returncode, 0)
|
||||||
self.assertNotEqual(ctx.exception.code, 0)
|
self.assertEqual(ctx.stdout, '')
|
||||||
|
|
||||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
|
||||||
self.assertEqual(out, '')
|
|
||||||
self.assertRegexpMatches(
|
self.assertRegexpMatches(
|
||||||
err.splitlines()[-1],
|
ctx.stderr.splitlines()[-1],
|
||||||
r'^yamllint: error: argument -d\/--config-data: '
|
r'^yamllint: error: argument -d\/--config-data: '
|
||||||
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
|
# checks if reading from stdin and files are mutually exclusive
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
|
||||||
cli.run(('-', 'file'))
|
cli.run(('-', 'file'))
|
||||||
|
self.assertNotEqual(ctx.returncode, 0)
|
||||||
self.assertNotEqual(ctx.exception.code, 0)
|
self.assertEqual(ctx.stdout, '')
|
||||||
|
self.assertRegexpMatches(ctx.stderr, r'^usage')
|
||||||
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()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
|
||||||
cli.run(('-d', 'rules: {a: b}', 'file'))
|
cli.run(('-d', 'rules: {a: b}', 'file'))
|
||||||
|
self.assertEqual(ctx.returncode, -1)
|
||||||
self.assertEqual(ctx.exception.code, -1)
|
self.assertEqual(ctx.stdout, '')
|
||||||
|
self.assertRegexpMatches(ctx.stderr, r'^invalid config: no such rule')
|
||||||
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):
|
def test_run_with_empty_config(self):
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
|
||||||
cli.run(('-d', '', 'file'))
|
cli.run(('-d', '', 'file'))
|
||||||
|
self.assertEqual(ctx.returncode, -1)
|
||||||
self.assertEqual(ctx.exception.code, -1)
|
self.assertEqual(ctx.stdout, '')
|
||||||
|
self.assertRegexpMatches(ctx.stderr, r'^invalid config: not a dict')
|
||||||
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):
|
def test_run_with_config_file(self):
|
||||||
with open(os.path.join(self.wd, 'config'), 'w') as f:
|
with open(os.path.join(self.wd, 'config'), 'w') as f:
|
||||||
f.write('rules: {trailing-spaces: disable}')
|
f.write('rules: {trailing-spaces: disable}')
|
||||||
|
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
with RunContext(self) as ctx:
|
||||||
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
|
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
|
||||||
self.assertEqual(ctx.exception.code, 0)
|
self.assertEqual(ctx.returncode, 0)
|
||||||
|
|
||||||
with open(os.path.join(self.wd, 'config'), 'w') as f:
|
with open(os.path.join(self.wd, 'config'), 'w') as f:
|
||||||
f.write('rules: {trailing-spaces: enable}')
|
f.write('rules: {trailing-spaces: enable}')
|
||||||
|
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
with RunContext(self) as ctx:
|
||||||
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
|
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
|
||||||
self.assertEqual(ctx.exception.code, 1)
|
self.assertEqual(ctx.returncode, 1)
|
||||||
|
|
||||||
def test_run_with_user_global_config_file(self):
|
def test_run_with_user_global_config_file(self):
|
||||||
home = os.path.join(self.wd, 'fake-home')
|
home = os.path.join(self.wd, 'fake-home')
|
||||||
os.mkdir(home)
|
dir = os.path.join(home, '.config', 'yamllint')
|
||||||
dir = os.path.join(home, '.config')
|
os.makedirs(dir)
|
||||||
os.mkdir(dir)
|
|
||||||
dir = os.path.join(dir, 'yamllint')
|
|
||||||
os.mkdir(dir)
|
|
||||||
config = os.path.join(dir, 'config')
|
config = os.path.join(dir, 'config')
|
||||||
|
|
||||||
temp = os.environ['HOME']
|
self.addCleanup(os.environ.update, HOME=os.environ['HOME'])
|
||||||
os.environ['HOME'] = home
|
os.environ['HOME'] = home
|
||||||
|
|
||||||
with open(config, 'w') as f:
|
with open(config, 'w') as f:
|
||||||
f.write('rules: {trailing-spaces: disable}')
|
f.write('rules: {trailing-spaces: disable}')
|
||||||
|
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
with RunContext(self) as ctx:
|
||||||
cli.run((os.path.join(self.wd, 'a.yaml'), ))
|
cli.run((os.path.join(self.wd, 'a.yaml'), ))
|
||||||
self.assertEqual(ctx.exception.code, 0)
|
self.assertEqual(ctx.returncode, 0)
|
||||||
|
|
||||||
with open(config, 'w') as f:
|
with open(config, 'w') as f:
|
||||||
f.write('rules: {trailing-spaces: enable}')
|
f.write('rules: {trailing-spaces: enable}')
|
||||||
|
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
with RunContext(self) as ctx:
|
||||||
cli.run((os.path.join(self.wd, 'a.yaml'), ))
|
cli.run((os.path.join(self.wd, 'a.yaml'), ))
|
||||||
self.assertEqual(ctx.exception.code, 1)
|
self.assertEqual(ctx.returncode, 1)
|
||||||
|
|
||||||
os.environ['HOME'] = temp
|
|
||||||
|
|
||||||
def test_run_version(self):
|
def test_run_version(self):
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
|
||||||
cli.run(('--version', ))
|
cli.run(('--version', ))
|
||||||
|
self.assertEqual(ctx.returncode, 0)
|
||||||
self.assertEqual(ctx.exception.code, 0)
|
self.assertRegexpMatches(ctx.stdout + ctx.stderr, r'yamllint \d+\.\d+')
|
||||||
|
|
||||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
|
||||||
self.assertRegexpMatches(out + err, r'yamllint \d+\.\d+')
|
|
||||||
|
|
||||||
def test_run_non_existing_file(self):
|
def test_run_non_existing_file(self):
|
||||||
file = os.path.join(self.wd, 'i-do-not-exist.yaml')
|
path = os.path.join(self.wd, 'i-do-not-exist.yaml')
|
||||||
|
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
cli.run(('-f', 'parsable', path))
|
||||||
cli.run(('-f', 'parsable', file))
|
self.assertEqual(ctx.returncode, -1)
|
||||||
|
self.assertEqual(ctx.stdout, '')
|
||||||
self.assertEqual(ctx.exception.code, -1)
|
self.assertRegexpMatches(ctx.stderr, r'No such file or directory')
|
||||||
|
|
||||||
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):
|
def test_run_one_problem_file(self):
|
||||||
file = os.path.join(self.wd, 'a.yaml')
|
path = os.path.join(self.wd, 'a.yaml')
|
||||||
|
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
cli.run(('-f', 'parsable', path))
|
||||||
cli.run(('-f', 'parsable', file))
|
self.assertEqual(ctx.returncode, 1)
|
||||||
|
self.assertEqual(ctx.stdout, (
|
||||||
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:2:4: [error] trailing spaces (trailing-spaces)\n'
|
||||||
'%s:3:4: [error] no new line character at the end of file '
|
'%s:3:4: [error] no new line character at the end of file '
|
||||||
'(new-line-at-end-of-file)\n') % (file, file))
|
'(new-line-at-end-of-file)\n' % (path, path)))
|
||||||
self.assertEqual(err, '')
|
self.assertEqual(ctx.stderr, '')
|
||||||
|
|
||||||
def test_run_one_warning(self):
|
def test_run_one_warning(self):
|
||||||
file = os.path.join(self.wd, 'warn.yaml')
|
path = os.path.join(self.wd, 'warn.yaml')
|
||||||
|
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
cli.run(('-f', 'parsable', path))
|
||||||
cli.run(('-f', 'parsable', file))
|
self.assertEqual(ctx.returncode, 0)
|
||||||
|
|
||||||
self.assertEqual(ctx.exception.code, 0)
|
|
||||||
|
|
||||||
def test_run_warning_in_strict_mode(self):
|
def test_run_warning_in_strict_mode(self):
|
||||||
file = os.path.join(self.wd, 'warn.yaml')
|
path = os.path.join(self.wd, 'warn.yaml')
|
||||||
|
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
cli.run(('-f', 'parsable', '--strict', path))
|
||||||
cli.run(('-f', 'parsable', '--strict', file))
|
self.assertEqual(ctx.returncode, 2)
|
||||||
|
|
||||||
self.assertEqual(ctx.exception.code, 2)
|
|
||||||
|
|
||||||
def test_run_one_ok_file(self):
|
def test_run_one_ok_file(self):
|
||||||
file = os.path.join(self.wd, 'sub', 'ok.yaml')
|
path = os.path.join(self.wd, 'sub', 'ok.yaml')
|
||||||
|
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
cli.run(('-f', 'parsable', path))
|
||||||
cli.run(('-f', 'parsable', file))
|
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
|
||||||
|
|
||||||
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):
|
def test_run_empty_file(self):
|
||||||
file = os.path.join(self.wd, 'empty.yml')
|
path = os.path.join(self.wd, 'empty.yml')
|
||||||
|
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
cli.run(('-f', 'parsable', path))
|
||||||
cli.run(('-f', 'parsable', file))
|
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
|
||||||
|
|
||||||
self.assertEqual(ctx.exception.code, 0)
|
|
||||||
|
|
||||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
|
||||||
self.assertEqual(out, '')
|
|
||||||
self.assertEqual(err, '')
|
|
||||||
|
|
||||||
def test_run_non_ascii_file(self):
|
def test_run_non_ascii_file(self):
|
||||||
file = os.path.join(self.wd, 'non-ascii', 'utf-8')
|
path = os.path.join(self.wd, 'non-ascii', 'éçäγλνπ¥', 'utf-8')
|
||||||
|
|
||||||
# Make sure the default localization conditions on this "system"
|
# Make sure the default localization conditions on this "system"
|
||||||
# support UTF-8 encoding.
|
# support UTF-8 encoding.
|
||||||
@@ -377,63 +350,46 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
|
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
|
||||||
except locale.Error:
|
except locale.Error:
|
||||||
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
|
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
|
||||||
|
self.addCleanup(locale.setlocale, locale.LC_ALL, loc)
|
||||||
|
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
cli.run(('-f', 'parsable', path))
|
||||||
cli.run(('-f', 'parsable', file))
|
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
|
||||||
|
|
||||||
locale.setlocale(locale.LC_ALL, loc)
|
|
||||||
|
|
||||||
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):
|
def test_run_multiple_files(self):
|
||||||
items = [os.path.join(self.wd, 'empty.yml'),
|
items = [os.path.join(self.wd, 'empty.yml'),
|
||||||
os.path.join(self.wd, 's')]
|
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'
|
path = 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 RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
|
||||||
cli.run(['-f', 'parsable'] + items)
|
cli.run(['-f', 'parsable'] + items)
|
||||||
|
self.assertEqual((ctx.returncode, ctx.stderr), (1, ''))
|
||||||
self.assertEqual(ctx.exception.code, 1)
|
self.assertEqual(ctx.stdout, (
|
||||||
|
|
||||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
|
||||||
self.assertEqual(out, (
|
|
||||||
'%s:3:1: [error] duplication of key "key" in mapping '
|
'%s:3:1: [error] duplication of key "key" in mapping '
|
||||||
'(key-duplicates)\n') % file)
|
'(key-duplicates)\n') % path)
|
||||||
self.assertEqual(err, '')
|
|
||||||
|
|
||||||
def test_run_piped_output_nocolor(self):
|
def test_run_piped_output_nocolor(self):
|
||||||
file = os.path.join(self.wd, 'a.yaml')
|
path = os.path.join(self.wd, 'a.yaml')
|
||||||
|
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
cli.run((path, ))
|
||||||
cli.run((file, ))
|
self.assertEqual((ctx.returncode, ctx.stderr), (1, ''))
|
||||||
|
self.assertEqual(ctx.stdout, (
|
||||||
self.assertEqual(ctx.exception.code, 1)
|
|
||||||
|
|
||||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
|
||||||
self.assertEqual(out, (
|
|
||||||
'%s\n'
|
'%s\n'
|
||||||
' 2:4 error trailing spaces (trailing-spaces)\n'
|
' 2:4 error trailing spaces (trailing-spaces)\n'
|
||||||
' 3:4 error no new line character at the end of file '
|
' 3:4 error no new line character at the end of file '
|
||||||
'(new-line-at-end-of-file)\n'
|
'(new-line-at-end-of-file)\n'
|
||||||
'\n' % file))
|
'\n' % path))
|
||||||
self.assertEqual(err, '')
|
|
||||||
|
|
||||||
def test_run_default_format_output_in_tty(self):
|
def test_run_default_format_output_in_tty(self):
|
||||||
file = os.path.join(self.wd, 'a.yaml')
|
path = 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
|
||||||
master, slave = pty.openpty()
|
master, slave = pty.openpty()
|
||||||
sys.stdout = sys.stderr = os.fdopen(slave, 'w')
|
sys.stdout = sys.stderr = os.fdopen(slave, 'w')
|
||||||
|
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
with self.assertRaises(SystemExit) as ctx:
|
||||||
cli.run((file, ))
|
cli.run((path, ))
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
self.assertEqual(ctx.exception.code, 1)
|
self.assertEqual(ctx.exception.code, 1)
|
||||||
@@ -456,79 +412,108 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
' \033[2m3:4\033[0m \033[31merror\033[0m '
|
' \033[2m3:4\033[0m \033[31merror\033[0m '
|
||||||
'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' % path))
|
||||||
|
|
||||||
def test_run_default_format_output_without_tty(self):
|
def test_run_default_format_output_without_tty(self):
|
||||||
file = os.path.join(self.wd, 'a.yaml')
|
path = os.path.join(self.wd, 'a.yaml')
|
||||||
|
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
cli.run((path, ))
|
||||||
cli.run((file, ))
|
expected_out = (
|
||||||
|
|
||||||
self.assertEqual(ctx.exception.code, 1)
|
|
||||||
|
|
||||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
|
||||||
self.assertEqual(out, (
|
|
||||||
'%s\n'
|
'%s\n'
|
||||||
' 2:4 error trailing spaces (trailing-spaces)\n'
|
' 2:4 error trailing spaces (trailing-spaces)\n'
|
||||||
' 3:4 error no new line character at the end of file '
|
' 3:4 error no new line character at the end of file '
|
||||||
'(new-line-at-end-of-file)\n'
|
'(new-line-at-end-of-file)\n'
|
||||||
'\n' % file))
|
'\n' % path)
|
||||||
self.assertEqual(err, '')
|
self.assertEqual(
|
||||||
|
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
|
||||||
|
|
||||||
def test_run_auto_output_without_tty_output(self):
|
def test_run_auto_output_without_tty_output(self):
|
||||||
file = os.path.join(self.wd, 'a.yaml')
|
path = os.path.join(self.wd, 'a.yaml')
|
||||||
|
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
cli.run((path, '--format', 'auto'))
|
||||||
cli.run((file, '--format', 'auto'))
|
expected_out = (
|
||||||
|
|
||||||
self.assertEqual(ctx.exception.code, 1)
|
|
||||||
|
|
||||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
|
||||||
self.assertEqual(out, (
|
|
||||||
'%s\n'
|
'%s\n'
|
||||||
' 2:4 error trailing spaces (trailing-spaces)\n'
|
' 2:4 error trailing spaces (trailing-spaces)\n'
|
||||||
' 3:4 error no new line character at the end of file '
|
' 3:4 error no new line character at the end of file '
|
||||||
'(new-line-at-end-of-file)\n'
|
'(new-line-at-end-of-file)\n'
|
||||||
'\n' % file))
|
'\n' % path)
|
||||||
self.assertEqual(err, '')
|
self.assertEqual(
|
||||||
|
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
|
||||||
|
|
||||||
def test_run_format_colored(self):
|
def test_run_format_colored(self):
|
||||||
file = os.path.join(self.wd, 'a.yaml')
|
path = os.path.join(self.wd, 'a.yaml')
|
||||||
|
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
with RunContext(self) as ctx:
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
cli.run((path, '--format', 'colored'))
|
||||||
cli.run((file, '--format', 'colored'))
|
expected_out = (
|
||||||
|
|
||||||
self.assertEqual(ctx.exception.code, 1)
|
|
||||||
|
|
||||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
|
||||||
self.assertEqual(out, (
|
|
||||||
'\033[4m%s\033[0m\n'
|
'\033[4m%s\033[0m\n'
|
||||||
' \033[2m2:4\033[0m \033[31merror\033[0m '
|
' \033[2m2:4\033[0m \033[31merror\033[0m '
|
||||||
'trailing spaces \033[2m(trailing-spaces)\033[0m\n'
|
'trailing spaces \033[2m(trailing-spaces)\033[0m\n'
|
||||||
' \033[2m3:4\033[0m \033[31merror\033[0m '
|
' \033[2m3:4\033[0m \033[31merror\033[0m '
|
||||||
'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' % path)
|
||||||
self.assertEqual(err, '')
|
self.assertEqual(
|
||||||
|
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
|
||||||
|
|
||||||
def test_run_read_from_stdin(self):
|
def test_run_read_from_stdin(self):
|
||||||
# prepares stdin with an invalid yaml string so that we can check
|
# prepares stdin with an invalid yaml string so that we can check
|
||||||
# for its specific error, and be assured that stdin was read
|
# for its specific error, and be assured that stdin was read
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
self.addCleanup(setattr, sys, 'stdin', sys.__stdin__)
|
||||||
sys.stdin = StringIO(
|
sys.stdin = StringIO(
|
||||||
'I am a string\n'
|
'I am a string\n'
|
||||||
'therefore: I am an error\n')
|
'therefore: I am an error\n')
|
||||||
|
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
with RunContext(self) as ctx:
|
||||||
cli.run(('-', '-f', 'parsable'))
|
cli.run(('-', '-f', 'parsable'))
|
||||||
|
expected_out = (
|
||||||
self.assertNotEqual(ctx.exception.code, 0)
|
|
||||||
|
|
||||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
|
||||||
self.assertEqual(out, (
|
|
||||||
'stdin:2:10: [error] syntax error: '
|
'stdin:2:10: [error] syntax error: '
|
||||||
'mapping values are not allowed here\n'))
|
'mapping values are not allowed here (syntax)\n')
|
||||||
self.assertEqual(err, '')
|
self.assertEqual(
|
||||||
|
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
|
||||||
|
|
||||||
|
def test_run_no_warnings(self):
|
||||||
|
path = os.path.join(self.wd, 'a.yaml')
|
||||||
|
|
||||||
|
with RunContext(self) as ctx:
|
||||||
|
cli.run((path, '--no-warnings', '-f', 'auto'))
|
||||||
|
expected_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' % path)
|
||||||
|
self.assertEqual(
|
||||||
|
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
|
||||||
|
|
||||||
|
path = os.path.join(self.wd, 'warn.yaml')
|
||||||
|
|
||||||
|
with RunContext(self) as ctx:
|
||||||
|
cli.run((path, '--no-warnings', '-f', 'auto'))
|
||||||
|
self.assertEqual(ctx.returncode, 0)
|
||||||
|
|
||||||
|
def test_run_no_warnings_and_strict(self):
|
||||||
|
path = os.path.join(self.wd, 'warn.yaml')
|
||||||
|
|
||||||
|
with RunContext(self) as ctx:
|
||||||
|
cli.run((path, '--no-warnings', '-s'))
|
||||||
|
self.assertEqual(ctx.returncode, 2)
|
||||||
|
|
||||||
|
def test_run_non_universal_newline(self):
|
||||||
|
path = os.path.join(self.wd, 'dos.yml')
|
||||||
|
|
||||||
|
with RunContext(self) as ctx:
|
||||||
|
cli.run(('-d', 'rules:\n new-lines:\n type: dos', path))
|
||||||
|
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
|
||||||
|
|
||||||
|
with RunContext(self) as ctx:
|
||||||
|
cli.run(('-d', 'rules:\n new-lines:\n type: unix', path))
|
||||||
|
expected_out = (
|
||||||
|
'%s\n'
|
||||||
|
' 1:4 error wrong new line character: expected \\n'
|
||||||
|
' (new-lines)\n'
|
||||||
|
'\n' % path)
|
||||||
|
self.assertEqual(
|
||||||
|
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
|
||||||
|
|||||||
@@ -302,3 +302,104 @@ class YamllintDirectivesTestCase(RuleTestCase):
|
|||||||
' c: [x]\n',
|
' c: [x]\n',
|
||||||
conf,
|
conf,
|
||||||
problem=(6, 2, 'comments-indentation'))
|
problem=(6, 2, 'comments-indentation'))
|
||||||
|
|
||||||
|
def test_disable_file_directive(self):
|
||||||
|
conf = ('comments: {min-spaces-from-content: 2}\n'
|
||||||
|
'comments-indentation: {}\n')
|
||||||
|
self.check('# yamllint disable-file\n'
|
||||||
|
'---\n'
|
||||||
|
'- a: 1 # comment too close\n'
|
||||||
|
' b:\n'
|
||||||
|
' # wrong indentation\n'
|
||||||
|
' c: [x]\n',
|
||||||
|
conf)
|
||||||
|
self.check('# yamllint disable-file\n'
|
||||||
|
'---\n'
|
||||||
|
'- a: 1 # comment too close\n'
|
||||||
|
' b:\n'
|
||||||
|
' # wrong indentation\n'
|
||||||
|
' c: [x]\n',
|
||||||
|
conf)
|
||||||
|
self.check('#yamllint disable-file\n'
|
||||||
|
'---\n'
|
||||||
|
'- a: 1 # comment too close\n'
|
||||||
|
' b:\n'
|
||||||
|
' # wrong indentation\n'
|
||||||
|
' c: [x]\n',
|
||||||
|
conf)
|
||||||
|
self.check('#yamllint disable-file \n'
|
||||||
|
'---\n'
|
||||||
|
'- a: 1 # comment too close\n'
|
||||||
|
' b:\n'
|
||||||
|
' # wrong indentation\n'
|
||||||
|
' c: [x]\n',
|
||||||
|
conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'# yamllint disable-file\n'
|
||||||
|
'- a: 1 # comment too close\n'
|
||||||
|
' b:\n'
|
||||||
|
' # wrong indentation\n'
|
||||||
|
' c: [x]\n',
|
||||||
|
conf,
|
||||||
|
problem1=(3, 8, 'comments'),
|
||||||
|
problem2=(5, 2, 'comments-indentation'))
|
||||||
|
self.check('# yamllint disable-file: rules cannot be specified\n'
|
||||||
|
'---\n'
|
||||||
|
'- a: 1 # comment too close\n'
|
||||||
|
' b:\n'
|
||||||
|
' # wrong indentation\n'
|
||||||
|
' c: [x]\n',
|
||||||
|
conf,
|
||||||
|
problem1=(3, 8, 'comments'),
|
||||||
|
problem2=(5, 2, 'comments-indentation'))
|
||||||
|
self.check('AAAA yamllint disable-file\n'
|
||||||
|
'---\n'
|
||||||
|
'- a: 1 # comment too close\n'
|
||||||
|
' b:\n'
|
||||||
|
' # wrong indentation\n'
|
||||||
|
' c: [x]\n',
|
||||||
|
conf,
|
||||||
|
problem1=(1, 1, 'document-start'),
|
||||||
|
problem2=(3, 8, 'comments'),
|
||||||
|
problem3=(5, 2, 'comments-indentation'))
|
||||||
|
|
||||||
|
def test_disable_file_directive_not_at_first_position(self):
|
||||||
|
self.check('# yamllint disable-file\n'
|
||||||
|
'---\n'
|
||||||
|
'- bad : colon and spaces \n',
|
||||||
|
self.conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'# yamllint disable-file\n'
|
||||||
|
'- bad : colon and spaces \n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(3, 7, 'colons'),
|
||||||
|
problem2=(3, 26, 'trailing-spaces'))
|
||||||
|
|
||||||
|
def test_disable_file_directive_with_syntax_error(self):
|
||||||
|
self.check('# This file is not valid YAML (it is a Jinja template)\n'
|
||||||
|
'{% if extra_info %}\n'
|
||||||
|
'key1: value1\n'
|
||||||
|
'{% endif %}\n'
|
||||||
|
'key2: value2\n',
|
||||||
|
self.conf,
|
||||||
|
problem=(2, 2, 'syntax'))
|
||||||
|
self.check('# yamllint disable-file\n'
|
||||||
|
'# This file is not valid YAML (it is a Jinja template)\n'
|
||||||
|
'{% if extra_info %}\n'
|
||||||
|
'key1: value1\n'
|
||||||
|
'{% endif %}\n'
|
||||||
|
'key2: value2\n',
|
||||||
|
self.conf)
|
||||||
|
|
||||||
|
def test_disable_file_directive_with_dos_lines(self):
|
||||||
|
self.check('# yamllint disable-file\r\n'
|
||||||
|
'---\r\n'
|
||||||
|
'- bad : colon and spaces \r\n',
|
||||||
|
self.conf)
|
||||||
|
self.check('# yamllint disable-file\r\n'
|
||||||
|
'# This file is not valid YAML (it is a Jinja template)\r\n'
|
||||||
|
'{% if extra_info %}\r\n'
|
||||||
|
'key1: value1\r\n'
|
||||||
|
'{% endif %}\r\n'
|
||||||
|
'key2: value2\r\n',
|
||||||
|
self.conf)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ indentation, etc."""
|
|||||||
|
|
||||||
|
|
||||||
APP_NAME = 'yamllint'
|
APP_NAME = 'yamllint'
|
||||||
APP_VERSION = '1.18.0'
|
APP_VERSION = '1.21.0'
|
||||||
APP_DESCRIPTION = __doc__
|
APP_DESCRIPTION = __doc__
|
||||||
|
|
||||||
__author__ = u'Adrien Vergé'
|
__author__ = u'Adrien Vergé'
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
@@ -84,11 +85,14 @@ class Format(object):
|
|||||||
return line
|
return line
|
||||||
|
|
||||||
|
|
||||||
def show_problems(problems, file, args_format):
|
def show_problems(problems, file, args_format, no_warn):
|
||||||
max_level = 0
|
max_level = 0
|
||||||
first = True
|
first = True
|
||||||
|
|
||||||
for problem in problems:
|
for problem in problems:
|
||||||
|
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
|
||||||
|
if no_warn and (problem.level != 'error'):
|
||||||
|
continue
|
||||||
if args_format == 'parsable':
|
if args_format == 'parsable':
|
||||||
print(Format.parsable(problem, file))
|
print(Format.parsable(problem, file))
|
||||||
elif args_format == 'colored' or \
|
elif args_format == 'colored' or \
|
||||||
@@ -102,7 +106,6 @@ def show_problems(problems, file, args_format):
|
|||||||
print(file)
|
print(file)
|
||||||
first = False
|
first = False
|
||||||
print(Format.standard(problem, file))
|
print(Format.standard(problem, file))
|
||||||
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
|
|
||||||
|
|
||||||
if not first and args_format != 'parsable':
|
if not first and args_format != 'parsable':
|
||||||
print('')
|
print('')
|
||||||
@@ -133,6 +136,9 @@ def run(argv=None):
|
|||||||
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('--no-warnings',
|
||||||
|
action='store_true',
|
||||||
|
help='output only error level problems')
|
||||||
parser.add_argument('-v', '--version', action='version',
|
parser.add_argument('-v', '--version', action='version',
|
||||||
version='{} {}'.format(APP_NAME, APP_VERSION))
|
version='{} {}'.format(APP_NAME, APP_VERSION))
|
||||||
|
|
||||||
@@ -171,12 +177,13 @@ def run(argv=None):
|
|||||||
for file in find_files_recursively(args.files, conf):
|
for file in find_files_recursively(args.files, conf):
|
||||||
filepath = file[2:] if file.startswith('./') else file
|
filepath = file[2:] if file.startswith('./') else file
|
||||||
try:
|
try:
|
||||||
with open(file) as f:
|
with io.open(file, newline='') as f:
|
||||||
problems = linter.run(f, conf, filepath)
|
problems = linter.run(f, conf, filepath)
|
||||||
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)
|
prob_level = show_problems(problems, file, args_format=args.format,
|
||||||
|
no_warn=args.no_warnings)
|
||||||
max_level = max(max_level, prob_level)
|
max_level = max(max_level, prob_level)
|
||||||
|
|
||||||
# read yaml from stdin
|
# read yaml from stdin
|
||||||
@@ -186,7 +193,8 @@ def run(argv=None):
|
|||||||
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, 'stdin', args_format=args.format)
|
prob_level = show_problems(problems, 'stdin', args_format=args.format,
|
||||||
|
no_warn=args.no_warnings)
|
||||||
max_level = max(max_level, prob_level)
|
max_level = max(max_level, prob_level)
|
||||||
|
|
||||||
if max_level == PROBLEM_LEVELS['error']:
|
if max_level == PROBLEM_LEVELS['error']:
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ def get_syntax_error(buffer):
|
|||||||
except yaml.error.MarkedYAMLError as e:
|
except yaml.error.MarkedYAMLError as e:
|
||||||
problem = LintProblem(e.problem_mark.line + 1,
|
problem = LintProblem(e.problem_mark.line + 1,
|
||||||
e.problem_mark.column + 1,
|
e.problem_mark.column + 1,
|
||||||
'syntax error: ' + e.problem)
|
'syntax error: ' + e.problem + ' (syntax)')
|
||||||
problem.level = 'error'
|
problem.level = 'error'
|
||||||
return problem
|
return problem
|
||||||
|
|
||||||
@@ -189,6 +189,10 @@ def _run(buffer, conf, filepath):
|
|||||||
assert hasattr(buffer, '__getitem__'), \
|
assert hasattr(buffer, '__getitem__'), \
|
||||||
'_run() argument must be a buffer, not a stream'
|
'_run() argument must be a buffer, not a stream'
|
||||||
|
|
||||||
|
first_line = next(parser.line_generator(buffer)).content
|
||||||
|
if re.match(r'^#\s*yamllint disable-file\s*$', first_line):
|
||||||
|
return
|
||||||
|
|
||||||
# If the document contains a syntax error, save it and yield it at the
|
# If the document contains a syntax error, save it and yield it at the
|
||||||
# right line
|
# right line
|
||||||
syntax_error = get_syntax_error(buffer)
|
syntax_error = get_syntax_error(buffer)
|
||||||
|
|||||||
@@ -15,15 +15,23 @@
|
|||||||
# 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 any string values that are not quoted.
|
Use this rule to forbid any string values that are not quoted, or to prevent
|
||||||
You can also enforce the type of the quote used using the ``quote-type`` option
|
quoted strings without needing it. You can also enforce the type of the quote
|
||||||
(``single``, ``double`` or ``any``).
|
used.
|
||||||
|
|
||||||
|
.. rubric:: Options
|
||||||
|
|
||||||
|
* ``quote-type`` defines allowed quotes: ``single``, ``double`` or ``any``
|
||||||
|
(default).
|
||||||
|
* ``required`` defines whether using quotes in string values is required
|
||||||
|
(``true``, default) or not (``false``), or only allowed when really needed
|
||||||
|
(``only-when-needed``).
|
||||||
|
|
||||||
**Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
|
**Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
|
||||||
|
|
||||||
.. rubric:: Examples
|
.. rubric:: Examples
|
||||||
|
|
||||||
#. With ``quoted-strings: {quote-type: any}``
|
#. With ``quoted-strings: {quote-type: any, required: true}``
|
||||||
|
|
||||||
the following code snippet would **PASS**:
|
the following code snippet would **PASS**:
|
||||||
::
|
::
|
||||||
@@ -37,6 +45,24 @@ You can also enforce the type of the quote used using the ``quote-type`` option
|
|||||||
::
|
::
|
||||||
|
|
||||||
foo: bar
|
foo: bar
|
||||||
|
|
||||||
|
#. With ``quoted-strings: {quote-type: single, required: only-when-needed}``
|
||||||
|
|
||||||
|
the following code snippet would **PASS**:
|
||||||
|
::
|
||||||
|
|
||||||
|
foo: bar
|
||||||
|
bar: foo
|
||||||
|
not_number: '123'
|
||||||
|
not_boolean: 'true'
|
||||||
|
not_comment: '# comment'
|
||||||
|
not_list: '[1, 2, 3]'
|
||||||
|
not_map: '{a: 1, b: 2}'
|
||||||
|
|
||||||
|
the following code snippet would **FAIL**:
|
||||||
|
::
|
||||||
|
|
||||||
|
foo: 'bar'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
@@ -45,34 +71,76 @@ from yamllint.linter import LintProblem
|
|||||||
|
|
||||||
ID = 'quoted-strings'
|
ID = 'quoted-strings'
|
||||||
TYPE = 'token'
|
TYPE = 'token'
|
||||||
CONF = {'quote-type': ('any', 'single', 'double')}
|
CONF = {'quote-type': ('any', 'single', 'double'),
|
||||||
DEFAULT = {'quote-type': 'any'}
|
'required': (True, False, 'only-when-needed')}
|
||||||
|
DEFAULT = {'quote-type': 'any',
|
||||||
|
'required': True}
|
||||||
|
|
||||||
|
DEFAULT_SCALAR_TAG = u'tag:yaml.org,2002:str'
|
||||||
|
START_TOKENS = {'#', '*', '!', '?', '@', '`', '&',
|
||||||
|
',', '-', '{', '}', '[', ']', ':'}
|
||||||
|
|
||||||
|
|
||||||
|
def quote_match(quote_type, token_style):
|
||||||
|
return ((quote_type == 'any') or
|
||||||
|
(quote_type == 'single' and token_style == "'") or
|
||||||
|
(quote_type == 'double' and token_style == '"'))
|
||||||
|
|
||||||
|
|
||||||
def check(conf, token, prev, next, nextnext, context):
|
def check(conf, token, prev, next, nextnext, context):
|
||||||
quote_type = conf['quote-type']
|
if not (isinstance(token, yaml.tokens.ScalarToken) and
|
||||||
|
|
||||||
if (isinstance(token, yaml.tokens.ScalarToken) and
|
|
||||||
isinstance(prev, (yaml.ValueToken, yaml.TagToken))):
|
isinstance(prev, (yaml.ValueToken, yaml.TagToken))):
|
||||||
# Ignore explicit types, e.g. !!str testtest or !!int 42
|
return
|
||||||
if (prev and isinstance(prev, yaml.tokens.TagToken) and
|
|
||||||
prev.value[0] == '!!'):
|
|
||||||
return
|
|
||||||
|
|
||||||
# Ignore numbers, booleans, etc.
|
# Ignore explicit types, e.g. !!str testtest or !!int 42
|
||||||
resolver = yaml.resolver.Resolver()
|
if (prev and isinstance(prev, yaml.tokens.TagToken) and
|
||||||
if resolver.resolve(yaml.nodes.ScalarNode, token.value,
|
prev.value[0] == '!!'):
|
||||||
(True, False)) != 'tag:yaml.org,2002:str':
|
return
|
||||||
return
|
|
||||||
|
|
||||||
# Ignore multi-line strings
|
# Ignore numbers, booleans, etc.
|
||||||
if (not token.plain) and (token.style == "|" or token.style == ">"):
|
resolver = yaml.resolver.Resolver()
|
||||||
return
|
tag = resolver.resolve(yaml.nodes.ScalarNode, token.value, (True, False))
|
||||||
|
if token.plain and tag != DEFAULT_SCALAR_TAG:
|
||||||
|
return
|
||||||
|
|
||||||
if ((quote_type == 'single' and token.style != "'") or
|
# Ignore multi-line strings
|
||||||
(quote_type == 'double' and token.style != '"') or
|
if (not token.plain) and (token.style == "|" or token.style == ">"):
|
||||||
(quote_type == 'any' and token.style is None)):
|
return
|
||||||
yield LintProblem(
|
|
||||||
token.start_mark.line + 1,
|
quote_type = conf['quote-type']
|
||||||
token.start_mark.column + 1,
|
required = conf['required']
|
||||||
"string value is not quoted with %s quotes" % (quote_type))
|
|
||||||
|
# Completely relaxed about quotes (same as the rule being disabled)
|
||||||
|
if required is False and quote_type == 'any':
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = None
|
||||||
|
if required is True:
|
||||||
|
|
||||||
|
# Quotes are mandatory and need to match config
|
||||||
|
if token.style is None or not quote_match(quote_type, token.style):
|
||||||
|
msg = "string value is not quoted with %s quotes" % (quote_type)
|
||||||
|
|
||||||
|
elif required is False:
|
||||||
|
|
||||||
|
# Quotes are not mandatory but when used need to match config
|
||||||
|
if token.style and not quote_match(quote_type, token.style):
|
||||||
|
msg = "string value is not quoted with %s quotes" % (quote_type)
|
||||||
|
|
||||||
|
elif not token.plain:
|
||||||
|
|
||||||
|
# Quotes are disallowed when not needed
|
||||||
|
if (tag == DEFAULT_SCALAR_TAG and token.value
|
||||||
|
and token.value[0] not in START_TOKENS):
|
||||||
|
msg = "string value is redundantly quoted with %s quotes" % (
|
||||||
|
quote_type)
|
||||||
|
|
||||||
|
# But when used need to match config
|
||||||
|
elif token.style and not quote_match(quote_type, token.style):
|
||||||
|
msg = "string value is not quoted with %s quotes" % (quote_type)
|
||||||
|
|
||||||
|
if msg is not None:
|
||||||
|
yield LintProblem(
|
||||||
|
token.start_mark.line + 1,
|
||||||
|
token.start_mark.column + 1,
|
||||||
|
msg)
|
||||||
|
|||||||
Reference in New Issue
Block a user