Compare commits

...

36 Commits

Author SHA1 Message Date
Adrien Vergé
b9e1fd18c1 yamllint version 1.29.0 2023-01-10 18:57:21 +01:00
Peter Leitzen
fa0bb03f9a cli: Add --list-files command line option
This option lists the files to lint by yamllint, taking into account `ignore`
and `yaml-files` configuration options.
2023-01-10 18:48:38 +01:00
Matthew Gamble
2a904f8fc1 configuration: Allow using a list of strings in ignore configuration
This may feel more natural for some users, rather than embedding
multiple entries in a multi-line string.
2023-01-10 18:45:09 +01:00
Ville Skyttä
6194a282fc docs: Spelling and grammar fixes 2022-12-12 19:08:31 +01:00
Dimitri Papadopoulos
5ac3ed4490 Apply some pyupgrade suggestions
io.open() is an alias for for the builtin open() function:
	https://docs.python.org/3/library/io.html#io.open

If open() cannot open a file, an OSError is raised:
	https://docs.python.org/3/library/functions.html#open

EnvironmentError is kept for compatibility with previous versions;
starting from Python 3.3, it is an alias of OSError:
	https://docs.python.org/3/library/exceptions.html#EnvironmentError

Directly yield from an iterable instead of iterating to yield items.
2022-10-28 07:48:54 +02:00
Dimitri Papadopoulos
5b21a3d9ea Remove Unicode marker before strings
All strings are Unicode in Python 3. No need for u'€', just use '€'.
2022-10-28 07:46:33 +02:00
Dimitri Papadopoulos
5fbf44c203 docs: Fix typos 2022-10-28 07:42:28 +02:00
Michael Käufl
c9c5e0b1c7 CI: Add support for Python 3.11 2022-10-26 19:27:06 +02:00
Adrien Vergé
a6e0e1213a CI: Fix pip install problem with Pygments version
Recently `python setup.py build_sphinx` started failing with:

    pkg_resources.VersionConflict: (Pygments 2.3.1
    (/usr/lib/python3/dist-packages), Requirement.parse('Pygments>=2.12'))

The reason is that `doc8` 1.0.0 installs `Pygments` 2.3.1, then `Sphinx`
5.3.0 needs `Pygments` ≥ 2.12.

The easiest fix is to change the install order.
2022-10-26 16:37:45 +02:00
Adrien Vergé
eb7b7ca627 docs: Fix Sphinx error on non-YAML code snippet
This problem was just introduced by commit cec4f33 "Clarify disable-line
and parser errors, workaround" and produced this error when building
documentation:

    docs/disable_with_comments.rst:120:Could not lex literal_block as
    "yaml". Highlighting skipped.
2022-10-26 16:37:45 +02:00
Andrew Imeson
cec4f3383a cocs: Clarify disable-line and parser errors, workaround
Lots of user confusion expecting `disable-line` to work around parser
errors caused by templating syntax.

Relates to #61, #65, #128, #311, #460, #462
2022-10-24 14:44:05 +02:00
Andrew Imeson
52234b7a46 docs: remove erroneous example text in disable-file 2022-10-24 14:44:05 +02:00
Adrien Vergé
151b1c4086 style: Fix indentation test file missing whitespace
Commit 764586d "indentation: Fix indent-sequences in nested collections"
introduced 2 style-related problems:

    tests/rules/test_indentation.py:1394:45: E231 missing whitespace after ','
    tests/rules/test_indentation.py:1402:45: E231 missing whitespace after ','

Let's fix them.
2022-10-24 14:37:48 +02:00
Brian Brookman
764586d836 indentation: Fix indent-sequences in nested collections
When {spaces: consistent, indent-sequences: true} (the defaults),
and the expected indent for a block sequence that should be indented
is unknown, set the expected indent to an unknown value (-1) rather
than an arbitrary one (2). This ensures wrong-indentation is properly
caught when block sequences are nested.

Fixes issue #485
2022-10-18 18:04:49 +02:00
Dimitri Papadopoulos
47cd8f2e9e No need to inherit from object in Python 3 2022-10-18 17:52:06 +02:00
Dimitri Papadopoulos
4d271f3daf ci: Security hardening for GitHub Actions
https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs

The idea is that the software supply chain relies on 3rd party actions
that could be compromised. Mitigate this risk by giving these actions
minimal rights to the repository. Here read-only access is good enough.
2022-10-14 17:30:28 +02:00
Dimitri Papadopoulos
22ddf4c8e5 linter: Use proper Python 3 I/O type for reading
Co-authored-by: Adrien Vergé <adrienverge@gmail.com>
2022-10-14 17:29:48 +02:00
Adrien Vergé
b8c85f0dfd build: Stop releasing universal wheels
This reverts commit 3c525ab "Release as a universal wheel".

Python 2 support was definitely dropped in early 2021 in commit a3fc64d,
since then it's no longer useful to build universal wheels.

According to the `wheel` documentation:
> If your project contains no C extensions and is expected to work on
> both Python 2 and 3, you will want to tell wheel to produce universal
> wheels

