Compare commits

..

13 Commits

Author SHA1 Message Date
Adrien Vergé
9cce294041 yamllint version 1.28.0 2022-09-12 14:37:48 +02:00
andrewnaguib
2f8ad7003a config: Implement for ignore-from-file option
Closes https://github.com/adrienverge/yamllint/issues/360
Co-authored-by: Adrien Vergé <@adrienverge>
2022-08-10 08:35:40 +02:00
Roman Geraskin
fb0c0a5247 quoted-strings: fix docs example 2022-08-07 20:13:48 +02:00
Roman Geraskin
352e1a975e quoted-strings: Add allow-quoted-quotes option
Allows strings like `'foo"bar'` on `quote-type: double` and vice versa.
2022-08-07 13:47:35 +02:00
Dimitri Papadopoulos
e319a17344 octal values: simpler test for match objects
From the Python 3 documentation:
	Match objects always have a boolean value of True.
	Since match() and search() return None when there is no match,
	you can test whether there was a match with a simple if statement:
		match = re.search(pattern, string)
		if match:
		    process(match)
2022-08-06 15:23:23 +02:00
Dimitri Papadopoulos
6b6fdba3bf linter: pre-compile disable/enable rules regexes
Not only this should improve performance, but I find the code more
readable.
2022-08-06 15:23:23 +02:00
Dimitri Papadopoulos Orfanos
868350681a License: Update to latest version of GPLv3
http:// → https://
2022-08-06 15:14:57 +02:00
Andrew Imeson
94c1c2bcf2 docs: Update ALE vim plugin link 2022-08-05 18:57:21 +02:00
Andrew Imeson
0130e15c8c docs: Simplify GitHub Actions example 2022-08-05 18:57:21 +02:00
Dimitri Papadopoulos
ae3158cd1f No need to inherit from object in Python 3 2022-08-05 08:49:56 +02:00
Dimitri Papadopoulos
4c7b47daf3 Most __future__ imports are specific to Python 2 2022-08-05 08:49:56 +02:00
Dimitri Papadopoulos Orfanos
3346843edc docs: Better compress PNG image 2022-08-05 08:43:55 +02:00
Dimitri Papadopoulos Orfanos
ea70520216 Changelog: Fix typo found by codespell 2022-08-05 08:42:10 +02:00
18 changed files with 417 additions and 62 deletions

View File

@@ -1,6 +1,19 @@
Changelog
=========
1.28.0 (2022-09-12)
-------------------
- Better compress PNG image in documentation
- Remove ``__future__`` imports specific to Python 2
- Remove inheritance from ``object`` specific to Python 2
- Simplify GitHub Actions example in documentation
- Update ALE vim plugin link in documentation
- Update license to latest version of GPLv3
- Pre-compile disable/enable rules regexes
- Rule ``quoted-strings``: add ``allow-quoted-quotes`` option
- Add option ``ignore-from-file`` in config
1.27.1 (2022-07-08)
-------------------
@@ -14,7 +27,7 @@ Changelog
- Refactor ``--format=auto`` logic
- Update GitHub format output to use groups
- Rule ``comments``: allow whitespace after the shebang marker
- Multiple minor fixes in documetation
- Multiple minor fixes in documentation
- Configure Sphinx to make man page show up in apropos
- Attempt to clarify configuration file location in documentation
- Rule ``key-duplicates``: don't crash on redundant closing brackets or braces

View File

