diff --git a/.travis.yml b/.travis.yml index 0ff3297..bd365c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,20 @@ --- language: python python: + - 2.6 - 2.7 - 3.3 - 3.4 - 3.5 + - 3.6 - nightly install: - - pip install pyyaml flake8 coveralls + - pip install pyyaml flake8 flake8-import-order coveralls + - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi - pip install . script: - - flake8 . - - yamllint $(git ls-files '*.yaml' '*.yml') + - if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then flake8 .; fi + - yamllint --strict $(git ls-files '*.yaml' '*.yml') - coverage run --source=yamllint setup.py test after_success: coveralls diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..0b2f9ff --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,15 @@ +Changelog +========= + +1.8.1 (2017-07-04) +------------------ + +- Require pathspec >= 0.5.3 +- Support Python 2.6 +- Add a changelog + +1.8.0 (2017-06-28) +------------------ + +- Refactor argparse with mutually_exclusive_group +- Add support to ignore paths in configuration diff --git a/README.rst b/README.rst index 1fb9541..1f5dac1 100644 --- a/README.rst +++ b/README.rst @@ -44,7 +44,7 @@ On Fedora / CentOS: sudo dnf install yamllint -On Debian 9+ / Ubuntu 16.04+: +On Debian 8+ / Ubuntu 16.04+: .. code:: bash @@ -119,6 +119,27 @@ or for a whole block: consectetur : adipiscing elit # yamllint enable +Specific files can be ignored (totally or for some rules only) using a +``.gitignore``-style pattern: + +.. code:: yaml + + # For all rules + ignore: | + *.dont-lint-me.yaml + /bin/ + !/bin/*.lint-me-anyway.yaml + + rules: + key-duplicates: + ignore: | + generated + *.template.yaml + trailing-spaces: + ignore: | + *.ignore-trailing-spaces.yaml + /ascii-art/* + `Read more in the complete documentation! `_ License diff --git a/docs/configuration.rst b/docs/configuration.rst index 661e506..0815789 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -102,8 +102,69 @@ Errors and warnings ------------------- Problems detected by yamllint can be raised either as errors or as warnings. +The CLI will output them (with different colors when using the ``standard`` +output format). -In both cases, the script will output them (with different colors when using the -``standard`` output format), but the exit code can be different. More precisely, -the script will exit will a failure code *only when* there is one or more -error(s). +By default the script will exit with a return code ``1`` *only when* there is one or +more error(s). + +However if strict mode is enabled with the ``-s`` (or ``--strict``) option, the +return code will be: + + * ``0`` if no errors or warnings occur + * ``1`` if one or more errors occur + * ``2`` if no errors occur, but one or more warnings occur + +Ignoring paths +-------------- + +It is possible to exclude specific files or directories, so that the linter +doesn't process them. + +You can either totally ignore files (they won't be looked at): + +.. code-block:: yaml + + extends: default + + ignore: | + /this/specific/file.yaml + /all/this/directory/ + *.template.yaml + +or ignore paths only for specific rules: + +.. code-block:: yaml + + extends: default + + 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 +exclusion/inclusion, see the `pathspec README file +`_ for more details. +Here is a more complex example: + +.. code-block:: yaml + + # For all rules + ignore: | + *.dont-lint-me.yaml + /bin/ + !/bin/*.lint-me-anyway.yaml + + extends: default + + rules: + key-duplicates: + ignore: | + generated + *.template.yaml + trailing-spaces: + ignore: | + *.ignore-trailing-spaces.yaml + /ascii-art/* diff --git a/docs/disable_with_comments.rst b/docs/disable_with_comments.rst index 10477fb..34d6987 100644 --- a/docs/disable_with_comments.rst +++ b/docs/disable_with_comments.rst @@ -32,7 +32,7 @@ or: - This line is waaaaaaaaaay too long but yamllint will not report anything about it. This line will be checked by yamllint. -It it possible, although not recommend, to disabled **all** rules for a +It is possible, although not recommend, to disabled **all** rules for a specific line: .. code-block:: yaml @@ -46,7 +46,7 @@ If you need to disable multiple rules, it is allowed to chain rules like this: Disabling checks for all (or part of) the file ---------------------------------------------- -To prevent yamllint from reporting problems for the whoe file, or for a block of +To prevent yamllint from reporting problems for the whole file, or for a block of lines within the file, use ``# yamllint disable ...`` and ``# yamllint enable ...`` directive comments. For instance: @@ -60,7 +60,7 @@ lines within the file, use ``# yamllint disable ...`` and ``# yamllint enable - rest of the document... -It it possible, although not recommend, to disabled **all** rules: +It is possible, although not recommend, to disabled **all** rules: .. code-block:: yaml diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 9da607a..3365877 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -10,7 +10,7 @@ On Fedora / CentOS: sudo dnf install yamllint -On Debian 9+ / Ubuntu 16.04+: +On Debian 8+ / Ubuntu 16.04+: .. code:: bash diff --git a/docs/text_editors.rst b/docs/text_editors.rst index 8909aa9..da3e96f 100644 --- a/docs/text_editors.rst +++ b/docs/text_editors.rst @@ -9,8 +9,12 @@ text editor. Vim --- -Assuming that the `syntastic `_ plugin -is installed, add to your ``.vimrc``: +Assuming that the `ALE `_ plugin is +installed, yamllint is supported by default. It is automatically enabled when +editing YAML files. + +If you instead use the `syntastic `_ +plugin, add this to your ``.vimrc``: :: @@ -23,6 +27,12 @@ Assuming that the `neomake `_ plugin is installed, yamllint is supported by default. It is automatically enabled when editing YAML files. +Emacs +----- + +If you are `flycheck `_ user, you can use +`flycheck-yamllint `_ integration. + Other text editors ------------------ diff --git a/hooks.yaml b/hooks.yaml new file mode 100644 index 0000000..482f3fb --- /dev/null +++ b/hooks.yaml @@ -0,0 +1,11 @@ +--- + +# For use with pre-commit. +# See usage instructions at http://pre-commit.com + +- id: yamllint + name: yamllint + description: This hook runs yamllint. + entry: yamllint + language: python + files: \.(yaml|yml)$ diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..4d5142d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[bdist_wheel] +universal = 1 + +[flake8] +import-order-style = pep8 diff --git a/setup.py b/setup.py index 8a865f4..e87980f 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,6 @@ setup( entry_points={'console_scripts': ['yamllint=yamllint.cli:run']}, package_data={'yamllint': ['conf/*.yaml'], 'tests': ['yaml-1.2-spec-examples/*']}, - install_requires=['pyyaml'], - tests_require=['nose'], - test_suite='nose.collector', + install_requires=['pathspec >=0.5.3', 'pyyaml'], + test_suite='tests', ) diff --git a/tests/common.py b/tests/common.py index ed626d0..11dd235 100644 --- a/tests/common.py +++ b/tests/common.py @@ -14,7 +14,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import unittest +import os +import tempfile +import sys +try: + assert sys.version_info >= (2, 7) + import unittest +except: + import unittest2 as unittest import yaml @@ -49,3 +56,21 @@ class RuleTestCase(unittest.TestCase): real_problems = list(linter.run(source, self.build_fake_config(conf))) self.assertEqual(real_problems, expected_problems) + + +def build_temp_workspace(files): + tempdir = tempfile.mkdtemp(prefix='yamllint-tests-') + + for path, content in files.items(): + path = os.path.join(tempdir, path) + if not os.path.exists(os.path.dirname(path)): + os.makedirs(os.path.dirname(path)) + + if type(content) is list: + os.mkdir(path) + else: + mode = 'wb' if isinstance(content, bytes) else 'w' + with open(path, mode) as f: + f.write(content) + + return tempdir diff --git a/tests/rules/test_braces.py b/tests/rules/test_braces.py index 0262ae0..308715b 100644 --- a/tests/rules/test_braces.py +++ b/tests/rules/test_braces.py @@ -32,11 +32,19 @@ class ColonTestCase(RuleTestCase): 'dict7: { a: 1, b, c: 3 }\n', conf) def test_min_spaces(self): - conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 0}' + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: {}\n', conf) - conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 1}' + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: {}\n', conf, problem=(2, 8)) self.check('---\n' @@ -52,7 +60,11 @@ class ColonTestCase(RuleTestCase): ' b\n' '}\n', conf) - conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 3}' + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 3\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: { a: 1, b }\n', conf, problem1=(2, 9), problem2=(2, 17)) @@ -60,7 +72,11 @@ class ColonTestCase(RuleTestCase): 'dict: { a: 1, b }\n', conf) def test_max_spaces(self): - conf = 'braces: {max-spaces-inside: 0, min-spaces-inside: -1}' + conf = ('braces:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: {}\n', conf) self.check('---\n' @@ -79,7 +95,11 @@ class ColonTestCase(RuleTestCase): ' b\n' '}\n', conf) - conf = 'braces: {max-spaces-inside: 3, min-spaces-inside: -1}' + conf = ('braces:\n' + ' max-spaces-inside: 3\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: { a: 1, b }\n', conf) self.check('---\n' @@ -87,7 +107,11 @@ class ColonTestCase(RuleTestCase): problem1=(2, 11), problem2=(2, 23)) def test_min_and_max_spaces(self): - conf = 'braces: {max-spaces-inside: 0, min-spaces-inside: 0}' + conf = ('braces:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: {}\n', conf) self.check('---\n' @@ -95,14 +119,169 @@ class ColonTestCase(RuleTestCase): self.check('---\n' 'dict: { a: 1, b}\n', conf, problem=(2, 10)) - conf = 'braces: {max-spaces-inside: 1, min-spaces-inside: 1}' + conf = ('braces:\n' + ' max-spaces-inside: 1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: {a: 1, b, c: 3 }\n', conf, problem=(2, 8)) - conf = 'braces: {max-spaces-inside: 2, min-spaces-inside: 0}' + conf = ('braces:\n' + ' max-spaces-inside: 2\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: {a: 1, b, c: 3 }\n', conf) self.check('---\n' 'dict: { a: 1, b, c: 3 }\n', conf) self.check('---\n' 'dict: { a: 1, b, c: 3 }\n', conf, problem=(2, 10)) + + def test_min_spaces_empty(self): + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: 0\n') + self.check('---\n' + 'array: {}\n', conf) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: {}\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: 3\n') + self.check('---\n' + 'array: {}\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + + def test_max_spaces_empty(self): + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: {}\n', conf) + self.check('---\n' + 'array: { }\n', conf, problem=(2, 9)) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: {}\n', conf) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf, problem=(2, 10)) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 3\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: {}\n', conf) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf, problem=(2, 12)) + + def test_min_and_max_spaces_empty(self): + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 2\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: {}\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf, problem=(2, 11)) + + def test_mixed_empty_nonempty(self): + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: 0\n') + self.check('---\n' + 'array: { a: 1, b }\n', conf) + self.check('---\n' + 'array: {a: 1, b}\n', conf, + problem1=(2, 9), problem2=(2, 16)) + self.check('---\n' + 'array: {}\n', conf) + self.check('---\n' + 'array: { }\n', conf, + problem1=(2, 9)) + + conf = ('braces:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: { a: 1, b }\n', conf, + problem1=(2, 9), problem2=(2, 17)) + self.check('---\n' + 'array: {a: 1, b}\n', conf) + self.check('---\n' + 'array: {}\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + + conf = ('braces:\n' + ' max-spaces-inside: 2\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: { a: 1, b }\n', conf) + self.check('---\n' + 'array: {a: 1, b }\n', conf, + problem1=(2, 9), problem2=(2, 18)) + self.check('---\n' + 'array: {}\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf, + problem1=(2, 11)) + + conf = ('braces:\n' + ' max-spaces-inside: 1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: { a: 1, b }\n', conf) + self.check('---\n' + 'array: {a: 1, b}\n', conf, + problem1=(2, 9), problem2=(2, 16)) + self.check('---\n' + 'array: {}\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) diff --git a/tests/rules/test_brackets.py b/tests/rules/test_brackets.py index 8fb8006..273f1a7 100644 --- a/tests/rules/test_brackets.py +++ b/tests/rules/test_brackets.py @@ -32,11 +32,19 @@ class ColonTestCase(RuleTestCase): 'array7: [ a, b, c ]\n', conf) def test_min_spaces(self): - conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 0}' + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: []\n', conf) - conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 1}' + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: []\n', conf, problem=(2, 9)) self.check('---\n' @@ -51,7 +59,11 @@ class ColonTestCase(RuleTestCase): ' b\n' ']\n', conf) - conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 3}' + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 3\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: [ a, b ]\n', conf, problem1=(2, 10), problem2=(2, 15)) @@ -59,7 +71,11 @@ class ColonTestCase(RuleTestCase): 'array: [ a, b ]\n', conf) def test_max_spaces(self): - conf = 'brackets: {max-spaces-inside: 0, min-spaces-inside: -1}' + conf = ('brackets:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: []\n', conf) self.check('---\n' @@ -78,7 +94,11 @@ class ColonTestCase(RuleTestCase): ' b\n' ']\n', conf) - conf = 'brackets: {max-spaces-inside: 3, min-spaces-inside: -1}' + conf = ('brackets:\n' + ' max-spaces-inside: 3\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: [ a, b ]\n', conf) self.check('---\n' @@ -86,7 +106,11 @@ class ColonTestCase(RuleTestCase): problem1=(2, 12), problem2=(2, 21)) def test_min_and_max_spaces(self): - conf = 'brackets: {max-spaces-inside: 0, min-spaces-inside: 0}' + conf = ('brackets:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: []\n', conf) self.check('---\n' @@ -94,14 +118,169 @@ class ColonTestCase(RuleTestCase): self.check('---\n' 'array: [ a, b]\n', conf, problem=(2, 11)) - conf = 'brackets: {max-spaces-inside: 1, min-spaces-inside: 1}' + conf = ('brackets:\n' + ' max-spaces-inside: 1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: [a, b, c ]\n', conf, problem=(2, 9)) - conf = 'brackets: {max-spaces-inside: 2, min-spaces-inside: 0}' + conf = ('brackets:\n' + ' max-spaces-inside: 2\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: [a, b, c ]\n', conf) self.check('---\n' 'array: [ a, b, c ]\n', conf) self.check('---\n' 'array: [ a, b, c ]\n', conf, problem=(2, 11)) + + def test_min_spaces_empty(self): + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: 0\n') + self.check('---\n' + 'array: []\n', conf) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: []\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: 3\n') + self.check('---\n' + 'array: []\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + + def test_max_spaces_empty(self): + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 9)) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 10)) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 3\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 12)) + + def test_min_and_max_spaces_empty(self): + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 2\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: []\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 11)) + + def test_mixed_empty_nonempty(self): + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: 0\n') + self.check('---\n' + 'array: [ a, b ]\n', conf) + self.check('---\n' + 'array: [a, b]\n', conf, + problem1=(2, 9), problem2=(2, 13)) + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, + problem1=(2, 9)) + + conf = ('brackets:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: [ a, b ]\n', conf, + problem1=(2, 9), problem2=(2, 14)) + self.check('---\n' + 'array: [a, b]\n', conf) + self.check('---\n' + 'array: []\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + + conf = ('brackets:\n' + ' max-spaces-inside: 2\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: [ a, b ]\n', conf) + self.check('---\n' + 'array: [a, b ]\n', conf, + problem1=(2, 9), problem2=(2, 15)) + self.check('---\n' + 'array: []\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, + problem1=(2, 11)) + + conf = ('brackets:\n' + ' max-spaces-inside: 1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: [ a, b ]\n', conf) + self.check('---\n' + 'array: [a, b]\n', conf, + problem1=(2, 9), problem2=(2, 13)) + self.check('---\n' + 'array: []\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) diff --git a/tests/rules/test_comments.py b/tests/rules/test_comments.py index ee8a62c..ded31fa 100644 --- a/tests/rules/test_comments.py +++ b/tests/rules/test_comments.py @@ -43,7 +43,7 @@ class CommentsTestCase(RuleTestCase): def test_starting_space(self): conf = ('comments:\n' - ' require-starting-space: yes\n' + ' require-starting-space: true\n' ' min-spaces-from-content: -1\n' 'comments-indentation: disable\n') self.check('---\n' @@ -82,7 +82,7 @@ class CommentsTestCase(RuleTestCase): def test_spaces_from_content(self): conf = ('comments:\n' - ' require-starting-space: no\n' + ' require-starting-space: false\n' ' min-spaces-from-content: 2\n') self.check('---\n' '# comment\n' @@ -104,7 +104,7 @@ class CommentsTestCase(RuleTestCase): def test_both(self): conf = ('comments:\n' - ' require-starting-space: yes\n' + ' require-starting-space: true\n' ' min-spaces-from-content: 2\n' 'comments-indentation: disable\n') self.check('---\n' @@ -134,7 +134,7 @@ class CommentsTestCase(RuleTestCase): def test_empty_comment(self): conf = ('comments:\n' - ' require-starting-space: yes\n' + ' require-starting-space: true\n' ' min-spaces-from-content: 2\n') self.check('---\n' '# This is paragraph 1.\n' @@ -146,13 +146,13 @@ class CommentsTestCase(RuleTestCase): def test_first_line(self): conf = ('comments:\n' - ' require-starting-space: yes\n' + ' require-starting-space: true\n' ' min-spaces-from-content: 2\n') self.check('# comment\n', conf) def test_last_line(self): conf = ('comments:\n' - ' require-starting-space: yes\n' + ' require-starting-space: true\n' ' min-spaces-from-content: 2\n' 'new-line-at-end-of-file: disable\n') self.check('# comment with no newline char:\n' @@ -160,7 +160,7 @@ class CommentsTestCase(RuleTestCase): def test_multi_line_scalar(self): conf = ('comments:\n' - ' require-starting-space: yes\n' + ' require-starting-space: true\n' ' min-spaces-from-content: 2\n' 'trailing-spaces: disable\n') self.check('---\n' diff --git a/tests/rules/test_document_end.py b/tests/rules/test_document_end.py index 82d5772..d62a7e6 100644 --- a/tests/rules/test_document_end.py +++ b/tests/rules/test_document_end.py @@ -31,7 +31,7 @@ class DocumentEndTestCase(RuleTestCase): ' document: end\n', conf) def test_required(self): - conf = 'document-end: {present: yes}' + conf = 'document-end: {present: true}' self.check('', conf) self.check('\n', conf) self.check('---\n' @@ -43,7 +43,7 @@ class DocumentEndTestCase(RuleTestCase): ' document: end\n', conf, problem=(3, 1)) def test_forbidden(self): - conf = 'document-end: {present: no}' + conf = 'document-end: {present: false}' self.check('---\n' 'with:\n' ' document: end\n' @@ -53,7 +53,7 @@ class DocumentEndTestCase(RuleTestCase): ' document: end\n', conf) def test_multiple_documents(self): - conf = ('document-end: {present: yes}\n' + conf = ('document-end: {present: true}\n' 'document-start: disable\n') self.check('---\n' 'first: document\n' diff --git a/tests/rules/test_document_start.py b/tests/rules/test_document_start.py index 15fe2b7..20de9ad 100644 --- a/tests/rules/test_document_start.py +++ b/tests/rules/test_document_start.py @@ -28,7 +28,7 @@ class DocumentStartTestCase(RuleTestCase): 'key: val\n', conf) def test_required(self): - conf = ('document-start: {present: yes}\n' + conf = ('document-start: {present: true}\n' 'empty-lines: disable\n') self.check('', conf) self.check('\n', conf) @@ -44,7 +44,7 @@ class DocumentStartTestCase(RuleTestCase): 'key: val\n', conf) def test_forbidden(self): - conf = ('document-start: {present: no}\n' + conf = ('document-start: {present: false}\n' 'empty-lines: disable\n') self.check('', conf) self.check('key: val\n', conf) @@ -62,7 +62,7 @@ class DocumentStartTestCase(RuleTestCase): 'key: val\n', conf, problem=(2, 1)) def test_multiple_documents(self): - conf = 'document-start: {present: yes}' + conf = 'document-start: {present: true}' self.check('---\n' 'first: document\n' '...\n' @@ -85,7 +85,7 @@ class DocumentStartTestCase(RuleTestCase): 'third: document\n', conf, problem=(4, 1, 'syntax')) def test_directives(self): - conf = 'document-start: {present: yes}' + conf = 'document-start: {present: true}' self.check('%YAML 1.2\n' '---\n' 'doc: ument\n' diff --git a/tests/rules/test_indentation.py b/tests/rules/test_indentation.py index cbbc979..2733ea7 100644 --- a/tests/rules/test_indentation.py +++ b/tests/rules/test_indentation.py @@ -549,7 +549,7 @@ class IndentationTestCase(RuleTestCase): '...\n', conf) def test_one_space(self): - conf = 'indentation: {spaces: 1, indent-sequences: no}' + conf = 'indentation: {spaces: 1, indent-sequences: false}' self.check('---\n' 'object:\n' ' k1:\n' @@ -562,7 +562,7 @@ class IndentationTestCase(RuleTestCase): ' - name: Linux\n' ' date: 1991\n' '...\n', conf) - conf = 'indentation: {spaces: 1, indent-sequences: yes}' + conf = 'indentation: {spaces: 1, indent-sequences: true}' self.check('---\n' 'object:\n' ' k1:\n' @@ -577,7 +577,7 @@ class IndentationTestCase(RuleTestCase): '...\n', conf) def test_two_spaces(self): - conf = 'indentation: {spaces: 2, indent-sequences: no}' + conf = 'indentation: {spaces: 2, indent-sequences: false}' self.check('---\n' 'object:\n' ' k1:\n' @@ -590,7 +590,7 @@ class IndentationTestCase(RuleTestCase): ' - name: Linux\n' ' date: 1991\n' '...\n', conf) - conf = 'indentation: {spaces: 2, indent-sequences: yes}' + conf = 'indentation: {spaces: 2, indent-sequences: true}' self.check('---\n' 'object:\n' ' k1:\n' @@ -605,7 +605,7 @@ class IndentationTestCase(RuleTestCase): '...\n', conf) def test_three_spaces(self): - conf = 'indentation: {spaces: 3, indent-sequences: no}' + conf = 'indentation: {spaces: 3, indent-sequences: false}' self.check('---\n' 'object:\n' ' k1:\n' @@ -618,7 +618,7 @@ class IndentationTestCase(RuleTestCase): ' - name: Linux\n' ' date: 1991\n' '...\n', conf) - conf = 'indentation: {spaces: 3, indent-sequences: yes}' + conf = 'indentation: {spaces: 3, indent-sequences: true}' self.check('---\n' 'object:\n' ' k1:\n' @@ -632,7 +632,7 @@ class IndentationTestCase(RuleTestCase): ' date: 1991\n' '...\n', conf) - def test_consistent(self): + def test_consistent_spaces(self): conf = ('indentation: {spaces: consistent,\n' ' indent-sequences: whatever}\n' 'document-start: disable\n') @@ -713,6 +713,142 @@ class IndentationTestCase(RuleTestCase): '- b\n' '- c\n', conf) + def test_consistent_spaces_and_indent_sequences(self): + conf = 'indentation: {spaces: consistent, indent-sequences: true}' + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(3, 1)) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(7, 5)) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf, problem1=(7, 1)) + + conf = 'indentation: {spaces: consistent, indent-sequences: false}' + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(7, 5)) + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(7, 3)) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf, problem1=(3, 3)) + + conf = ('indentation: {spaces: consistent,\n' + ' indent-sequences: consistent}') + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(7, 5)) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf, problem1=(7, 1)) + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(7, 5)) + + conf = 'indentation: {spaces: consistent, indent-sequences: whatever}' + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf) + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(7, 5)) + def test_indent_sequences_whatever(self): conf = 'indentation: {spaces: 4, indent-sequences: whatever}' self.check('---\n' @@ -1130,7 +1266,7 @@ class IndentationTestCase(RuleTestCase): problem=(2, 3)) def test_multi_lines(self): - conf = 'indentation: {spaces: consistent, indent-sequences: yes}' + conf = 'indentation: {spaces: consistent, indent-sequences: true}' self.check('---\n' 'long_string: >\n' ' bla bla blah\n' @@ -1438,7 +1574,7 @@ class IndentationTestCase(RuleTestCase): '- !!map # Block collection\n' ' foo: bar\n', conf) - conf = 'indentation: {spaces: consistent, indent-sequences: no}' + conf = 'indentation: {spaces: consistent, indent-sequences: false}' self.check('---\n' 'sequence: !!seq\n' '- entry\n' @@ -1505,7 +1641,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_basics_plain(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: no}\n' + ' check-multi-line-strings: false}\n' 'document-start: disable\n') self.check('multi\n' 'line\n', conf) @@ -1534,7 +1670,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_check_multi_line_plain(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: yes}\n' + ' check-multi-line-strings: true}\n' 'document-start: disable\n') self.check('multi\n' ' line\n', conf, problem=(2, 2)) @@ -1557,7 +1693,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_basics_quoted(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: no}\n' + ' check-multi-line-strings: false}\n' 'document-start: disable\n') self.check('"multi\n' ' line"\n', conf) @@ -1588,7 +1724,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_check_multi_line_quoted(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: yes}\n' + ' check-multi-line-strings: true}\n' 'document-start: disable\n') self.check('"multi\n' 'line"\n', conf, problem=(2, 1)) @@ -1644,7 +1780,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_basics_folded_style(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: no}\n' + ' check-multi-line-strings: false}\n' 'document-start: disable\n') self.check('>\n' ' multi\n' @@ -1682,7 +1818,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_check_multi_line_folded_style(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: yes}\n' + ' check-multi-line-strings: true}\n' 'document-start: disable\n') self.check('>\n' ' multi\n' @@ -1723,7 +1859,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_basics_literal_style(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: no}\n' + ' check-multi-line-strings: false}\n' 'document-start: disable\n') self.check('|\n' ' multi\n' @@ -1761,7 +1897,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_check_multi_line_literal_style(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: yes}\n' + ' check-multi-line-strings: true}\n' 'document-start: disable\n') self.check('|\n' ' multi\n' @@ -1805,7 +1941,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_paragraph_plain(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: yes}\n' + ' check-multi-line-strings: true}\n' 'document-start: disable\n') self.check('- long text: very "long"\n' ' \'string\' with\n' @@ -1827,7 +1963,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_paragraph_double_quoted(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: yes}\n' + ' check-multi-line-strings: true}\n' 'document-start: disable\n') self.check('- long text: "very \\"long\\"\n' ' \'string\' with\n' @@ -1855,7 +1991,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_paragraph_single_quoted(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: yes}\n' + ' check-multi-line-strings: true}\n' 'document-start: disable\n') self.check('- long text: \'very "long"\n' ' \'\'string\'\' with\n' @@ -1883,7 +2019,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_paragraph_folded(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: yes}\n' + ' check-multi-line-strings: true}\n' 'document-start: disable\n') self.check('- long text: >\n' ' very "long"\n' @@ -1901,7 +2037,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_paragraph_literal(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: yes}\n' + ' check-multi-line-strings: true}\n' 'document-start: disable\n') self.check('- long text: |\n' ' very "long"\n' @@ -1919,7 +2055,7 @@ class ScalarIndentationTestCase(RuleTestCase): def test_consistent(self): conf = ('indentation: {spaces: consistent,\n' - ' check-multi-line-strings: yes}\n' + ' check-multi-line-strings: true}\n' 'document-start: disable\n') self.check('multi\n' 'line\n', conf) diff --git a/tests/rules/test_line_length.py b/tests/rules/test_line_length.py index be1115e..cab3e18 100644 --- a/tests/rules/test_line_length.py +++ b/tests/rules/test_line_length.py @@ -66,7 +66,7 @@ class LineLengthTestCase(RuleTestCase): self.check('---\n' + 81 * ' ' + '\n', conf, problem=(2, 81)) def test_non_breakable_word(self): - conf = 'line-length: {max: 20, allow-non-breakable-words: yes}' + conf = 'line-length: {max: 20, allow-non-breakable-words: true}' self.check('---\n' + 30 * 'A' + '\n', conf) self.check('---\n' 'this:\n' @@ -91,7 +91,7 @@ class LineLengthTestCase(RuleTestCase): 'long_line: http://localhost/very/very/long/url\n', conf, problem=(2, 21)) - conf = 'line-length: {max: 20, allow-non-breakable-words: no}' + conf = 'line-length: {max: 20, allow-non-breakable-words: false}' self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21)) self.check('---\n' 'this:\n' @@ -116,7 +116,7 @@ class LineLengthTestCase(RuleTestCase): 'long_line: http://localhost/very/very/long/url\n' '...\n', conf, problem=(2, 21)) - conf = ('line-length: {max: 20, allow-non-breakable-words: yes}\n' + conf = ('line-length: {max: 20, allow-non-breakable-words: true}\n' 'trailing-spaces: disable') self.check('---\n' 'loooooooooong+word+and+some+space+at+the+end \n', @@ -124,7 +124,7 @@ class LineLengthTestCase(RuleTestCase): def test_non_breakable_inline_mappings(self): conf = 'line-length: {max: 20, ' \ - 'allow-non-breakable-inline-mappings: yes}' + 'allow-non-breakable-inline-mappings: true}' self.check('---\n' 'long_line: http://localhost/very/very/long/url\n' 'long line: http://localhost/very/very/long/url\n', conf) @@ -137,7 +137,7 @@ class LineLengthTestCase(RuleTestCase): conf, problem1=(2, 21), problem2=(3, 21)) conf = ('line-length: {max: 20,' - ' allow-non-breakable-inline-mappings: yes}\n' + ' allow-non-breakable-inline-mappings: true}\n' 'trailing-spaces: disable') self.check('---\n' 'long_line: and+some+space+at+the+end \n', @@ -150,7 +150,7 @@ class LineLengthTestCase(RuleTestCase): conf, problem=(2, 21)) # See https://github.com/adrienverge/yamllint/issues/21 - conf = 'line-length: {allow-non-breakable-inline-mappings: yes}' + conf = 'line-length: {allow-non-breakable-inline-mappings: true}' self.check('---\n' 'content: |\n' ' {% this line is' + 99 * ' really' + ' long %}\n', diff --git a/tests/test_cli.py b/tests/test_cli.py index da139a8..bec7078 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -23,61 +23,59 @@ import locale import os import pty import shutil -import tempfile -import unittest import sys +try: + assert sys.version_info >= (2, 7) + import unittest +except: + import unittest2 as unittest from yamllint import cli +from tests.common import build_temp_workspace + +@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported') class CommandLineTestCase(unittest.TestCase): - def setUp(self): - self.wd = tempfile.mkdtemp(prefix='yamllint-tests-') - - # .yaml file at root - with open(os.path.join(self.wd, 'a.yaml'), 'w') as f: - f.write('---\n' - '- 1 \n' - '- 2') - - # .yml file at root - open(os.path.join(self.wd, 'empty.yml'), 'w').close() - - # file in dir - os.mkdir(os.path.join(self.wd, 'sub')) - with open(os.path.join(self.wd, 'sub', 'ok.yaml'), 'w') as f: - f.write('---\n' - 'key: value\n') - - # file in very nested dir - dir = self.wd - for i in range(15): - dir = os.path.join(dir, 's') - os.mkdir(dir) - with open(os.path.join(dir, 'file.yaml'), 'w') as f: - f.write('---\n' - 'key: value\n' - 'key: other value\n') - - # empty dir - os.mkdir(os.path.join(self.wd, 'empty-dir')) - - # non-YAML file - with open(os.path.join(self.wd, 'no-yaml.json'), 'w') as f: - f.write('---\n' - 'key: value\n') - - # non-ASCII chars - os.mkdir(os.path.join(self.wd, 'non-ascii')) - with open(os.path.join(self.wd, 'non-ascii', 'utf-8'), 'wb') as f: - f.write((u'---\n' - u'- hétérogénéité\n' - u'# 19.99 €\n' - u'- お早う御座います。\n' - u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8')) - - def tearDown(self): - shutil.rmtree(self.wd) + @classmethod + def setUpClass(cls): + super(CommandLineTestCase, cls).setUpClass() + + cls.wd = build_temp_workspace({ + # .yaml file at root + 'a.yaml': '---\n' + '- 1 \n' + '- 2', + # file with only one warning + 'warn.yaml': 'key: value\n', + # .yml file at root + 'empty.yml': '', + # file in dir + 'sub/ok.yaml': '---\n' + 'key: value\n', + # file in very nested dir + 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml': '---\n' + 'key: value\n' + 'key: other value\n', + # empty dir + 'empty-dir': [], + # non-YAML file + 'no-yaml.json': '---\n' + 'key: value\n', + # non-ASCII chars + 'non-ascii/utf-8': ( + u'---\n' + u'- hétérogénéité\n' + u'# 19.99 €\n' + u'- お早う御座います。\n' + u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'), + }) + + @classmethod + def tearDownClass(cls): + super(CommandLineTestCase, cls).tearDownClass() + + shutil.rmtree(cls.wd) def test_find_files_recursively(self): self.assertEqual( @@ -85,7 +83,8 @@ class CommandLineTestCase(unittest.TestCase): [os.path.join(self.wd, 'a.yaml'), os.path.join(self.wd, 'empty.yml'), os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'), - os.path.join(self.wd, 'sub/ok.yaml')], + os.path.join(self.wd, 'sub/ok.yaml'), + os.path.join(self.wd, 'warn.yaml')], ) items = [os.path.join(self.wd, 'sub/ok.yaml'), @@ -140,8 +139,11 @@ class CommandLineTestCase(unittest.TestCase): out, err = sys.stdout.getvalue(), sys.stderr.getvalue() self.assertEqual(out, '') - self.assertRegexpMatches(err, r'^Options --config-file and ' - r'--config-data cannot be used') + self.assertRegexpMatches( + err.splitlines()[-1], + r'^yamllint: error: argument -d\/--config-data: ' + r'not allowed with argument -c\/--config-file$' + ) def test_run_with_bad_config(self): sys.stdout, sys.stderr = StringIO(), StringIO() @@ -247,6 +249,24 @@ class CommandLineTestCase(unittest.TestCase): '(new-line-at-end-of-file)\n') % (file, file)) self.assertEqual(err, '') + def test_run_one_warning(self): + file = os.path.join(self.wd, 'warn.yaml') + + sys.stdout, sys.stderr = StringIO(), StringIO() + with self.assertRaises(SystemExit) as ctx: + cli.run(('-f', 'parsable', file)) + + self.assertEqual(ctx.exception.code, 0) + + def test_run_warning_in_strict_mode(self): + file = os.path.join(self.wd, 'warn.yaml') + + sys.stdout, sys.stderr = StringIO(), StringIO() + with self.assertRaises(SystemExit) as ctx: + cli.run(('-f', 'parsable', '--strict', file)) + + self.assertEqual(ctx.exception.code, 2) + def test_run_one_ok_file(self): file = os.path.join(self.wd, 'sub', 'ok.yaml') diff --git a/tests/test_config.py b/tests/test_config.py index ef93ec7..a23da10 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -14,10 +14,24 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import unittest - +try: + from cStringIO import StringIO +except ImportError: + from io import StringIO +import os +import shutil +import sys +try: + assert sys.version_info >= (2, 7) + import unittest +except: + import unittest2 as unittest + +from yamllint import cli from yamllint import config +from tests.common import build_temp_workspace + class SimpleConfigTestCase(unittest.TestCase): def test_parse_config(self): @@ -30,7 +44,7 @@ class SimpleConfigTestCase(unittest.TestCase): self.assertEqual(new.rules['colons']['max-spaces-before'], 0) self.assertEqual(new.rules['colons']['max-spaces-after'], 1) - self.assertEqual(len(new.enabled_rules()), 1) + self.assertEqual(len(new.enabled_rules(None)), 1) def test_invalid_conf(self): with self.assertRaises(config.YamlLintConfigError): @@ -62,6 +76,45 @@ class SimpleConfigTestCase(unittest.TestCase): ' max-spaces-after: 1\n' ' abcdef: yes\n') + def test_yes_no_for_booleans(self): + c = config.YamlLintConfig('rules:\n' + ' indentation:\n' + ' spaces: 2\n' + ' indent-sequences: true\n' + ' check-multi-line-strings: false\n') + self.assertEqual(c.rules['indentation']['indent-sequences'], True) + self.assertEqual(c.rules['indentation']['check-multi-line-strings'], + False) + + c = config.YamlLintConfig('rules:\n' + ' indentation:\n' + ' spaces: 2\n' + ' indent-sequences: yes\n' + ' check-multi-line-strings: false\n') + self.assertEqual(c.rules['indentation']['indent-sequences'], True) + self.assertEqual(c.rules['indentation']['check-multi-line-strings'], + False) + + c = config.YamlLintConfig('rules:\n' + ' indentation:\n' + ' spaces: 2\n' + ' indent-sequences: whatever\n' + ' check-multi-line-strings: false\n') + self.assertEqual(c.rules['indentation']['indent-sequences'], + 'whatever') + self.assertEqual(c.rules['indentation']['check-multi-line-strings'], + False) + + with self.assertRaisesRegexp( + config.YamlLintConfigError, + 'invalid config: option "indent-sequences" of "indentation" ' + 'should be in '): + c = config.YamlLintConfig('rules:\n' + ' indentation:\n' + ' spaces: 2\n' + ' indent-sequences: YES!\n' + ' check-multi-line-strings: false\n') + def test_validate_rule_conf(self): class Rule(object): ID = 'fake' @@ -131,7 +184,7 @@ class ExtendedConfigTestCase(unittest.TestCase): self.assertEqual(new.rules['colons']['max-spaces-after'], 1) self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2) - self.assertEqual(len(new.enabled_rules()), 2) + self.assertEqual(len(new.enabled_rules(None)), 2) def test_extend_remove_rule(self): old = config.YamlLintConfig('rules:\n' @@ -148,7 +201,7 @@ class ExtendedConfigTestCase(unittest.TestCase): self.assertEqual(new.rules['colons'], False) self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2) - self.assertEqual(len(new.enabled_rules()), 1) + self.assertEqual(len(new.enabled_rules(None)), 1) def test_extend_edit_rule(self): old = config.YamlLintConfig('rules:\n' @@ -168,7 +221,7 @@ class ExtendedConfigTestCase(unittest.TestCase): self.assertEqual(new.rules['colons']['max-spaces-after'], 4) self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2) - self.assertEqual(len(new.enabled_rules()), 2) + self.assertEqual(len(new.enabled_rules(None)), 2) def test_extend_reenable_rule(self): old = config.YamlLintConfig('rules:\n' @@ -186,7 +239,7 @@ class ExtendedConfigTestCase(unittest.TestCase): self.assertEqual(new.rules['colons']['max-spaces-after'], 1) self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2) - self.assertEqual(len(new.enabled_rules()), 2) + self.assertEqual(len(new.enabled_rules(None)), 2) class ExtendedLibraryConfigTestCase(unittest.TestCase): @@ -231,3 +284,94 @@ class ExtendedLibraryConfigTestCase(unittest.TestCase): self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys())) for rule in new.rules: self.assertEqual(new.rules[rule], old.rules[rule]) + + +class IgnorePathConfigTestCase(unittest.TestCase): + @classmethod + def setUpClass(cls): + super(IgnorePathConfigTestCase, cls).setUpClass() + + bad_yaml = ('---\n' + '- key: val1\n' + ' key: val2\n' + '- trailing space \n' + '- lonely hyphen\n') + + cls.wd = build_temp_workspace({ + 'bin/file.lint-me-anyway.yaml': bad_yaml, + 'bin/file.yaml': bad_yaml, + 'file-at-root.yaml': bad_yaml, + 'file.dont-lint-me.yaml': bad_yaml, + 'ign-dup/file.yaml': bad_yaml, + 'ign-dup/sub/dir/file.yaml': bad_yaml, + 'ign-trail/file.yaml': bad_yaml, + 'include/ign-dup/sub/dir/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/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() + os.chdir(cls.wd) + + @classmethod + def tearDownClass(cls): + super(IgnorePathConfigTestCase, cls).tearDownClass() + + os.chdir(cls.backup_wd) + + shutil.rmtree(cls.wd) + + @unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported') + def test_run_with_ignored_path(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, + './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, + ))) diff --git a/tests/test_linter.py b/tests/test_linter.py index db126ad..edd803f 100644 --- a/tests/test_linter.py +++ b/tests/test_linter.py @@ -15,8 +15,12 @@ # along with this program. If not, see . import io - -import unittest +import sys +try: + assert sys.version_info >= (2, 7) + import unittest +except: + import unittest2 as unittest from yamllint.config import YamlLintConfig from yamllint import linter diff --git a/tests/test_module.py b/tests/test_module.py new file mode 100644 index 0000000..678f40c --- /dev/null +++ b/tests/test_module.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2017 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import shutil +import subprocess +import tempfile +import sys +try: + assert sys.version_info >= (2, 7) + import unittest +except: + import unittest2 as unittest + + +@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported') +class ModuleTestCase(unittest.TestCase): + def setUp(self): + self.wd = tempfile.mkdtemp(prefix='yamllint-tests-') + + # file with only one warning + with open(os.path.join(self.wd, 'warn.yaml'), 'w') as f: + f.write('key: value\n') + + # file in dir + os.mkdir(os.path.join(self.wd, 'sub')) + with open(os.path.join(self.wd, 'sub', 'nok.yaml'), 'w') as f: + f.write('---\n' + 'list: [ 1, 1, 2, 3, 5, 8] \n') + + def tearDown(self): + shutil.rmtree(self.wd) + + def test_run_module_no_args(self): + with self.assertRaises(subprocess.CalledProcessError) as ctx: + subprocess.check_output(['python', '-m', 'yamllint'], + stderr=subprocess.STDOUT) + self.assertEqual(ctx.exception.returncode, 2) + self.assertRegexpMatches(ctx.exception.output.decode(), + r'^usage: yamllint') + + def test_run_module_on_bad_dir(self): + with self.assertRaises(subprocess.CalledProcessError) as ctx: + subprocess.check_output(['python', '-m', 'yamllint', + '/does/not/exist'], + stderr=subprocess.STDOUT) + self.assertRegexpMatches(ctx.exception.output.decode(), + r'No such file or directory') + + def test_run_module_on_file(self): + out = subprocess.check_output( + ['python', '-m', 'yamllint', os.path.join(self.wd, 'warn.yaml')]) + lines = out.decode().splitlines() + self.assertIn('/warn.yaml', lines[0]) + self.assertEqual('\n'.join(lines[1:]), + ' 1:1 warning missing document start "---"' + ' (document-start)\n') + + def test_run_module_on_dir(self): + with self.assertRaises(subprocess.CalledProcessError) as ctx: + subprocess.check_output(['python', '-m', 'yamllint', self.wd]) + self.assertEqual(ctx.exception.returncode, 1) + + files = ctx.exception.output.decode().split('\n\n') + self.assertIn( + '/warn.yaml\n' + ' 1:1 warning missing document start "---"' + ' (document-start)', + files[0]) + self.assertIn( + '/sub/nok.yaml\n' + ' 2:9 error too many spaces inside brackets' + ' (brackets)\n' + ' 2:27 error trailing spaces (trailing-spaces)', + files[1]) diff --git a/tests/test_parser.py b/tests/test_parser.py index e40b9ac..c5c51d8 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -14,7 +14,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import unittest +import sys +try: + assert sys.version_info >= (2, 7) + import unittest +except: + import unittest2 as unittest import yaml diff --git a/tests/test_spec_examples.py b/tests/test_spec_examples.py index 04d6ea7..f3e9239 100644 --- a/tests/test_spec_examples.py +++ b/tests/test_spec_examples.py @@ -48,6 +48,7 @@ from tests.common import RuleTestCase class SpecificationTestCase(RuleTestCase): rule_id = None + conf_general = ('document-start: disable\n' 'comments: {min-spaces-from-content: 1}\n' 'braces: {min-spaces-inside: 1, max-spaces-inside: 1}\n' @@ -66,7 +67,7 @@ conf_overrides = { 'example-2.18': ('empty-lines: {max-end: 1}\n'), 'example-2.19': ('empty-lines: {max-end: 1}\n'), 'example-2.28': ('empty-lines: {max-end: 3}\n'), - 'example-5.3': ('indentation: {indent-sequences: no}\n' + 'example-5.3': ('indentation: {indent-sequences: false}\n' 'colons: {max-spaces-before: 1}\n'), 'example-6.4': ('trailing-spaces: disable\n'), 'example-6.5': ('trailing-spaces: disable\n'), @@ -114,11 +115,11 @@ conf_overrides = { 'example-8.14': ('colons: {max-spaces-before: 1}\n'), 'example-8.16': ('indentation: {spaces: 1}\n'), 'example-8.17': ('indentation: disable\n'), - 'example-8.20': ('indentation: {indent-sequences: no}\n' + 'example-8.20': ('indentation: {indent-sequences: false}\n' 'colons: {max-spaces-before: 1}\n'), 'example-8.22': ('indentation: disable\n'), 'example-10.1': ('colons: {max-spaces-before: 2}\n'), - 'example-10.2': ('indentation: {indent-sequences: no}\n'), + 'example-10.2': ('indentation: {indent-sequences: false}\n'), 'example-10.8': ('truthy: disable\n'), 'example-10.9': ('truthy: disable\n'), } @@ -133,6 +134,7 @@ def _gen_test(buffer, conf): self.check(buffer, conf) return test + # The following tests are blacklisted (i.e. will not be checked against # yamllint), because pyyaml is currently not able to parse the contents # (using yaml.parse()). diff --git a/yamllint/__init__.py b/yamllint/__init__.py index 1219cca..e53dce1 100644 --- a/yamllint/__init__.py +++ b/yamllint/__init__.py @@ -22,7 +22,7 @@ indentation, etc.""" APP_NAME = 'yamllint' -APP_VERSION = '1.5.0' +APP_VERSION = '1.8.1' APP_DESCRIPTION = __doc__ __author__ = u'Adrien Vergé' diff --git a/yamllint/__main__.py b/yamllint/__main__.py new file mode 100644 index 0000000..bc16534 --- /dev/null +++ b/yamllint/__main__.py @@ -0,0 +1,4 @@ +from yamllint.cli import run + +if __name__ == '__main__': + run() diff --git a/yamllint/cli.py b/yamllint/cli.py index 9e3e5f3..41695a3 100644 --- a/yamllint/cli.py +++ b/yamllint/cli.py @@ -15,6 +15,7 @@ # along with this program. If not, see . from __future__ import print_function + import os.path import sys @@ -22,6 +23,7 @@ import argparse from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION from yamllint.config import YamlLintConfig, YamlLintConfigError +from yamllint.linter import PROBLEM_LEVELS from yamllint import linter @@ -77,14 +79,20 @@ def run(argv=None): description=APP_DESCRIPTION) parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+', help='files to check') - parser.add_argument('-c', '--config-file', dest='config_file', - action='store', help='path to a custom configuration') - parser.add_argument('-d', '--config-data', dest='config_data', - action='store', - help='custom configuration (as YAML source)') + config_group = parser.add_mutually_exclusive_group() + config_group.add_argument('-c', '--config-file', dest='config_file', + action='store', + help='path to a custom configuration') + config_group.add_argument('-d', '--config-data', dest='config_data', + action='store', + help='custom configuration (as YAML source)') parser.add_argument('-f', '--format', choices=('parsable', 'standard'), default='standard', help='format for parsing output') + parser.add_argument('-s', '--strict', + action='store_true', + help='return non-zero exit code on warnings ' + 'as well as errors') parser.add_argument('-v', '--version', action='version', version='%s %s' % (APP_NAME, APP_VERSION)) @@ -92,11 +100,6 @@ def run(argv=None): args = parser.parse_args(argv) - if args.config_file is not None and args.config_data is not None: - print('Options --config-file and --config-data cannot be used ' - 'simultaneously.', file=sys.stderr) - sys.exit(-1) - # User-global config is supposed to be in ~/.config/yamllint/config if 'XDG_CONFIG_HOME' in os.environ: user_global_config = os.path.join( @@ -121,13 +124,14 @@ def run(argv=None): print(e, file=sys.stderr) sys.exit(-1) - return_code = 0 + max_level = 0 for file in find_files_recursively(args.files): + filepath = file[2:] if file.startswith('./') else file try: first = True with open(file) as f: - for problem in linter.run(f, conf): + for problem in linter.run(f, conf, filepath): if args.format == 'parsable': print(Format.parsable(problem, file)) elif sys.stdout.isatty(): @@ -143,13 +147,19 @@ def run(argv=None): print(Format.standard(problem, file)) - if return_code == 0 and problem.level == 'error': - return_code = 1 + max_level = max(max_level, PROBLEM_LEVELS[problem.level]) if not first and args.format != 'parsable': print('') except EnvironmentError as e: print(e, file=sys.stderr) - return_code = -1 + sys.exit(-1) + + if max_level == PROBLEM_LEVELS['error']: + return_code = 1 + elif max_level == PROBLEM_LEVELS['warning']: + return_code = 2 if args.strict else 0 + else: + return_code = 0 sys.exit(return_code) diff --git a/yamllint/conf/default.yaml b/yamllint/conf/default.yaml index e4d720b..c7c4da4 100644 --- a/yamllint/conf/default.yaml +++ b/yamllint/conf/default.yaml @@ -4,9 +4,13 @@ rules: braces: min-spaces-inside: 0 max-spaces-inside: 0 + min-spaces-inside-empty: -1 + max-spaces-inside-empty: -1 brackets: min-spaces-inside: 0 max-spaces-inside: 0 + min-spaces-inside-empty: -1 + max-spaces-inside-empty: -1 colons: max-spaces-before: 0 max-spaces-after: 1 @@ -16,14 +20,14 @@ rules: max-spaces-after: 1 comments: level: warning - require-starting-space: yes + require-starting-space: true min-spaces-from-content: 2 comments-indentation: level: warning document-end: disable document-start: level: warning - present: yes + present: true empty-lines: max: 2 max-start: 0 @@ -32,13 +36,13 @@ rules: max-spaces-after: 1 indentation: spaces: consistent - indent-sequences: yes - check-multi-line-strings: no + indent-sequences: true + check-multi-line-strings: false key-duplicates: enable line-length: max: 80 - allow-non-breakable-words: yes - allow-non-breakable-inline-mappings: no + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: false new-line-at-end-of-file: enable new-lines: type: unix diff --git a/yamllint/conf/relaxed.yaml b/yamllint/conf/relaxed.yaml index fb2cdaf..83f5340 100644 --- a/yamllint/conf/relaxed.yaml +++ b/yamllint/conf/relaxed.yaml @@ -25,5 +25,5 @@ rules: indent-sequences: consistent line-length: level: warning - allow-non-breakable-inline-mappings: yes + allow-non-breakable-inline-mappings: true truthy: disable diff --git a/yamllint/config.py b/yamllint/config.py index 48ea380..fb5a161 100644 --- a/yamllint/config.py +++ b/yamllint/config.py @@ -16,6 +16,7 @@ import os.path +import pathspec import yaml import yamllint.rules @@ -29,6 +30,8 @@ class YamlLintConfig(object): def __init__(self, content=None, file=None): assert (content is None) ^ (file is None) + self.ignore = None + if file is not None: with open(file) as f: content = f.read() @@ -36,9 +39,14 @@ class YamlLintConfig(object): self.parse(content) self.validate() - def enabled_rules(self): + def is_file_ignored(self, filepath): + return self.ignore and self.ignore.match_file(filepath) + + def enabled_rules(self, filepath): return [yamllint.rules.get(id) for id, val in self.rules.items() - if val is not False] + if val is not False and ( + filepath is None or 'ignore' not in val or + not val['ignore'].match_file(filepath))] def extend(self, base_config): assert isinstance(base_config, YamlLintConfig) @@ -53,6 +61,9 @@ class YamlLintConfig(object): self.rules = base_config.rules + if base_config.ignore is not None: + self.ignore = base_config.ignore + def parse(self, raw_content): try: conf = yaml.safe_load(raw_content) @@ -73,6 +84,13 @@ class YamlLintConfig(object): except Exception as e: raise YamlLintConfigError('invalid config: %s' % e) + if 'ignore' in conf: + if type(conf['ignore']) != str: + raise YamlLintConfigError( + 'invalid config: ignore should be a list of patterns') + self.ignore = pathspec.PathSpec.from_lines( + 'gitwildmatch', conf['ignore'].splitlines()) + def validate(self): for id in self.rules: try: @@ -90,6 +108,14 @@ def validate_rule_conf(rule, conf): conf = {} if type(conf) == dict: + if ('ignore' in conf and + type(conf['ignore']) != pathspec.pathspec.PathSpec): + if type(conf['ignore']) != str: + raise YamlLintConfigError( + 'invalid config: ignore should be a list of patterns') + conf['ignore'] = pathspec.PathSpec.from_lines( + 'gitwildmatch', conf['ignore'].splitlines()) + if 'level' not in conf: conf['level'] = 'error' elif conf['level'] not in ('error', 'warning'): @@ -98,7 +124,7 @@ def validate_rule_conf(rule, conf): options = getattr(rule, 'CONF', {}) for optkey in conf: - if optkey == 'level': + if optkey in ('ignore', 'level'): continue if optkey not in options: raise YamlLintConfigError( diff --git a/yamllint/linter.py b/yamllint/linter.py index b546e8a..c8eff8d 100644 --- a/yamllint/linter.py +++ b/yamllint/linter.py @@ -21,6 +21,16 @@ import yaml from yamllint import parser +PROBLEM_LEVELS = { + 0: None, + 1: 'warning', + 2: 'error', + None: 0, + 'warning': 1, + 'error': 2, +} + + class LintProblem(object): """Represents a linting problem found by yamllint.""" def __init__(self, line, column, desc='', rule=None): @@ -53,8 +63,8 @@ class LintProblem(object): return '%d:%d: %s' % (self.line, self.column, self.message) -def get_costemic_problems(buffer, conf): - rules = conf.enabled_rules() +def get_cosmetic_problems(buffer, conf, filepath): + rules = conf.enabled_rules(filepath) # Split token rules from line rules token_rules = [r for r in rules if r.TYPE == 'token'] @@ -175,7 +185,7 @@ def get_syntax_error(buffer): return problem -def _run(buffer, conf): +def _run(buffer, conf, filepath): assert hasattr(buffer, '__getitem__'), \ '_run() argument must be a buffer, not a stream' @@ -183,7 +193,7 @@ def _run(buffer, conf): # right line syntax_error = get_syntax_error(buffer) - for problem in get_costemic_problems(buffer, conf): + for problem in get_cosmetic_problems(buffer, conf, filepath): # Insert the syntax error (if any) at the right place... if (syntax_error and syntax_error.line <= problem.line and syntax_error.column <= problem.column): @@ -205,7 +215,7 @@ def _run(buffer, conf): yield syntax_error -def run(input, conf): +def run(input, conf, filepath=None): """Lints a YAML source. Returns a generator of LintProblem objects. @@ -213,11 +223,14 @@ def run(input, conf): :param input: buffer, string or stream to read from :param conf: yamllint configuration object """ + if conf.is_file_ignored(filepath): + return () + if type(input) in (type(b''), type(u'')): # compat with Python 2 & 3 - return _run(input, conf) + return _run(input, conf, filepath) elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase # We need to have everything in memory to parse correctly content = input.read() - return _run(content, conf) + return _run(content, conf, filepath) else: raise TypeError('input should be a string or a stream') diff --git a/yamllint/rules/braces.py b/yamllint/rules/braces.py index 55f465e..59d9558 100644 --- a/yamllint/rules/braces.py +++ b/yamllint/rules/braces.py @@ -23,6 +23,10 @@ Use this rule to control the number of spaces inside braces (``{`` and ``}``). braces. * ``max-spaces-inside`` defines the maximal number of spaces allowed inside braces. +* ``min-spaces-inside-empty`` defines the minimal number of spaces required + inside empty braces. +* ``max-spaces-inside-empty`` defines the maximal number of spaces allowed + inside empty braces. .. rubric:: Examples @@ -59,6 +63,30 @@ Use this rule to control the number of spaces inside braces (``{`` and ``}``). :: object: {key1: 4, key2: 8 } + +#. With ``braces: {min-spaces-inside-empty: 0, max-spaces-inside-empty: 0}`` + + the following code snippet would **PASS**: + :: + + object: {} + + the following code snippet would **FAIL**: + :: + + object: { } + +#. With ``braces: {min-spaces-inside-empty: 1, max-spaces-inside-empty: -1}`` + + the following code snippet would **PASS**: + :: + + object: { } + + the following code snippet would **FAIL**: + :: + + object: {} """ @@ -70,11 +98,27 @@ from yamllint.rules.common import spaces_after, spaces_before ID = 'braces' TYPE = 'token' CONF = {'min-spaces-inside': int, - 'max-spaces-inside': int} + 'max-spaces-inside': int, + 'min-spaces-inside-empty': int, + 'max-spaces-inside-empty': int} def check(conf, token, prev, next, nextnext, context): - if isinstance(token, yaml.FlowMappingStartToken): + if (isinstance(token, yaml.FlowMappingStartToken) and + isinstance(next, yaml.FlowMappingEndToken)): + problem = spaces_after(token, prev, next, + min=(conf['min-spaces-inside-empty'] + if conf['min-spaces-inside-empty'] != -1 + else conf['min-spaces-inside']), + max=(conf['max-spaces-inside-empty'] + if conf['max-spaces-inside-empty'] != -1 + else conf['max-spaces-inside']), + min_desc='too few spaces inside empty braces', + max_desc='too many spaces inside empty braces') + if problem is not None: + yield problem + + elif isinstance(token, yaml.FlowMappingStartToken): problem = spaces_after(token, prev, next, min=conf['min-spaces-inside'], max=conf['max-spaces-inside'], diff --git a/yamllint/rules/brackets.py b/yamllint/rules/brackets.py index ae0e744..33bdaa9 100644 --- a/yamllint/rules/brackets.py +++ b/yamllint/rules/brackets.py @@ -24,6 +24,10 @@ Use this rule to control the number of spaces inside brackets (``[`` and brackets. * ``max-spaces-inside`` defines the maximal number of spaces allowed inside brackets. +* ``min-spaces-inside-empty`` defines the minimal number of spaces required + inside empty brackets. +* ``max-spaces-inside-empty`` defines the maximal number of spaces allowed + inside empty brackets. .. rubric:: Examples @@ -60,6 +64,30 @@ Use this rule to control the number of spaces inside brackets (``[`` and :: object: [1, 2, abc ] + +#. With ``brackets: {min-spaces-inside-empty: 0, max-spaces-inside-empty: 0}`` + + the following code snippet would **PASS**: + :: + + object: [] + + the following code snippet would **FAIL**: + :: + + object: [ ] + +#. With ``brackets: {min-spaces-inside-empty: 1, max-spaces-inside-empty: -1}`` + + the following code snippet would **PASS**: + :: + + object: [ ] + + the following code snippet would **FAIL**: + :: + + object: [] """ @@ -71,11 +99,28 @@ from yamllint.rules.common import spaces_after, spaces_before ID = 'brackets' TYPE = 'token' CONF = {'min-spaces-inside': int, - 'max-spaces-inside': int} + 'max-spaces-inside': int, + 'min-spaces-inside-empty': int, + 'max-spaces-inside-empty': int} def check(conf, token, prev, next, nextnext, context): - if isinstance(token, yaml.FlowSequenceStartToken): + if (isinstance(token, yaml.FlowSequenceStartToken) and + isinstance(next, yaml.FlowSequenceEndToken)): + problem = spaces_after(token, prev, next, + min=(conf['min-spaces-inside-empty'] + if conf['min-spaces-inside-empty'] != -1 + else conf['min-spaces-inside']), + max=(conf['max-spaces-inside-empty'] + if conf['max-spaces-inside-empty'] != -1 + else conf['max-spaces-inside']), + min_desc='too few spaces inside empty brackets', + max_desc=('too many spaces inside empty ' + 'brackets')) + if problem is not None: + yield problem + + elif isinstance(token, yaml.FlowSequenceStartToken): problem = spaces_after(token, prev, next, min=conf['min-spaces-inside'], max=conf['max-spaces-inside'], diff --git a/yamllint/rules/comments.py b/yamllint/rules/comments.py index 33fbcbe..69da045 100644 --- a/yamllint/rules/comments.py +++ b/yamllint/rules/comments.py @@ -20,14 +20,14 @@ Use this rule to control the position and formatting of comments. .. rubric:: Options * Use ``require-starting-space`` to require a space character right after the - ``#``. Set to ``yes`` to enable, ``no`` to disable. + ``#``. Set to ``true`` to enable, ``false`` to disable. * ``min-spaces-from-content`` is used to visually separate inline comments from content. It defines the minimal required number of spaces between a comment and its preceding content. .. rubric:: Examples -#. With ``comments: {require-starting-space: yes}`` +#. With ``comments: {require-starting-space: true}`` the following code snippet would **PASS**: :: diff --git a/yamllint/rules/document_end.py b/yamllint/rules/document_end.py index c6e7356..34323e4 100644 --- a/yamllint/rules/document_end.py +++ b/yamllint/rules/document_end.py @@ -19,12 +19,12 @@ Use this rule to require or forbid the use of document end marker (``...``). .. rubric:: Options -* Set ``present`` to ``yes`` when the document end marker is required, or to - ``no`` when it is forbidden. +* Set ``present`` to ``true`` when the document end marker is required, or to + ``false`` when it is forbidden. .. rubric:: Examples -#. With ``document-end: {present: yes}`` +#. With ``document-end: {present: true}`` the following code snippet would **PASS**: :: @@ -49,7 +49,7 @@ Use this rule to require or forbid the use of document end marker (``...``). - is: another one ... -#. With ``document-end: {present: no}`` +#. With ``document-end: {present: false}`` the following code snippet would **PASS**: :: diff --git a/yamllint/rules/document_start.py b/yamllint/rules/document_start.py index 5955a7f..090260a 100644 --- a/yamllint/rules/document_start.py +++ b/yamllint/rules/document_start.py @@ -19,12 +19,12 @@ Use this rule to require or forbid the use of document start marker (``---``). .. rubric:: Options -* Set ``present`` to ``yes`` when the document start marker is required, or to - ``no`` when it is forbidden. +* Set ``present`` to ``true`` when the document start marker is required, or to + ``false`` when it is forbidden. .. rubric:: Examples -#. With ``document-start: {present: yes}`` +#. With ``document-start: {present: true}`` the following code snippet would **PASS**: :: @@ -45,7 +45,7 @@ Use this rule to require or forbid the use of document start marker (``---``). - this - is: another one -#. With ``document-start: {present: no}`` +#. With ``document-start: {present: false}`` the following code snippet would **PASS**: :: diff --git a/yamllint/rules/indentation.py b/yamllint/rules/indentation.py index 9388e4f..432c23c 100644 --- a/yamllint/rules/indentation.py +++ b/yamllint/rules/indentation.py @@ -25,12 +25,12 @@ Use this rule to control the indentation. same within the file. * ``indent-sequences`` defines whether block sequences should be indented or not (when in a mapping, this indentation is not mandatory -- some people - perceive the ``-`` as part of the indentation). Possible values: ``yes``, - ``no``, ``whatever`` and ``consistent``. ``consistent`` requires either all - block sequences to be indented, or none to be. ``whatever`` means either + perceive the ``-`` as part of the indentation). Possible values: ``true``, + ``false``, ``whatever`` and ``consistent``. ``consistent`` requires either + all block sequences to be indented, or none to be. ``whatever`` means either indenting or not indenting individual block sequences is OK. * ``check-multi-line-strings`` defines whether to lint indentation in - multi-line strings. Set to ``yes`` to enable, ``no`` to disable. + multi-line strings. Set to ``true`` to enable, ``false`` to disable. .. rubric:: Examples @@ -99,7 +99,7 @@ Use this rule to control the indentation. Russian: dolls -#. With ``indentation: {spaces: 2, indent-sequences: no}`` +#. With ``indentation: {spaces: 2, indent-sequences: false}`` the following code snippet would **PASS**: :: @@ -152,7 +152,7 @@ Use this rule to control the indentation. - spaghetti - sauce -#. With ``indentation: {spaces: 4, check-multi-line-strings: yes}`` +#. With ``indentation: {spaces: 4, check-multi-line-strings: true}`` the following code snippet would **PASS**: :: @@ -469,7 +469,19 @@ def _check(conf, token, prev, next, nextnext, context): if context['indent-sequences'] is False: indent = context['stack'][-1].indent elif context['indent-sequences'] is True: - indent = detect_indent(context['stack'][-1].indent, next) + if (context['spaces'] == 'consistent' and + next.start_mark.column - + context['stack'][-1].indent == 0): + # In this case, the block sequence item is not indented + # (while it should be), but we don't know yet the + # indentation it should have (because `spaces` is + # `consistent` and its value has not been computed yet + # -- this is probably the beginning of the document). + # So we choose an arbitrary value (2). + indent = 2 + else: + indent = detect_indent(context['stack'][-1].indent, + next) else: # 'whatever' or 'consistent' if next.start_mark.column == context['stack'][-1].indent: # key: diff --git a/yamllint/rules/line_length.py b/yamllint/rules/line_length.py index a0c0b37..e87972e 100644 --- a/yamllint/rules/line_length.py +++ b/yamllint/rules/line_length.py @@ -22,7 +22,7 @@ Use this rule to set a limit to lines length. * ``max`` defines the maximal (inclusive) length of lines. * ``allow-non-breakable-words`` is used to allow non breakable words (without spaces inside) to overflow the limit. This is useful for long URLs, for - instance. Use ``yes`` to allow, ``no`` to forbid. + instance. Use ``true`` to allow, ``false`` to forbid. * ``allow-non-breakable-inline-mappings`` implies ``allow-non-breakable-words`` and extends it to also allow non-breakable words in inline mappings. @@ -44,7 +44,7 @@ Use this rule to set a limit to lines length. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. -#. With ``line-length: {max: 60, allow-non-breakable-words: yes}`` +#. With ``line-length: {max: 60, allow-non-breakable-words: true}`` the following code snippet would **PASS**: :: @@ -68,15 +68,15 @@ Use this rule to set a limit to lines length. - foobar: http://localhost/very/very/very/very/very/very/very/very/long/url -#. With ``line-length: {max: 60, allow-non-breakable-words: yes, - allow-non-breakable-inline-mappings: yes}`` +#. With ``line-length: {max: 60, allow-non-breakable-words: true, + allow-non-breakable-inline-mappings: true}`` the following code snippet would **PASS**: :: - foobar: http://localhost/very/very/very/very/very/very/very/very/long/url -#. With ``line-length: {max: 60, allow-non-breakable-words: no}`` +#. With ``line-length: {max: 60, allow-non-breakable-words: false}`` the following code snippet would **FAIL**: :: diff --git a/yamllint/rules/truthy.py b/yamllint/rules/truthy.py index 8b541a7..c453c86 100644 --- a/yamllint/rules/truthy.py +++ b/yamllint/rules/truthy.py @@ -17,9 +17,9 @@ """ Use this rule to forbid truthy values that are not quoted nor explicitly typed. -This would prevent YAML parsers to tranform ``[yes, FALSE, Off]`` into ``[true, -false, false]`` or ``{y: 1, yes: 2, on: 3, true: 4, True: 5}`` into ``{y: 1, -true: 5}``. +This would prevent YAML parsers from transforming ``[yes, FALSE, Off]`` into +``[true, false, false]`` or ``{y: 1, yes: 2, on: 3, true: 4, True: 5}`` into +``{y: 1, true: 5}``. .. rubric:: Examples