Partly fixes https://github.com/adrienverge/yamllint/issues/501
2022-10-11 11:38:01 +02:00
Dimitri Papadopoulos
e0f749bf5d comments-indentation: Refactor to use 'max' builtin
It is unnecessary to use an `if` statement to check the maximum of two
values and then assign the value to a name. You can use the max
built-in do do this. It is straightforward and more readable.
2022-10-07 17:01:51 +02:00
Dimitri Papadopoulos
19d00809d1 quoted-strings: Merge comparisons with in
To check if a variable is equal to one of many values, combine the
values into a tuple and check if the variable is contained in it
instead of checking for equality against each of the values. This
is faster, less verbose, and more readable.
2022-10-07 17:01:51 +02:00
Adrien Vergé
008db4aa09 float-values: Fix bug on strings containing fordidden values
The rule correctly reports number values like `.1`, `1e2`, `.NaN` and
`.Inf`, but it also reported false positives on strings like `.1two3`,
`1e2a`, `.NaNa` and `.Infinit∞`.

The regexps need to end with an end delimiter (`$`) otherwise longer
strings can be matched too.

Fixes https://github.com/adrienverge/yamllint/issues/495
2022-10-04 08:59:51 +02:00
Dimitri Papadopoulos Orfanos
e8391de711 Drop support for Python 3.6
Python 3.6 reached end-of-life on 2021-12-23.
https://peps.python.org/pep-0494/
2022-09-13 07:59:17 +02:00
Dimitri Papadopoulos
a5adec1570 ci: Update GitHub Actions
https://github.com/actions/checkout
https://github.com/actions/setup-python
2022-09-13 07:58:03 +02:00
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
30 changed files with 727 additions and 134 deletions

View File

@@ -8,18 +8,21 @@ on: # yamllint disable-line rule:truthy
branches: branches:
- master - master
permissions:
contents: read
jobs: jobs:
lint: lint:
name: Linters name: Linters
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v2 uses: actions/setup-python@v4
- run: - run:
python -m pip install flake8 flake8-import-order doc8 sphinx python -m pip install flake8 flake8-import-order sphinx
rstcheck[sphinx] rstcheck[sphinx] doc8
- run: python -m pip install . - run: python -m pip install .
- run: flake8 . - run: flake8 .
- run: doc8 $(git ls-files '*.rst') - run: doc8 $(git ls-files '*.rst')
@@ -34,16 +37,16 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: python-version:
- '3.6'
- '3.7' - '3.7'
- '3.8' - '3.8'
- '3.9' - '3.9'
- '3.10' - '3.10'
- '3.11'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Append GitHub Actions system path - name: Append GitHub Actions system path

View File

@@ -1,6 +1,32 @@
Changelog Changelog
========= =========
1.29.0 (2023-01-10)
-------------------
- Add support for Python 3.11, drop support for Python 3.6
- Rule ``float-values``: fix bug on strings containing fordidden values
- Stop releasing universal wheels
- Use proper Python 3 I/O type for file reading
- Rule ``indentation``: fix ``indent-sequences`` in nested collections
- Docs: clarify ``disable-line`` and parser errors, give a workaround
- Refactors to apply some pyupgrade suggestions
- Allow using a list of strings in ``ignore`` configuration
- Add ``--list-files`` command line option
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) 1.27.1 (2022-07-08)
------------------- -------------------
@@ -14,7 +40,7 @@ Changelog
- Refactor ``--format=auto`` logic - Refactor ``--format=auto`` logic
- Update GitHub format output to use groups - Update GitHub format output to use groups
- Rule ``comments``: allow whitespace after the shebang marker - 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 - Configure Sphinx to make man page show up in apropos
- Attempt to clarify configuration file location in documentation - Attempt to clarify configuration file location in documentation
- Rule ``key-duplicates``: don't crash on redundant closing brackets or braces - Rule ``key-duplicates``: don't crash on redundant closing brackets or braces

View File

@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 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 Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. 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. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail. 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, 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. 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 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 The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with 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 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 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

@@ -38,7 +38,7 @@ htmlhelp_basename = 'yamllintdoc'
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
('index', 'yamllint', 'Linter for YAML files', [u'Adrien Vergé'], 1) ('index', 'yamllint', 'Linter for YAML files', ['Adrien Vergé'], 1)
] ]
# -- Build with sphinx automodule without needing to install third-party libs # -- Build with sphinx automodule without needing to install third-party libs

View File