@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
@@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@@ -190,6 +190,20 @@ Here is a more complex example:
*.ignore-trailing-spaces.yaml
ascii-art/*
You can also use the ``.gitignore`` file (or any list of files) through:
.. code-block:: yaml
ignore-from-file: .gitignore
or:
.. code-block:: yaml
ignore-from-file: [.gitignore, .yamlignore]
.. note:: However, this is mutually exclusive with the ``ignore`` key.
Setting the locale
------------------

View File

@@ -22,29 +22,22 @@ Integration with GitHub Actions
-------------------------------
yamllint auto-detects when it's running inside of `GitHub
Actions <https://github.com/features/actions>`_ and automatically uses the suited
output format to decorate code with linting errors. You can also force the
GitHub Actions output with ``yamllint --format github``.
Actions <https://github.com/features/actions>`_ and automatically uses the
suited output format to decorate code with linting errors. You can also force
the GitHub Actions output with ``yamllint --format github``.
An example workflow using GitHub Actions:
An minimal example workflow using GitHub Actions:
.. code:: yaml
---
name: yamllint test
on: push
on: push # yamllint disable-line rule:truthy
jobs:
test:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- uses: actions/checkout@v3
- name: Install yamllint
run: pip install yamllint

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -9,7 +9,7 @@ text editor.
Vim
---
Assuming that the `ALE <https://github.com/w0rp/ale>`_ plugin is
Assuming that the `ALE <https://github.com/dense-analysis/ale>`_ plugin is
installed, yamllint is supported by default. It is automatically enabled when
editing YAML files.

View File

@@ -453,3 +453,106 @@ class QuotedTestCase(RuleTestCase):
'- "0o800"\n',
conf,
problem1=(9, 3), problem2=(10, 3))
def test_allow_quoted_quotes(self):
conf = ('quoted-strings: {quote-type: single,\n'
' required: false,\n'
' allow-quoted-quotes: false}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n', # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: false,\n'
' allow-quoted-quotes: true}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n',
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: true,\n'
' allow-quoted-quotes: false}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n', # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: true,\n'
' allow-quoted-quotes: true}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n',
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: only-when-needed,\n'
' allow-quoted-quotes: false}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n', # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: only-when-needed,\n'
' allow-quoted-quotes: true}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n',
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: false,\n'
' allow-quoted-quotes: false}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n", # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: false,\n'
' allow-quoted-quotes: true}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n",
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: true,\n'
' allow-quoted-quotes: false}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n", # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: true,\n'
' allow-quoted-quotes: true}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n",
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: only-when-needed,\n'
' allow-quoted-quotes: false}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n", # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: only-when-needed,\n'
' allow-quoted-quotes: true}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n",
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: any}\n')
self.check("---\n"
"foo1: '[barbaz]'\n"
"foo2: '[bar\"baz]'\n",
conf)

View File

@@ -29,7 +29,7 @@ from yamllint import cli
from yamllint import config
class RunContext(object):
class RunContext:
"""Context manager for ``cli.run()`` to capture exit code and streams."""
def __init__(self, case):

View File

@@ -22,6 +22,7 @@ import unittest
from tests.common import build_temp_workspace
from yamllint.config import YamlLintConfigError
from yamllint import cli
from yamllint import config
@@ -121,7 +122,7 @@ class SimpleConfigTestCase(unittest.TestCase):
self.assertEqual(c.rules['hyphens'], False)
def test_validate_rule_conf(self):
class Rule(object):
class Rule:
ID = 'fake'
self.assertFalse(config.validate_rule_conf(Rule, False))
@@ -429,10 +430,10 @@ class ExtendedLibraryConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['empty-lines']['max-end'], 0)
class IgnorePathConfigTestCase(unittest.TestCase):
class IgnoreConfigTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
super(IgnorePathConfigTestCase, cls).setUpClass()
super().setUpClass()
bad_yaml = ('---\n'
'- key: val1\n'
@@ -452,22 +453,6 @@ class IgnorePathConfigTestCase(unittest.TestCase):
's/s/ign-trail/file.yaml': bad_yaml,
's/s/ign-trail/s/s/file.yaml': bad_yaml,
's/s/ign-trail/s/s/file2.lint-me-anyway.yaml': bad_yaml,
'.yamllint': 'ignore: |\n'
' *.dont-lint-me.yaml\n'
' /bin/\n'
' !/bin/*.lint-me-anyway.yaml\n'
'\n'
'extends: default\n'
'\n'
'rules:\n'
' key-duplicates:\n'
' ignore: |\n'
' /ign-dup\n'
' trailing-spaces:\n'
' ignore: |\n'
' ign-trail\n'
' !*.lint-me-anyway.yaml\n',
})
cls.backup_wd = os.getcwd()
@@ -475,13 +460,101 @@ class IgnorePathConfigTestCase(unittest.TestCase):
@classmethod
def tearDownClass(cls):
super(IgnorePathConfigTestCase, cls).tearDownClass()
super().tearDownClass()
os.chdir(cls.backup_wd)
shutil.rmtree(cls.wd)
def test_run_with_ignored_path(self):
def test_mutually_exclusive_ignore_keys(self):
self.assertRaises(
YamlLintConfigError,
config.YamlLintConfig, 'extends: default\n'
'ignore-from-file: .gitignore\n'
'ignore: |\n'
' *.dont-lint-me.yaml\n'
' /bin/\n')
def test_ignore_from_file_not_exist(self):
self.assertRaises(
FileNotFoundError,
config.YamlLintConfig, 'extends: default\n'
'ignore-from-file: not_found_file\n')
def test_ignore_from_file_incorrect_type(self):
self.assertRaises(
YamlLintConfigError,
config.YamlLintConfig, 'extends: default\n'
'ignore-from-file: 0\n')
self.assertRaises(
YamlLintConfigError,
config.YamlLintConfig, 'extends: default\n'
'ignore-from-file: [0]\n')
def test_no_ignore(self):
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
out = sys.stdout.getvalue()
out = '\n'.join(sorted(out.splitlines()))
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
trailing = '[error] trailing spaces (trailing-spaces)'
hyphen = '[error] too many spaces after hyphen (hyphens)'
self.assertEqual(out, '\n'.join((
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
'./bin/file.yaml:3:3: ' + keydup,
'./bin/file.yaml:4:17: ' + trailing,
'./bin/file.yaml:5:5: ' + hyphen,
'./file-at-root.yaml:3:3: ' + keydup,
'./file-at-root.yaml:4:17: ' + trailing,
'./file-at-root.yaml:5:5: ' + hyphen,
'./file.dont-lint-me.yaml:3:3: ' + keydup,
'./file.dont-lint-me.yaml:4:17: ' + trailing,
'./file.dont-lint-me.yaml:5:5: ' + hyphen,
'./ign-dup/file.yaml:3:3: ' + keydup,
'./ign-dup/file.yaml:4:17: ' + trailing,
'./ign-dup/file.yaml:5:5: ' + hyphen,
'./ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./ign-trail/file.yaml:3:3: ' + keydup,
'./ign-trail/file.yaml:4:17: ' + trailing,
'./ign-trail/file.yaml:5:5: ' + hyphen,
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/file.yaml:4:17: ' + trailing,
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))
def test_run_with_ignore(self):
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
f.write('extends: default\n'
'ignore: |\n'
' *.dont-lint-me.yaml\n'
' /bin/\n'
' !/bin/*.lint-me-anyway.yaml\n'
'rules:\n'
' key-duplicates:\n'
' ignore: |\n'
' /ign-dup\n'
' trailing-spaces:\n'
' ignore: |\n'
' ign-trail\n'
' !*.lint-me-anyway.yaml\n')
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
@@ -519,3 +592,108 @@ class IgnorePathConfigTestCase(unittest.TestCase):
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))
def test_run_with_ignore_from_file(self):
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
f.write('extends: default\n'
'ignore-from-file: .gitignore\n')
with open(os.path.join(self.wd, '.gitignore'), 'w') as f:
f.write('*.dont-lint-me.yaml\n'
'/bin/\n'
'!/bin/*.lint-me-anyway.yaml\n')
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
out = sys.stdout.getvalue()
out = '\n'.join(sorted(out.splitlines()))
docstart = '[warning] missing document start "---" (document-start)'
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
trailing = '[error] trailing spaces (trailing-spaces)'
hyphen = '[error] too many spaces after hyphen (hyphens)'
self.assertEqual(out, '\n'.join((
'./.yamllint:1:1: ' + docstart,
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
'./file-at-root.yaml:3:3: ' + keydup,
'./file-at-root.yaml:4:17: ' + trailing,
'./file-at-root.yaml:5:5: ' + hyphen,
'./ign-dup/file.yaml:3:3: ' + keydup,
'./ign-dup/file.yaml:4:17: ' + trailing,
'./ign-dup/file.yaml:5:5: ' + hyphen,
'./ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./ign-trail/file.yaml:3:3: ' + keydup,
'./ign-trail/file.yaml:4:17: ' + trailing,
'./ign-trail/file.yaml:5:5: ' + hyphen,
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/file.yaml:4:17: ' + trailing,
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))
def test_run_with_ignored_from_file(self):
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
f.write('ignore-from-file: [.gitignore, .yamlignore]\n'
'extends: default\n')
with open(os.path.join(self.wd, '.gitignore'), 'w') as f:
f.write('*.dont-lint-me.yaml\n'
'/bin/\n')
with open(os.path.join(self.wd, '.yamlignore'), 'w') as f:
f.write('!/bin/*.lint-me-anyway.yaml\n')
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
out = sys.stdout.getvalue()
out = '\n'.join(sorted(out.splitlines()))
docstart = '[warning] missing document start "---" (document-start)'
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
trailing = '[error] trailing spaces (trailing-spaces)'
hyphen = '[error] too many spaces after hyphen (hyphens)'
self.assertEqual(out, '\n'.join((
'./.yamllint:1:1: ' + docstart,
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
'./file-at-root.yaml:3:3: ' + keydup,
'./file-at-root.yaml:4:17: ' + trailing,
'./file-at-root.yaml:5:5: ' + hyphen,
'./ign-dup/file.yaml:3:3: ' + keydup,
'./ign-dup/file.yaml:4:17: ' + trailing,
'./ign-dup/file.yaml:5:5: ' + hyphen,
'./ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./ign-trail/file.yaml:3:3: ' + keydup,
'./ign-trail/file.yaml:4:17: ' + trailing,
'./ign-trail/file.yaml:5:5: ' + hyphen,
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/file.yaml:4:17: ' + trailing,
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))

View File

@@ -21,7 +21,7 @@ indentation, etc."""
APP_NAME = 'yamllint'
APP_VERSION = '1.27.1'
APP_VERSION = '1.28.0'
APP_DESCRIPTION = __doc__
__author__ = u'Adrien Vergé'

View File

@@ -13,8 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
import argparse
import io
import locale

View File

@@ -13,6 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import fileinput
import os.path
import pathspec
@@ -25,7 +26,7 @@ class YamlLintConfigError(Exception):
pass
class YamlLintConfig(object):
class YamlLintConfig:
def __init__(self, content=None, file=None):
assert (content is None) ^ (file is None)
@@ -96,7 +97,21 @@ class YamlLintConfig(object):
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
if 'ignore' in conf:
if 'ignore' in conf and 'ignore-from-file' in conf:
raise YamlLintConfigError(
'invalid config: ignore and ignore-from-file keys cannot be '
'used together')
elif 'ignore-from-file' in conf:
if isinstance(conf['ignore-from-file'], str):
conf['ignore-from-file'] = [conf['ignore-from-file']]
if not (isinstance(conf['ignore-from-file'], list) and all(
isinstance(ln, str) for ln in conf['ignore-from-file'])):
raise YamlLintConfigError(
'invalid config: ignore-from-file should contain '
'filename(s), either as a list or string')
with fileinput.input(conf['ignore-from-file']) as f:
self.ignore = pathspec.PathSpec.from_lines('gitwildmatch', f)
elif 'ignore' in conf:
if not isinstance(conf['ignore'], str):
raise YamlLintConfigError(
'invalid config: ignore should contain file patterns')
@@ -150,7 +165,7 @@ def validate_rule_conf(rule, conf):
options = getattr(rule, 'CONF', {})
options_default = getattr(rule, 'DEFAULT', {})
for optkey in conf:
if optkey in ('ignore', 'level'):
if optkey in ('ignore', 'ignore-from-file', 'level'):
continue
if optkey not in options:
raise YamlLintConfigError(

View File

@@ -29,6 +29,9 @@ PROBLEM_LEVELS = {
'error': 2,
}
DISABLE_RULE_PATTERN = re.compile(r'^# yamllint disable( rule:\S+)*\s*$')
ENABLE_RULE_PATTERN = re.compile(r'^# yamllint enable( rule:\S+)*\s*$')
class LintProblem(object):
"""Represents a linting problem found by yamllint."""
@@ -82,7 +85,7 @@ def get_cosmetic_problems(buffer, conf, filepath):
def process_comment(self, comment):
comment = str(comment)
if re.match(r'^# yamllint disable( rule:\S+)*\s*$', comment):
if DISABLE_RULE_PATTERN.match(comment):
items = comment[18:].rstrip().split(' ')
rules = [item[5:] for item in items][1:]
if len(rules) == 0:
@@ -92,7 +95,7 @@ def get_cosmetic_problems(buffer, conf, filepath):
if id in self.all_rules:
self.rules.add(id)
elif re.match(r'^# yamllint enable( rule:\S+)*\s*$', comment):
elif ENABLE_RULE_PATTERN.match(comment):
items = comment[17:].rstrip().split(' ')
rules = [item[5:] for item in items][1:]
if len(rules) == 0:

View File

@@ -16,7 +16,7 @@
import yaml
class Line(object):
class Line:
def __init__(self, line_no, buffer, start, end):
self.line_no = line_no
self.start = start
@@ -28,7 +28,7 @@ class Line(object):
return self.buffer[self.start:self.end]
class Token(object):
class Token:
def __init__(self, line_no, curr, prev, next, nextnext):
self.line_no = line_no
self.curr = curr
@@ -37,7 +37,7 @@ class Token(object):
self.nextnext = nextnext
class Comment(object):
class Comment:
def __init__(self, line_no, column_no, buffer, pointer,
token_before=None, token_after=None, comment_before=None):
self.line_no = line_no

View File

@@ -218,7 +218,7 @@ ROOT, B_MAP, F_MAP, B_SEQ, F_SEQ, B_ENT, KEY, VAL = range(8)
labels = ('ROOT', 'B_MAP', 'F_MAP', 'B_SEQ', 'F_SEQ', 'B_ENT', 'KEY', 'VAL')
class Parent(object):
class Parent:
def __init__(self, type, indent, line_indent=None):
self.type = type
self.indent = indent

View File

@@ -64,7 +64,7 @@ TYPE = 'token'
MAP, SEQ = range(2)
class Parent(object):
class Parent:
def __init__(self, type):
self.type = type
self.keys = []

View File

@@ -96,7 +96,7 @@ def check(conf, token, prev, next, nextnext, context):
if not token.style:
val = token.value
if (val.isdigit() and len(val) > 1 and val[0] == '0' and
IS_OCTAL_NUMBER_PATTERN.match(val[1:]) is not None):
IS_OCTAL_NUMBER_PATTERN.match(val[1:])):
yield LintProblem(
token.start_mark.line + 1, token.end_mark.column + 1,
'forbidden implicit octal value "%s"' %
@@ -107,7 +107,7 @@ def check(conf, token, prev, next, nextnext, context):
if not token.style:
val = token.value
if (len(val) > 2 and val[:2] == '0o' and
IS_OCTAL_NUMBER_PATTERN.match(val[2:]) is not None):
IS_OCTAL_NUMBER_PATTERN.match(val[2:])):
yield LintProblem(
token.start_mark.line + 1, token.end_mark.column + 1,
'forbidden explicit octal value "%s"' %

View File

@@ -30,6 +30,8 @@ used.
``required: false`` and ``required: only-when-needed``.
* ``extra-allowed`` is a list of PCRE regexes to allow quoted string values,
even if ``required: only-when-needed`` is set.
* ``allow-quoted-quotes`` allows (``true``) using disallowed quotes for strings
with allowed quotes inside. Default ``false``.
**Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
@@ -43,6 +45,7 @@ used.
required: true
extra-required: []
extra-allowed: []
allow-quoted-quotes: false
.. rubric:: Examples
@@ -112,6 +115,26 @@ used.
- "localhost"
- this is a string that needs to be QUOTED
#. With ``quoted-strings: {quote-type: double, allow-quoted-quotes: false}``
the following code snippet would **PASS**:
::
foo: "bar\\"baz"
the following code snippet would **FAIL**:
::
foo: 'bar"baz'
#. With ``quoted-strings: {quote-type: double, allow-quoted-quotes: true}``
the following code snippet would **PASS**:
::
foo: 'bar"baz'
"""
import re
@@ -125,11 +148,13 @@ TYPE = 'token'
CONF = {'quote-type': ('any', 'single', 'double'),
'required': (True, False, 'only-when-needed'),
'extra-required': [str],
'extra-allowed': [str]}
'extra-allowed': [str],
'allow-quoted-quotes': bool}
DEFAULT = {'quote-type': 'any',
'required': True,
'extra-required': [],
'extra-allowed': []}
'extra-allowed': [],
'allow-quoted-quotes': False}
def VALIDATE(conf):
@@ -177,6 +202,12 @@ def _quotes_are_needed(string):
return True
def _has_quoted_quotes(token):
return ((not token.plain) and
((token.style == "'" and '"' in token.value) or
(token.style == '"' and "'" in token.value)))
def check(conf, token, prev, next, nextnext, context):
if not (isinstance(token, yaml.tokens.ScalarToken) and
isinstance(prev, (yaml.BlockEntryToken, yaml.FlowEntryToken,
@@ -206,13 +237,18 @@ def check(conf, token, prev, next, nextnext, context):
if conf['required'] is True:
# Quotes are mandatory and need to match config
if token.style is None or not _quote_match(quote_type, token.style):
if (token.style is None or
not (_quote_match(quote_type, token.style) or
(conf['allow-quoted-quotes'] and _has_quoted_quotes(token)))):
msg = "string value is not quoted with %s quotes" % quote_type
elif conf['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):
if (token.style and
not _quote_match(quote_type, token.style) and
not (conf['allow-quoted-quotes'] and
_has_quoted_quotes(token))):
msg = "string value is not quoted with %s quotes" % quote_type
elif not token.style:
@@ -235,7 +271,9 @@ def check(conf, token, prev, next, nextnext, context):
quote_type)
# But when used need to match config
elif token.style and not _quote_match(quote_type, token.style):
elif (token.style and
not _quote_match(quote_type, token.style) and
not (conf['allow-quoted-quotes'] and _has_quoted_quotes(token))):
msg = "string value is not quoted with %s quotes" % quote_type
elif not token.style: