Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
542ae758f5 | ||
|
|
3a6a09b7b6 | ||
|
|
15aea73fbe | ||
|
|
91763f5476 | ||
|
|
5b049e4229 | ||
|
|
044c7f0248 | ||
|
|
734d5d5f73 | ||
|
|
fd86455076 | ||
|
|
13a0f11e7c | ||
|
|
43b95e99d1 | ||
|
|
8fa9eb3ced |
@@ -3,10 +3,10 @@ dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069)
|
||||
language: python
|
||||
python:
|
||||
- 2.7
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
- 3.7
|
||||
- 3.8
|
||||
- nightly
|
||||
install:
|
||||
- pip install pyyaml coveralls flake8 flake8-import-order doc8
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
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)
|
||||
-------------------
|
||||
|
||||
|
||||
@@ -116,6 +116,9 @@ return code will be:
|
||||
* ``1`` if one or more errors 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
|
||||
---------------------
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ class QuotedTestCase(RuleTestCase):
|
||||
|
||||
def test_disabled(self):
|
||||
conf = 'quoted-strings: disable'
|
||||
|
||||
self.check('---\n'
|
||||
'foo: bar\n', conf)
|
||||
self.check('---\n'
|
||||
@@ -30,18 +31,23 @@ class QuotedTestCase(RuleTestCase):
|
||||
'foo: \'bar\'\n', conf)
|
||||
self.check('---\n'
|
||||
'bar: 123\n', conf)
|
||||
self.check('---\n'
|
||||
'bar: "123"\n', conf)
|
||||
|
||||
def test_quote_type_any(self):
|
||||
conf = 'quoted-strings: {quote-type: any}\n'
|
||||
|
||||
self.check('---\n'
|
||||
'boolean1: true\n'
|
||||
'number1: 123\n'
|
||||
'string1: foo\n' # fails
|
||||
'string2: "foo"\n'
|
||||
'string3: \'bar\'\n'
|
||||
'string4: !!str genericstring\n'
|
||||
'string5: !!str 456\n'
|
||||
'string6: !!str "quotedgenericstring"\n'
|
||||
'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'
|
||||
@@ -55,7 +61,7 @@ class QuotedTestCase(RuleTestCase):
|
||||
' word 1\n'
|
||||
' word 2\n'
|
||||
'multiline string 3:\n'
|
||||
' word 1\n'
|
||||
' word 1\n' # fails
|
||||
' word 2\n'
|
||||
'multiline string 4:\n'
|
||||
' "word 1\\\n'
|
||||
@@ -64,20 +70,24 @@ class QuotedTestCase(RuleTestCase):
|
||||
|
||||
def test_quote_type_single(self):
|
||||
conf = 'quoted-strings: {quote-type: single}\n'
|
||||
|
||||
self.check('---\n'
|
||||
'boolean1: true\n'
|
||||
'number1: 123\n'
|
||||
'string1: foo\n' # fails
|
||||
'string2: "foo"\n' # fails
|
||||
'string3: \'bar\'\n'
|
||||
'string4: !!str genericstring\n'
|
||||
'string5: !!str 456\n'
|
||||
'string6: !!str "quotedgenericstring"\n'
|
||||
'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))
|
||||
conf, problem1=(4, 10), problem2=(5, 10),
|
||||
problem3=(6, 10), problem4=(7, 10))
|
||||
self.check('---\n'
|
||||
'multiline string 1: |\n'
|
||||
' line 1\n'
|
||||
@@ -86,7 +96,7 @@ class QuotedTestCase(RuleTestCase):
|
||||
' word 1\n'
|
||||
' word 2\n'
|
||||
'multiline string 3:\n'
|
||||
' word 1\n'
|
||||
' word 1\n' # fails
|
||||
' word 2\n'
|
||||
'multiline string 4:\n'
|
||||
' "word 1\\\n'
|
||||
@@ -95,20 +105,162 @@ class QuotedTestCase(RuleTestCase):
|
||||
|
||||
def test_quote_type_double(self):
|
||||
conf = 'quoted-strings: {quote-type: double}\n'
|
||||
|
||||
self.check('---\n'
|
||||
'boolean1: true\n'
|
||||
'number1: 123\n'
|
||||
'string1: foo\n' # fails
|
||||
'string2: "foo"\n'
|
||||
'string3: \'bar\'\n' # fails
|
||||
'string4: !!str genericstring\n'
|
||||
'string5: !!str 456\n'
|
||||
'string6: !!str "quotedgenericstring"\n'
|
||||
'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=(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'
|
||||
'multiline string 1: |\n'
|
||||
' line 1\n'
|
||||
@@ -122,4 +274,73 @@ class QuotedTestCase(RuleTestCase):
|
||||
'multiline string 4:\n'
|
||||
' "word 1\\\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))
|
||||
|
||||
@@ -32,6 +32,29 @@ from yamllint import cli
|
||||
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):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@@ -59,12 +82,15 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
'no-yaml.json': '---\n'
|
||||
'key: value\n',
|
||||
# non-ASCII chars
|
||||
'non-ascii/utf-8': (
|
||||
'non-ascii/éçäγλνπ¥/utf-8': (
|
||||
u'---\n'
|
||||
u'- hétérogénéité\n'
|
||||
u'# 19.99 €\n'
|
||||
u'- お早う御座います。\n'
|
||||
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
|
||||
# dos line endings yaml
|
||||
'dos.yml': '---\r\n'
|
||||
'dos: true',
|
||||
})
|
||||
|
||||
@classmethod
|
||||
@@ -78,6 +104,7 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
sorted(cli.find_files_recursively([self.wd], conf)),
|
||||
[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, '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'),
|
||||
@@ -123,7 +150,8 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
' - \'*.yml\'\n')
|
||||
self.assertEqual(
|
||||
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'
|
||||
@@ -140,9 +168,10 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
sorted(cli.find_files_recursively([self.wd], conf)),
|
||||
[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, '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, 'sub/ok.yaml'),
|
||||
os.path.join(self.wd, 'warn.yaml')]
|
||||
@@ -156,9 +185,10 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
sorted(cli.find_files_recursively([self.wd], conf)),
|
||||
[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, '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, 'sub/ok.yaml'),
|
||||
os.path.join(self.wd, 'warn.yaml')]
|
||||
@@ -170,205 +200,148 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
' - \'**/utf-8\'\n')
|
||||
self.assertEqual(
|
||||
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):
|
||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||
with self.assertRaises(SystemExit) as ctx:
|
||||
with RunContext(self) as ctx:
|
||||
cli.run(())
|
||||
self.assertNotEqual(ctx.returncode, 0)
|
||||
self.assertEqual(ctx.stdout, '')
|
||||
self.assertRegexpMatches(ctx.stderr, r'^usage')
|
||||
|
||||
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:
|
||||
with RunContext(self) as ctx:
|
||||
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)
|
||||
|
||||
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:
|
||||
with RunContext(self) 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.assertNotEqual(ctx.returncode, 0)
|
||||
self.assertEqual(ctx.stdout, '')
|
||||
self.assertRegexpMatches(
|
||||
err.splitlines()[-1],
|
||||
ctx.stderr.splitlines()[-1],
|
||||
r'^yamllint: error: argument -d\/--config-data: '
|
||||
r'not allowed with argument -c\/--config-file$'
|
||||
)
|
||||
|
||||
# checks if reading from stdin and files are mutually exclusive
|
||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||
with self.assertRaises(SystemExit) as ctx:
|
||||
with RunContext(self) as ctx:
|
||||
cli.run(('-', 'file'))
|
||||
|
||||
self.assertNotEqual(ctx.exception.code, 0)
|
||||
|
||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
||||
self.assertEqual(out, '')
|
||||
self.assertRegexpMatches(err, r'^usage')
|
||||
self.assertNotEqual(ctx.returncode, 0)
|
||||
self.assertEqual(ctx.stdout, '')
|
||||
self.assertRegexpMatches(ctx.stderr, r'^usage')
|
||||
|
||||
def test_run_with_bad_config(self):
|
||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||
with self.assertRaises(SystemExit) as ctx:
|
||||
with RunContext(self) 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')
|
||||
self.assertEqual(ctx.returncode, -1)
|
||||
self.assertEqual(ctx.stdout, '')
|
||||
self.assertRegexpMatches(ctx.stderr, 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:
|
||||
with RunContext(self) 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')
|
||||
self.assertEqual(ctx.returncode, -1)
|
||||
self.assertEqual(ctx.stdout, '')
|
||||
self.assertRegexpMatches(ctx.stderr, 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:
|
||||
with RunContext(self) as ctx:
|
||||
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:
|
||||
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')))
|
||||
self.assertEqual(ctx.exception.code, 1)
|
||||
self.assertEqual(ctx.returncode, 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)
|
||||
dir = os.path.join(home, '.config', 'yamllint')
|
||||
os.makedirs(dir)
|
||||
config = os.path.join(dir, 'config')
|
||||
|
||||
temp = os.environ['HOME']
|
||||
self.addCleanup(os.environ.update, HOME=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:
|
||||
with RunContext(self) as ctx:
|
||||
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:
|
||||
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'), ))
|
||||
self.assertEqual(ctx.exception.code, 1)
|
||||
|
||||
os.environ['HOME'] = temp
|
||||
self.assertEqual(ctx.returncode, 1)
|
||||
|
||||
def test_run_version(self):
|
||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||
with self.assertRaises(SystemExit) as ctx:
|
||||
with RunContext(self) 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+')
|
||||
self.assertEqual(ctx.returncode, 0)
|
||||
self.assertRegexpMatches(ctx.stdout + ctx.stderr, r'yamllint \d+\.\d+')
|
||||
|
||||
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 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')
|
||||
with RunContext(self) as ctx:
|
||||
cli.run(('-f', 'parsable', path))
|
||||
self.assertEqual(ctx.returncode, -1)
|
||||
self.assertEqual(ctx.stdout, '')
|
||||
self.assertRegexpMatches(ctx.stderr, r'No such file or directory')
|
||||
|
||||
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 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, (
|
||||
with RunContext(self) as ctx:
|
||||
cli.run(('-f', 'parsable', path))
|
||||
self.assertEqual(ctx.returncode, 1)
|
||||
self.assertEqual(ctx.stdout, (
|
||||
'%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, '')
|
||||
'(new-line-at-end-of-file)\n' % (path, path)))
|
||||
self.assertEqual(ctx.stderr, '')
|
||||
|
||||
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 self.assertRaises(SystemExit) as ctx:
|
||||
cli.run(('-f', 'parsable', file))
|
||||
|
||||
self.assertEqual(ctx.exception.code, 0)
|
||||
with RunContext(self) as ctx:
|
||||
cli.run(('-f', 'parsable', path))
|
||||
self.assertEqual(ctx.returncode, 0)
|
||||
|
||||
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 self.assertRaises(SystemExit) as ctx:
|
||||
cli.run(('-f', 'parsable', '--strict', file))
|
||||
|
||||
self.assertEqual(ctx.exception.code, 2)
|
||||
with RunContext(self) as ctx:
|
||||
cli.run(('-f', 'parsable', '--strict', path))
|
||||
self.assertEqual(ctx.returncode, 2)
|
||||
|
||||
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 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, '')
|
||||
with RunContext(self) as ctx:
|
||||
cli.run(('-f', 'parsable', path))
|
||||
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
|
||||
|
||||
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 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, '')
|
||||
with RunContext(self) as ctx:
|
||||
cli.run(('-f', 'parsable', path))
|
||||
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
|
||||
|
||||
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"
|
||||
# support UTF-8 encoding.
|
||||
@@ -377,63 +350,46 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
|
||||
except locale.Error:
|
||||
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
|
||||
self.addCleanup(locale.setlocale, locale.LC_ALL, loc)
|
||||
|
||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||
with self.assertRaises(SystemExit) as ctx:
|
||||
cli.run(('-f', 'parsable', file))
|
||||
|
||||
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, '')
|
||||
with RunContext(self) as ctx:
|
||||
cli.run(('-f', 'parsable', path))
|
||||
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
|
||||
|
||||
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'
|
||||
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 self.assertRaises(SystemExit) as ctx:
|
||||
with RunContext(self) as ctx:
|
||||
cli.run(['-f', 'parsable'] + items)
|
||||
|
||||
self.assertEqual(ctx.exception.code, 1)
|
||||
|
||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
||||
self.assertEqual(out, (
|
||||
self.assertEqual((ctx.returncode, ctx.stderr), (1, ''))
|
||||
self.assertEqual(ctx.stdout, (
|
||||
'%s:3:1: [error] duplication of key "key" in mapping '
|
||||
'(key-duplicates)\n') % file)
|
||||
self.assertEqual(err, '')
|
||||
'(key-duplicates)\n') % path)
|
||||
|
||||
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 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, (
|
||||
with RunContext(self) as ctx:
|
||||
cli.run((path, ))
|
||||
self.assertEqual((ctx.returncode, ctx.stderr), (1, ''))
|
||||
self.assertEqual(ctx.stdout, (
|
||||
'%s\n'
|
||||
' 2:4 error trailing spaces (trailing-spaces)\n'
|
||||
' 3:4 error no new line character at the end of file '
|
||||
'(new-line-at-end-of-file)\n'
|
||||
'\n' % file))
|
||||
self.assertEqual(err, '')
|
||||
'\n' % path))
|
||||
|
||||
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
|
||||
master, slave = pty.openpty()
|
||||
sys.stdout = sys.stderr = os.fdopen(slave, 'w')
|
||||
|
||||
with self.assertRaises(SystemExit) as ctx:
|
||||
cli.run((file, ))
|
||||
cli.run((path, ))
|
||||
sys.stdout.flush()
|
||||
|
||||
self.assertEqual(ctx.exception.code, 1)
|
||||
@@ -456,79 +412,108 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
' \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))
|
||||
'\n' % path))
|
||||
|
||||
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 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, (
|
||||
with RunContext(self) as ctx:
|
||||
cli.run((path, ))
|
||||
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' % file))
|
||||
self.assertEqual(err, '')
|
||||
'\n' % path)
|
||||
self.assertEqual(
|
||||
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
|
||||
|
||||
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 self.assertRaises(SystemExit) as ctx:
|
||||
cli.run((file, '--format', 'auto'))
|
||||
|
||||
self.assertEqual(ctx.exception.code, 1)
|
||||
|
||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
||||
self.assertEqual(out, (
|
||||
with RunContext(self) as ctx:
|
||||
cli.run((path, '--format', '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' % file))
|
||||
self.assertEqual(err, '')
|
||||
'\n' % path)
|
||||
self.assertEqual(
|
||||
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
|
||||
|
||||
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 self.assertRaises(SystemExit) as ctx:
|
||||
cli.run((file, '--format', 'colored'))
|
||||
|
||||
self.assertEqual(ctx.exception.code, 1)
|
||||
|
||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
||||
self.assertEqual(out, (
|
||||
with RunContext(self) as ctx:
|
||||
cli.run((path, '--format', 'colored'))
|
||||
expected_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, '')
|
||||
'\n' % path)
|
||||
self.assertEqual(
|
||||
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
|
||||
|
||||
def test_run_read_from_stdin(self):
|
||||
# prepares stdin with an invalid yaml string so that we can check
|
||||
# for its specific error, and be assured that stdin was read
|
||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||
self.addCleanup(setattr, sys, 'stdin', sys.__stdin__)
|
||||
sys.stdin = StringIO(
|
||||
'I am a string\n'
|
||||
'therefore: I am an error\n')
|
||||
|
||||
with self.assertRaises(SystemExit) as ctx:
|
||||
with RunContext(self) as ctx:
|
||||
cli.run(('-', '-f', 'parsable'))
|
||||
|
||||
self.assertNotEqual(ctx.exception.code, 0)
|
||||
|
||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
||||
self.assertEqual(out, (
|
||||
expected_out = (
|
||||
'stdin:2:10: [error] syntax error: '
|
||||
'mapping values are not allowed here\n'))
|
||||
self.assertEqual(err, '')
|
||||
'mapping values are not allowed here (syntax)\n')
|
||||
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, ''))
|
||||
|
||||
@@ -22,7 +22,7 @@ indentation, etc."""
|
||||
|
||||
|
||||
APP_NAME = 'yamllint'
|
||||
APP_VERSION = '1.19.0'
|
||||
APP_VERSION = '1.21.0'
|
||||
APP_DESCRIPTION = __doc__
|
||||
|
||||
__author__ = u'Adrien Vergé'
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
@@ -84,11 +85,14 @@ class Format(object):
|
||||
return line
|
||||
|
||||
|
||||
def show_problems(problems, file, args_format):
|
||||
def show_problems(problems, file, args_format, no_warn):
|
||||
max_level = 0
|
||||
first = True
|
||||
|
||||
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':
|
||||
print(Format.parsable(problem, file))
|
||||
elif args_format == 'colored' or \
|
||||
@@ -102,7 +106,6 @@ def show_problems(problems, file, args_format):
|
||||
print(file)
|
||||
first = False
|
||||
print(Format.standard(problem, file))
|
||||
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
|
||||
|
||||
if not first and args_format != 'parsable':
|
||||
print('')
|
||||
@@ -133,6 +136,9 @@ def run(argv=None):
|
||||
action='store_true',
|
||||
help='return non-zero exit code on warnings '
|
||||
'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',
|
||||
version='{} {}'.format(APP_NAME, APP_VERSION))
|
||||
|
||||
@@ -171,12 +177,13 @@ def run(argv=None):
|
||||
for file in find_files_recursively(args.files, conf):
|
||||
filepath = file[2:] if file.startswith('./') else file
|
||||
try:
|
||||
with open(file) as f:
|
||||
with io.open(file, newline='') as f:
|
||||
problems = linter.run(f, conf, filepath)
|
||||
except EnvironmentError as e:
|
||||
print(e, file=sys.stderr)
|
||||
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)
|
||||
|
||||
# read yaml from stdin
|
||||
@@ -186,7 +193,8 @@ def run(argv=None):
|
||||
except EnvironmentError as e:
|
||||
print(e, file=sys.stderr)
|
||||
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)
|
||||
|
||||
if max_level == PROBLEM_LEVELS['error']:
|
||||
|
||||
@@ -180,7 +180,7 @@ def get_syntax_error(buffer):
|
||||
except yaml.error.MarkedYAMLError as e:
|
||||
problem = LintProblem(e.problem_mark.line + 1,
|
||||
e.problem_mark.column + 1,
|
||||
'syntax error: ' + e.problem)
|
||||
'syntax error: ' + e.problem + ' (syntax)')
|
||||
problem.level = 'error'
|
||||
return problem
|
||||
|
||||
|
||||
@@ -15,15 +15,23 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Use this rule to forbid any string values that are not quoted.
|
||||
You can also enforce the type of the quote used using the ``quote-type`` option
|
||||
(``single``, ``double`` or ``any``).
|
||||
Use this rule to forbid any string values that are not quoted, or to prevent
|
||||
quoted strings without needing it. You can also enforce the type of the quote
|
||||
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.
|
||||
|
||||
.. rubric:: Examples
|
||||
|
||||
#. With ``quoted-strings: {quote-type: any}``
|
||||
#. With ``quoted-strings: {quote-type: any, required: true}``
|
||||
|
||||
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
|
||||
|
||||
#. 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
|
||||
@@ -45,15 +71,27 @@ from yamllint.linter import LintProblem
|
||||
|
||||
ID = 'quoted-strings'
|
||||
TYPE = 'token'
|
||||
CONF = {'quote-type': ('any', 'single', 'double')}
|
||||
DEFAULT = {'quote-type': 'any'}
|
||||
CONF = {'quote-type': ('any', 'single', 'double'),
|
||||
'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):
|
||||
quote_type = conf['quote-type']
|
||||
|
||||
if (isinstance(token, yaml.tokens.ScalarToken) and
|
||||
if not (isinstance(token, yaml.tokens.ScalarToken) and
|
||||
isinstance(prev, (yaml.ValueToken, yaml.TagToken))):
|
||||
return
|
||||
|
||||
# Ignore explicit types, e.g. !!str testtest or !!int 42
|
||||
if (prev and isinstance(prev, yaml.tokens.TagToken) and
|
||||
prev.value[0] == '!!'):
|
||||
@@ -61,18 +99,48 @@ def check(conf, token, prev, next, nextnext, context):
|
||||
|
||||
# Ignore numbers, booleans, etc.
|
||||
resolver = yaml.resolver.Resolver()
|
||||
if resolver.resolve(yaml.nodes.ScalarNode, token.value,
|
||||
(True, False)) != 'tag:yaml.org,2002:str':
|
||||
tag = resolver.resolve(yaml.nodes.ScalarNode, token.value, (True, False))
|
||||
if token.plain and tag != DEFAULT_SCALAR_TAG:
|
||||
return
|
||||
|
||||
# Ignore multi-line strings
|
||||
if (not token.plain) and (token.style == "|" or token.style == ">"):
|
||||
return
|
||||
|
||||
if ((quote_type == 'single' and token.style != "'") or
|
||||
(quote_type == 'double' and token.style != '"') or
|
||||
(quote_type == 'any' and token.style is None)):
|
||||
quote_type = conf['quote-type']
|
||||
required = conf['required']
|
||||
|
||||
# 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,
|
||||
"string value is not quoted with %s quotes" % (quote_type))
|
||||
msg)
|
||||
|
||||
Reference in New Issue
Block a user