@@ -136,11 +136,19 @@ directories, set ``yaml-files`` configuration option. The default is:
The same rules as for ignoring paths apply (``.gitignore``-style path pattern, The same rules as for ignoring paths apply (``.gitignore``-style path pattern,
see below). see below).
If you need to know the exact list of files that yamllint would process,
without really linting them, you can use ``--list-files``:
.. code:: bash
yamllint --list-files .
Ignoring paths Ignoring paths
-------------- --------------
It is possible to exclude specific files or directories, so that the linter It is possible to exclude specific files or directories, so that the linter
doesn't process them. doesn't process them. They can be provided either as a list of paths, or as a
bulk string.
You can either totally ignore files (they won't be looked at): You can either totally ignore files (they won't be looked at):
@@ -153,6 +161,13 @@ You can either totally ignore files (they won't be looked at):
all/this/directory/ all/this/directory/
*.template.yaml *.template.yaml
# or:
ignore:
- /this/specific/file.yaml
- all/this/directory/
- '*.template.yaml'
or ignore paths only for specific rules: or ignore paths only for specific rules:
.. code-block:: yaml .. code-block:: yaml
@@ -165,6 +180,14 @@ or ignore paths only for specific rules:
/this-file-has-trailing-spaces-but-it-is-OK.yaml /this-file-has-trailing-spaces-but-it-is-OK.yaml
/generated/*.yaml /generated/*.yaml
# or:
rules:
trailing-spaces:
ignore:
- /this-file-has-trailing-spaces-but-it-is-OK.yaml
- /generated/*.yaml
Note that this ``.gitignore``-style path pattern allows complex path Note that this ``.gitignore``-style path pattern allows complex path
exclusion/inclusion, see the `pathspec README file exclusion/inclusion, see the `pathspec README file
<https://pypi.python.org/pypi/pathspec>`_ for more details. <https://pypi.python.org/pypi/pathspec>`_ for more details.
@@ -190,6 +213,27 @@ Here is a more complex example:
*.ignore-trailing-spaces.yaml *.ignore-trailing-spaces.yaml
ascii-art/* 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.
If you need to know the exact list of files that yamllint would process,
without really linting them, you can use ``--list-files``:
.. code:: bash
yamllint --list-files .
Setting the locale Setting the locale
------------------ ------------------

View File

@@ -40,6 +40,11 @@ specific line:
# yamllint disable-line # yamllint disable-line
- { all : rules ,are disabled for this line} - { all : rules ,are disabled for this line}
You can't make yamllint ignore invalid YAML syntax on a line (which generates a
`syntax error`), such as when templating a YAML file with Jinja. In some cases,
you can workaround this by putting the templating syntax in a YAML comment. See
`Putting template flow control in comments`_.
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-line rule:hyphens rule:commas rule:indentation``. ``# yamllint disable-line rule:hyphens rule:commas rule:indentation``.
@@ -89,7 +94,6 @@ For instance:
key: value 2 key: value 2
- This line is waaaaaaaaaay too long but yamllint will not report anything about it. - This line is waaaaaaaaaay too long but yamllint will not report anything about it.
This line will be checked by yamllint.
or: or:
@@ -101,3 +105,32 @@ or:
key1: value1 key1: value1
{% endif %} {% endif %}
key2: value2 key2: value2
Putting template flow control in comments
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Alternatively for templating you can wrap the template statements in comments
to make it a valid YAML file. As long as the templating language doesn't use
the same comment symbol, it should be a valid template and valid YAML (pre and
post-template processing).
Example of a Jinja2 code that cannot be parsed as YAML because it contains
invalid tokens ``{%`` and ``%}``:
.. code-block::
# This file IS NOT valid YAML and will produce syntax errors
{% if extra_info %}
key1: value1
{% endif %}
key2: value2
But it can be fixed using YAML comments:
.. code-block:: yaml
# This file IS valid YAML because the Jinja is in a YAML comment
# {% if extra_info %}
key1: value1
# {% endif %}
key2: value2

View File

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

View File

@@ -1,6 +1,3 @@
[bdist_wheel]
universal = 1
[flake8] [flake8]
import-order-style = pep8 import-order-style = pep8
application-import-names = yamllint application-import-names = yamllint
@@ -27,11 +24,11 @@ classifiers =
Intended Audience :: Developers Intended Audience :: Developers
License :: OSI Approved :: GNU General Public License v3 (GPLv3) License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Topic :: Software Development Topic :: Software Development
Topic :: Software Development :: Debuggers Topic :: Software Development :: Debuggers
Topic :: Software Development :: Quality Assurance Topic :: Software Development :: Quality Assurance
@@ -46,7 +43,7 @@ project_urls =
[options] [options]
packages = find: packages = find:
python_requires = >=3.6 python_requires = >=3.7
include_package_data = True include_package_data = True
install_requires = install_requires =

View File

@@ -40,6 +40,8 @@ class FloatValuesTestCase(RuleTestCase):
'- 0.0\n' '- 0.0\n'
'- .1\n' '- .1\n'
'- \'.1\'\n' '- \'.1\'\n'
'- string.1\n'
'- .1string\n'
'- !custom_tag .2\n' '- !custom_tag .2\n'
'- &angle1 0.0\n' '- &angle1 0.0\n'
'- *angle1\n' '- *angle1\n'
@@ -47,7 +49,7 @@ class FloatValuesTestCase(RuleTestCase):
'- *angle2\n', '- *angle2\n',
conf, conf,
problem1=(3, 3), problem1=(3, 3),
problem2=(8, 11)) problem2=(10, 11))
def test_scientific_notation(self): def test_scientific_notation(self):
conf = ( conf = (
@@ -61,6 +63,8 @@ class FloatValuesTestCase(RuleTestCase):
'- 10e-6\n' '- 10e-6\n'
'- 0.00001\n' '- 0.00001\n'
'- \'10e-6\'\n' '- \'10e-6\'\n'
'- string10e-6\n'
'- 10e-6string\n'
'- !custom_tag 10e-6\n' '- !custom_tag 10e-6\n'
'- &angle1 0.000001\n' '- &angle1 0.000001\n'
'- *angle1\n' '- *angle1\n'
@@ -71,8 +75,8 @@ class FloatValuesTestCase(RuleTestCase):
conf, conf,
problem1=(2, 3), problem1=(2, 3),
problem2=(3, 3), problem2=(3, 3),
problem3=(9, 11), problem3=(11, 11),
problem4=(11, 11)) problem4=(13, 11))
def test_nan(self): def test_nan(self):
conf = ( conf = (
@@ -85,13 +89,15 @@ class FloatValuesTestCase(RuleTestCase):
'- .NaN\n' '- .NaN\n'
'- .NAN\n' '- .NAN\n'
'- \'.NaN\'\n' '- \'.NaN\'\n'
'- a.NaN\n'
'- .NaNa\n'
'- !custom_tag .NaN\n' '- !custom_tag .NaN\n'
'- &angle .nan\n' '- &angle .nan\n'
'- *angle\n', '- *angle\n',
conf, conf,
problem1=(2, 3), problem1=(2, 3),
problem2=(3, 3), problem2=(3, 3),
problem3=(6, 10)) problem3=(8, 10))
def test_inf(self): def test_inf(self):
conf = ( conf = (
@@ -106,6 +112,8 @@ class FloatValuesTestCase(RuleTestCase):
'- -.inf\n' '- -.inf\n'
'- -.INF\n' '- -.INF\n'
'- \'.inf\'\n' '- \'.inf\'\n'
'- ∞.infinity\n'
'- .infinity∞\n'
'- !custom_tag .inf\n' '- !custom_tag .inf\n'
'- &angle .inf\n' '- &angle .inf\n'
'- *angle\n' '- *angle\n'
@@ -116,5 +124,5 @@ class FloatValuesTestCase(RuleTestCase):
problem2=(3, 3), problem2=(3, 3),
problem3=(4, 3), problem3=(4, 3),
problem4=(5, 3), problem4=(5, 3),
problem5=(8, 10), problem5=(10, 10),
problem6=(10, 10)) problem6=(12, 10))

View File

@@ -1370,6 +1370,45 @@ class IndentationTestCase(RuleTestCase):
' key: value\n' ' key: value\n'
'...\n', conf, problem=(2, 2)) '...\n', conf, problem=(2, 2))
def test_nested_collections_with_spaces_consistent(self):
"""Tests behavior of {spaces: consistent} in nested collections to
ensure wrong-indentation is properly caught--especially when the
expected indent value is initially unknown. For details, see
https://github.com/adrienverge/yamllint/issues/485.
"""
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: true}')
self.check('---\n'
'- item:\n'
' - elem\n'
'- item:\n'
' - elem\n'
'...\n', conf, problem=(3, 3))
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: false}')
self.check('---\n'
'- item:\n'
' - elem\n'
'- item:\n'
' - elem\n'
'...\n', conf, problem=(5, 5))
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: consistent}')
self.check('---\n'
'- item:\n'
' - elem\n'
'- item:\n'
' - elem\n'
'...\n', conf, problem=(5, 5))
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: whatever}')
self.check('---\n'
'- item:\n'
' - elem\n'
'- item:\n'
' - elem\n'
'...\n', conf)
def test_return(self): def test_return(self):
conf = 'indentation: {spaces: consistent}' conf = 'indentation: {spaces: consistent}'
self.check('---\n' self.check('---\n'

View File

@@ -453,3 +453,106 @@ class QuotedTestCase(RuleTestCase):
'- "0o800"\n', '- "0o800"\n',
conf, conf,
problem1=(9, 3), problem2=(10, 3)) 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 from yamllint import config
class RunContext(object): class RunContext:
"""Context manager for ``cli.run()`` to capture exit code and streams.""" """Context manager for ``cli.run()`` to capture exit code and streams."""
def __init__(self, case): def __init__(self, case):
@@ -92,12 +92,12 @@ class CommandLineTestCase(unittest.TestCase):
'no-yaml.json': '---\n' 'no-yaml.json': '---\n'
'key: value\n', 'key: value\n',
# non-ASCII chars # non-ASCII chars
u'non-ascii/éçäγλνπ¥/utf-8': ( 'non-ascii/éçäγλνπ¥/utf-8': (
u'---\n' '---\n'
u'- hétérogénéité\n' '- hétérogénéité\n'
u'# 19.99 €\n' '# 19.99 €\n'
u'- お早う御座います。\n' '- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'), '# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
# dos line endings yaml # dos line endings yaml
'dos.yml': '---\r\n' 'dos.yml': '---\r\n'
'dos: true', 'dos: true',
@@ -678,6 +678,39 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual( self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, '')) (ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_list_files(self):
with RunContext(self) as ctx:
cli.run(('--list-files', self.wd))
self.assertEqual(ctx.returncode, 0)
self.assertEqual(
sorted(ctx.stdout.splitlines()),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 'en.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/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
config = '{ignore: "*.yml", yaml-files: ["*.*"]}'
with RunContext(self) as ctx:
cli.run(('--list-files', '-d', config, self.wd))
self.assertEqual(ctx.returncode, 0)
self.assertEqual(
sorted(ctx.stdout.splitlines()),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 'no-yaml.json'),
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/directory.yaml/not-yaml.txt'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
class CommandLineConfigTestCase(unittest.TestCase): class CommandLineConfigTestCase(unittest.TestCase):
def test_config_file(self): def test_config_file(self):

View File

@@ -22,6 +22,7 @@ import unittest
from tests.common import build_temp_workspace from tests.common import build_temp_workspace
from yamllint.config import YamlLintConfigError
from yamllint import cli from yamllint import cli
from yamllint import config from yamllint import config
@@ -121,7 +122,7 @@ class SimpleConfigTestCase(unittest.TestCase):
self.assertEqual(c.rules['hyphens'], False) self.assertEqual(c.rules['hyphens'], False)
def test_validate_rule_conf(self): def test_validate_rule_conf(self):
class Rule(object): class Rule:
ID = 'fake' ID = 'fake'
self.assertFalse(config.validate_rule_conf(Rule, False)) self.assertFalse(config.validate_rule_conf(Rule, False))
@@ -368,7 +369,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(c.rules['colons']['max-spaces-before'], 0) self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1) self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
def test_extended_ignore(self): def test_extended_ignore_str(self):
with tempfile.NamedTemporaryFile('w') as f: with tempfile.NamedTemporaryFile('w') as f:
f.write('ignore: |\n' f.write('ignore: |\n'
' *.template.yaml\n') ' *.template.yaml\n')
@@ -378,6 +379,16 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(c.ignore.match_file('test.template.yaml'), True) self.assertEqual(c.ignore.match_file('test.template.yaml'), True)
self.assertEqual(c.ignore.match_file('test.yaml'), False) self.assertEqual(c.ignore.match_file('test.yaml'), False)
def test_extended_ignore_list(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('ignore:\n'
' - "*.template.yaml"\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n')
self.assertEqual(c.ignore.match_file('test.template.yaml'), True)
self.assertEqual(c.ignore.match_file('test.yaml'), False)
class ExtendedLibraryConfigTestCase(unittest.TestCase): class ExtendedLibraryConfigTestCase(unittest.TestCase):
def test_extend_config_disable_rule(self): def test_extend_config_disable_rule(self):
@@ -429,10 +440,10 @@ class ExtendedLibraryConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['empty-lines']['max-end'], 0) self.assertEqual(new.rules['empty-lines']['max-end'], 0)
class IgnorePathConfigTestCase(unittest.TestCase): class IgnoreConfigTestCase(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(IgnorePathConfigTestCase, cls).setUpClass() super().setUpClass()
bad_yaml = ('---\n' bad_yaml = ('---\n'
'- key: val1\n' '- key: val1\n'
@@ -452,22 +463,6 @@ class IgnorePathConfigTestCase(unittest.TestCase):
's/s/ign-trail/file.yaml': bad_yaml, 's/s/ign-trail/file.yaml': bad_yaml,
's/s/ign-trail/s/s/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, '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() cls.backup_wd = os.getcwd()
@@ -475,13 +470,101 @@ class IgnorePathConfigTestCase(unittest.TestCase):
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
super(IgnorePathConfigTestCase, cls).tearDownClass() super().tearDownClass()
os.chdir(cls.backup_wd) os.chdir(cls.backup_wd)
shutil.rmtree(cls.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_str(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() sys.stdout = StringIO()
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.')) cli.run(('-f', 'parsable', '.'))
@@ -519,3 +602,162 @@ 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:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen, './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
))) )))
def test_run_with_ignore_list(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', '.'))
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:4:17: ' + trailing,
'./ign-dup/file.yaml:5:5: ' + hyphen,
'./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: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:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
'./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_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

@@ -31,10 +31,10 @@ class LinterTestCase(unittest.TestCase):
linter.run(b'test: document', self.fake_config()) linter.run(b'test: document', self.fake_config())
def test_run_on_unicode(self): def test_run_on_unicode(self):
linter.run(u'test: document', self.fake_config()) linter.run('test: document', self.fake_config())
def test_run_on_stream(self): def test_run_on_stream(self):
linter.run(io.StringIO(u'hello'), self.fake_config()) linter.run(io.StringIO('hello'), self.fake_config())
def test_run_on_int(self): def test_run_on_int(self):
self.assertRaises(TypeError, linter.run, 42, self.fake_config()) self.assertRaises(TypeError, linter.run, 42, self.fake_config())
@@ -44,14 +44,14 @@ class LinterTestCase(unittest.TestCase):
['h', 'e', 'l', 'l', 'o'], self.fake_config()) ['h', 'e', 'l', 'l', 'o'], self.fake_config())
def test_run_on_non_ascii_chars(self): def test_run_on_non_ascii_chars(self):
s = (u'- hétérogénéité\n' s = ('- hétérogénéité\n'
u'# 19.99 €\n') '# 19.99 €\n')
linter.run(s, self.fake_config()) linter.run(s, self.fake_config())
linter.run(s.encode('utf-8'), self.fake_config()) linter.run(s.encode('utf-8'), self.fake_config())
linter.run(s.encode('iso-8859-15'), self.fake_config()) linter.run(s.encode('iso-8859-15'), self.fake_config())
s = (u'- お早う御座います。\n' s = ('- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n') '# الأَبْجَدِيَّة العَرَبِيَّة\n')
linter.run(s, self.fake_config()) linter.run(s, self.fake_config())
linter.run(s.encode('utf-8'), self.fake_config()) linter.run(s.encode('utf-8'), self.fake_config())

View File

@@ -13,7 +13,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from io import open
import os import os
from tests.common import RuleTestCase from tests.common import RuleTestCase

View File

@@ -21,10 +21,10 @@ indentation, etc."""
APP_NAME = 'yamllint' APP_NAME = 'yamllint'
APP_VERSION = '1.27.1' APP_VERSION = '1.29.0'
APP_DESCRIPTION = __doc__ APP_DESCRIPTION = __doc__
__author__ = u'Adrien Vergé' __author__ = 'Adrien Vergé'
__copyright__ = u'Copyright 2022, Adrien Vergé' __copyright__ = 'Copyright 2022, Adrien Vergé'
__license__ = 'GPLv3' __license__ = 'GPLv3'
__version__ = APP_VERSION __version__ = APP_VERSION

View File

@@ -13,10 +13,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
import argparse import argparse
import io
import locale import locale
import os import os
import platform import platform
@@ -49,7 +46,7 @@ def supports_color():
hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()) hasattr(sys.stdout, 'isatty') and sys.stdout.isatty())
class Format(object): class Format:
@staticmethod @staticmethod
def parsable(problem, filename): def parsable(problem, filename):
return ('%(file)s:%(line)s:%(column)s: [%(level)s] %(message)s' % return ('%(file)s:%(line)s:%(column)s: [%(level)s] %(message)s' %
@@ -160,6 +157,8 @@ def run(argv=None):
config_group.add_argument('-d', '--config-data', dest='config_data', config_group.add_argument('-d', '--config-data', dest='config_data',
action='store', action='store',
help='custom configuration (as YAML source)') help='custom configuration (as YAML source)')
parser.add_argument('--list-files', action='store_true', dest='list_files',
help='list files to lint and exit')
parser.add_argument('-f', '--format', parser.add_argument('-f', '--format',
choices=('parsable', 'standard', 'colored', 'github', choices=('parsable', 'standard', 'colored', 'github',
'auto'), 'auto'),
@@ -210,14 +209,20 @@ def run(argv=None):
if conf.locale is not None: if conf.locale is not None:
locale.setlocale(locale.LC_ALL, conf.locale) locale.setlocale(locale.LC_ALL, conf.locale)
if args.list_files:
for file in find_files_recursively(args.files, conf):
if not conf.is_file_ignored(file):
print(file)
sys.exit(0)
max_level = 0 max_level = 0
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 io.open(file, newline='') as f: with open(file, newline='') as f:
problems = linter.run(f, conf, filepath) problems = linter.run(f, conf, filepath)
except EnvironmentError as e: except OSError 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,
@@ -228,7 +233,7 @@ def run(argv=None):
if args.stdin: if args.stdin:
try: try:
problems = linter.run(sys.stdin, conf, '') problems = linter.run(sys.stdin, conf, '')
except EnvironmentError as e: except OSError 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,

View File

@@ -13,6 +13,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import fileinput
import os.path import os.path
import pathspec import pathspec
@@ -25,7 +26,7 @@ class YamlLintConfigError(Exception):
pass pass
class YamlLintConfig(object): class YamlLintConfig:
def __init__(self, content=None, file=None): def __init__(self, content=None, file=None):
assert (content is None) ^ (file is None) assert (content is None) ^ (file is None)
@@ -96,12 +97,31 @@ class YamlLintConfig(object):
except Exception as e: except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e) raise YamlLintConfigError('invalid config: %s' % e)
if 'ignore' in conf: if 'ignore' in conf and 'ignore-from-file' in conf:
if not isinstance(conf['ignore'], str): 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 isinstance(conf['ignore'], str):
self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
elif (isinstance(conf['ignore'], list) and
all(isinstance(line, str) for line in conf['ignore'])):
self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'])
else:
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: ignore should contain file patterns') 'invalid config: ignore should contain file patterns')
self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
if 'yaml-files' in conf: if 'yaml-files' in conf:
if not (isinstance(conf['yaml-files'], list) if not (isinstance(conf['yaml-files'], list)
@@ -135,11 +155,16 @@ def validate_rule_conf(rule, conf):
if isinstance(conf, dict): if isinstance(conf, dict):
if ('ignore' in conf and if ('ignore' in conf and
not isinstance(conf['ignore'], pathspec.pathspec.PathSpec)): not isinstance(conf['ignore'], pathspec.pathspec.PathSpec)):
if not isinstance(conf['ignore'], str): if isinstance(conf['ignore'], str):
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
elif (isinstance(conf['ignore'], list) and
all(isinstance(line, str) for line in conf['ignore'])):
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'])
else:
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: ignore should contain file patterns') 'invalid config: ignore should contain file patterns')
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
if 'level' not in conf: if 'level' not in conf:
conf['level'] = 'error' conf['level'] = 'error'
@@ -150,7 +175,7 @@ def validate_rule_conf(rule, conf):
options = getattr(rule, 'CONF', {}) options = getattr(rule, 'CONF', {})
options_default = getattr(rule, 'DEFAULT', {}) options_default = getattr(rule, 'DEFAULT', {})
for optkey in conf: for optkey in conf:
if optkey in ('ignore', 'level'): if optkey in ('ignore', 'ignore-from-file', 'level'):
continue continue
if optkey not in options: if optkey not in options:
raise YamlLintConfigError( raise YamlLintConfigError(

View File

@@ -14,6 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import re import re
import io
import yaml import yaml
@@ -29,8 +30,11 @@ PROBLEM_LEVELS = {
'error': 2, '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):
class LintProblem:
"""Represents a linting problem found by yamllint.""" """Represents a linting problem found by yamllint."""
def __init__(self, line, column, desc='<no description>', rule=None): def __init__(self, line, column, desc='<no description>', rule=None):
#: Line on which the problem was found (starting at 1) #: Line on which the problem was found (starting at 1)
@@ -82,7 +86,7 @@ def get_cosmetic_problems(buffer, conf, filepath):
def process_comment(self, comment): def process_comment(self, comment):
comment = str(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(' ') items = comment[18:].rstrip().split(' ')
rules = [item[5:] for item in items][1:] rules = [item[5:] for item in items][1:]
if len(rules) == 0: if len(rules) == 0:
@@ -92,7 +96,7 @@ def get_cosmetic_problems(buffer, conf, filepath):
if id in self.all_rules: if id in self.all_rules:
self.rules.add(id) 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(' ') items = comment[17:].rstrip().split(' ')
rules = [item[5:] for item in items][1:] rules = [item[5:] for item in items][1:]
if len(rules) == 0: if len(rules) == 0:
@@ -118,7 +122,7 @@ def get_cosmetic_problems(buffer, conf, filepath):
if id in self.all_rules: if id in self.all_rules:
self.rules.add(id) self.rules.add(id)
# Use a cache to store problems and flush it only when a end of line is # Use a cache to store problems and flush it only when an end of line is
# found. This allows the use of yamllint directive to disable some rules on # found. This allows the use of yamllint directive to disable some rules on
# some lines. # some lines.
cache = [] cache = []
@@ -224,7 +228,7 @@ def run(input, conf, filepath=None):
if isinstance(input, (bytes, str)): if isinstance(input, (bytes, str)):
return _run(input, conf, filepath) return _run(input, conf, filepath)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase elif isinstance(input, io.IOBase):
# We need to have everything in memory to parse correctly # We need to have everything in memory to parse correctly
content = input.read() content = input.read()
return _run(content, conf, filepath) return _run(content, conf, filepath)

View File

@@ -16,7 +16,7 @@
import yaml import yaml
class Line(object): class Line:
def __init__(self, line_no, buffer, start, end): def __init__(self, line_no, buffer, start, end):
self.line_no = line_no self.line_no = line_no
self.start = start self.start = start
@@ -28,7 +28,7 @@ class Line(object):
return self.buffer[self.start:self.end] return self.buffer[self.start:self.end]
class Token(object): class Token:
def __init__(self, line_no, curr, prev, next, nextnext): def __init__(self, line_no, curr, prev, next, nextnext):
self.line_no = line_no self.line_no = line_no
self.curr = curr self.curr = curr
@@ -37,7 +37,7 @@ class Token(object):
self.nextnext = nextnext self.nextnext = nextnext
class Comment(object): class Comment:
def __init__(self, line_no, column_no, buffer, pointer, def __init__(self, line_no, column_no, buffer, pointer,
token_before=None, token_after=None, comment_before=None): token_before=None, token_after=None, comment_before=None):
self.line_no = line_no self.line_no = line_no
@@ -132,8 +132,7 @@ def token_or_comment_generator(buffer):
yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext) yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext)
for comment in comments_between_tokens(curr, next): yield from comments_between_tokens(curr, next)
yield comment
prev = curr prev = curr
curr = next curr = next

View File

@@ -117,8 +117,7 @@ def check(conf, comment):
# # comment # # comment
# - 1 # - 1
# - 2 # - 2
if prev_line_indent <= next_line_indent: prev_line_indent = max(prev_line_indent, next_line_indent)
prev_line_indent = next_line_indent
# If two indents are valid but a previous comment went back to normal # If two indents are valid but a previous comment went back to normal
# indent, for the next ones to do the same. In other words, avoid this: # indent, for the next ones to do the same. In other words, avoid this:

View File

@@ -107,13 +107,13 @@ DEFAULT = {
} }
IS_NUMERAL_BEFORE_DECIMAL_PATTERN = ( IS_NUMERAL_BEFORE_DECIMAL_PATTERN = (
re.compile('[-+]?(\\.[0-9]+)([eE][-+]?[0-9]+)?') re.compile('[-+]?(\\.[0-9]+)([eE][-+]?[0-9]+)?$')
) )
IS_SCIENTIFIC_NOTATION_PATTERN = re.compile( IS_SCIENTIFIC_NOTATION_PATTERN = re.compile(
'[-+]?(\\.[0-9]+|[0-9]+(\\.[0-9]*)?)([eE][-+]?[0-9]+)' '[-+]?(\\.[0-9]+|[0-9]+(\\.[0-9]*)?)([eE][-+]?[0-9]+)$'
) )
IS_INF_PATTERN = re.compile('[-+]?(\\.inf|\\.Inf|\\.INF)') IS_INF_PATTERN = re.compile('[-+]?(\\.inf|\\.Inf|\\.INF)$')
IS_NAN_PATTERN = re.compile('\\.nan|\\.NaN|\\.NAN') IS_NAN_PATTERN = re.compile('(\\.nan|\\.NaN|\\.NAN)$')
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):

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') 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): def __init__(self, type, indent, line_indent=None):
self.type = type self.type = type
self.indent = indent self.indent = indent
@@ -341,14 +341,18 @@ def _check(conf, token, prev, next, nextnext, context):
expected = detect_indent(expected, token) expected = detect_indent(expected, token)
if found_indentation != expected: if found_indentation != expected:
yield LintProblem(token.start_mark.line + 1, found_indentation + 1, if expected < 0:
'wrong indentation: expected %d but found %d' % message = 'wrong indentation: expected at least %d' % \
(expected, found_indentation)) (found_indentation + 1)
else:
message = 'wrong indentation: expected %d but found %d' % \
(expected, found_indentation)
yield LintProblem(token.start_mark.line + 1,
found_indentation + 1, message)
if (isinstance(token, yaml.ScalarToken) and if (isinstance(token, yaml.ScalarToken) and
conf['check-multi-line-strings']): conf['check-multi-line-strings']):
for problem in check_scalar_indentation(conf, token, context): yield from check_scalar_indentation(conf, token, context)
yield problem
# Step 2.a: # Step 2.a:
@@ -493,8 +497,8 @@ def _check(conf, token, prev, next, nextnext, context):
# indentation it should have (because `spaces` is # indentation it should have (because `spaces` is
# `consistent` and its value has not been computed yet # `consistent` and its value has not been computed yet
# -- this is probably the beginning of the document). # -- this is probably the beginning of the document).
# So we choose an arbitrary value (2). # So we choose an unknown value (-1).
indent = 2 indent = -1
else: else:
indent = detect_indent(context['stack'][-1].indent, indent = detect_indent(context['stack'][-1].indent,
next) next)
@@ -576,8 +580,7 @@ def _check(conf, token, prev, next, nextnext, context):
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):
try: try:
for problem in _check(conf, token, prev, next, nextnext, context): yield from _check(conf, token, prev, next, nextnext, context)
yield problem
except AssertionError: except AssertionError:
yield LintProblem(token.start_mark.line + 1, yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1, token.start_mark.column + 1,

View File

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

View File

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

View File

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

View File

@@ -30,6 +30,8 @@ used.
``required: false`` and ``required: only-when-needed``. ``required: false`` and ``required: only-when-needed``.
* ``extra-allowed`` is a list of PCRE regexes to allow quoted string values, * ``extra-allowed`` is a list of PCRE regexes to allow quoted string values,
even if ``required: only-when-needed`` is set. 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. **Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
@@ -43,6 +45,7 @@ used.
required: true required: true
extra-required: [] extra-required: []
extra-allowed: [] extra-allowed: []
allow-quoted-quotes: false
.. rubric:: Examples .. rubric:: Examples
@@ -112,6 +115,26 @@ used.
- "localhost" - "localhost"
- this is a string that needs to be QUOTED - 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 import re
@@ -125,11 +148,13 @@ TYPE = 'token'
CONF = {'quote-type': ('any', 'single', 'double'), CONF = {'quote-type': ('any', 'single', 'double'),
'required': (True, False, 'only-when-needed'), 'required': (True, False, 'only-when-needed'),
'extra-required': [str], 'extra-required': [str],
'extra-allowed': [str]} 'extra-allowed': [str],
'allow-quoted-quotes': bool}
DEFAULT = {'quote-type': 'any', DEFAULT = {'quote-type': 'any',
'required': True, 'required': True,
'extra-required': [], 'extra-required': [],
'extra-allowed': []} 'extra-allowed': [],
'allow-quoted-quotes': False}
def VALIDATE(conf): def VALIDATE(conf):
@@ -141,7 +166,7 @@ def VALIDATE(conf):
return 'cannot use both "required: false" and "extra-allowed"' return 'cannot use both "required: false" and "extra-allowed"'
DEFAULT_SCALAR_TAG = u'tag:yaml.org,2002:str' DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str'
# https://stackoverflow.com/a/36514274 # https://stackoverflow.com/a/36514274
yaml.resolver.Resolver.add_implicit_resolver( yaml.resolver.Resolver.add_implicit_resolver(
@@ -177,6 +202,12 @@ def _quotes_are_needed(string):
return True 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): def check(conf, token, prev, next, nextnext, context):
if not (isinstance(token, yaml.tokens.ScalarToken) and if not (isinstance(token, yaml.tokens.ScalarToken) and
isinstance(prev, (yaml.BlockEntryToken, yaml.FlowEntryToken, isinstance(prev, (yaml.BlockEntryToken, yaml.FlowEntryToken,
@@ -197,7 +228,7 @@ def check(conf, token, prev, next, nextnext, context):
return return
# Ignore multi-line strings # Ignore multi-line strings
if (not token.plain) and (token.style == "|" or token.style == ">"): if not token.plain and token.style in ("|", ">"):
return return
quote_type = conf['quote-type'] quote_type = conf['quote-type']
@@ -206,13 +237,18 @@ def check(conf, token, prev, next, nextnext, context):
if conf['required'] is True: if conf['required'] is True:
# Quotes are mandatory and need to match config # 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 msg = "string value is not quoted with %s quotes" % quote_type
elif conf['required'] is False: elif conf['required'] is False:
# Quotes are not mandatory but when used need to match config # 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 msg = "string value is not quoted with %s quotes" % quote_type
elif not token.style: elif not token.style:
@@ -235,7 +271,9 @@ def check(conf, token, prev, next, nextnext, context):
quote_type) quote_type)
# But when used need to match config # 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 msg = "string value is not quoted with %s quotes" % quote_type
elif not token.style: elif not token.style:

View File

@@ -14,7 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
Use this rule to forbid non-explictly typed truthy values other than allowed Use this rule to forbid non-explicitly typed truthy values other than allowed
ones (by default: ``true`` and ``false``), for example ``YES`` or ``off``. ones (by default: ``true`` and ``false``), for example ``YES`` or ``off``.
This can be useful to prevent surprises from YAML parsers transforming This can be useful to prevent surprises from YAML parsers transforming