From 3c525ab74352628c8467c03fe9cc6a6eef1dc21b Mon Sep 17 00:00:00 2001 From: Adam Chainz Date: Mon, 10 Oct 2016 10:40:36 +0100 Subject: [PATCH 01/33] Release as a universal wheel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By releasing as a [Python wheel](http://pythonwheels.com/) as well as a source distribution, you can speed up end user’s installs. After merging this command, to release you just need to run `python setup.py clean sdist bdist_wheel upload`. --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2a9acf1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 From 03e0f5aa6b2bf2ca0d92535f0cd75518094b281e Mon Sep 17 00:00:00 2001 From: Jonathan Sokolowski Date: Mon, 24 Oct 2016 14:08:26 +1100 Subject: [PATCH 02/33] Add strict mode argument to CLI --- tests/test_cli.py | 25 ++++++++++++++++++++++++- yamllint/cli.py | 19 +++++++++++++++---- yamllint/linter.py | 10 ++++++++++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index da139a8..8a5bab5 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -40,6 +40,10 @@ class CommandLineTestCase(unittest.TestCase): '- 1 \n' '- 2') + # file with only one warning + with open(os.path.join(self.wd, 'warn.yaml'), 'w') as f: + f.write('key: value\n') + # .yml file at root open(os.path.join(self.wd, 'empty.yml'), 'w').close() @@ -85,7 +89,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'), @@ -247,6 +252,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/yamllint/cli.py b/yamllint/cli.py index 9e3e5f3..1a22f73 100644 --- a/yamllint/cli.py +++ b/yamllint/cli.py @@ -22,6 +22,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 @@ -85,6 +86,10 @@ def run(argv=None): 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)) @@ -121,7 +126,7 @@ 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): try: @@ -143,13 +148,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/linter.py b/yamllint/linter.py index b546e8a..1698556 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): From bf386b3c9063b49b320c2e1dd799f0222689f0ec Mon Sep 17 00:00:00 2001 From: Jonathan Sokolowski Date: Mon, 24 Oct 2016 14:15:23 +1100 Subject: [PATCH 03/33] docs: Explain strict mode return codes --- docs/configuration.rst | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 661e506..517e098 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -102,8 +102,15 @@ 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 will 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 From 64369db9a2f343158fe51062ef7af7f13a0e3320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Mon, 7 Nov 2016 18:11:33 +0100 Subject: [PATCH 04/33] docs(configuration): Fix typo --- docs/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 517e098..44aad82 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -105,7 +105,7 @@ 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). -By default the script will exit will a return code ``1`` *only when* there is one or +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 From 2b7f5c5e72437a4c783abee7cbf16a1ff3cd3e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Wed, 16 Nov 2016 18:50:13 +0100 Subject: [PATCH 05/33] docs(install): Update Debian version yamllint is now backported in Debian 8 (Jessie): https://tracker.debian.org/pkg/yamllint https://packages.debian.org/source/jessie-backports/yamllint --- README.rst | 2 +- docs/quickstart.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 1fb9541..5393adf 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 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 From b97b6ad19bfa14eafea84d294c8cfba8acaa7e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Thu, 17 Nov 2016 12:24:38 +0100 Subject: [PATCH 06/33] style(tests): Fix new flake8 errors This change fixes new errors detected by the last version of pycodestyle (2.2.0), which is a dependency of flake8: ./tests/test_spec_examples.py:51:1: E305 expected 2 blank lines after class or function definition, found 1 ./tests/test_spec_examples.py:139:1: E305 expected 2 blank lines after class or function definition, found 1 See pycodestyle changelog at 2.2.0 and https://github.com/PyCQA/pycodestyle/pull/593. --- tests/test_spec_examples.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_spec_examples.py b/tests/test_spec_examples.py index 04d6ea7..cdc2bdb 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' @@ -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()). From 21e81b643564310c767d7e7397853b869fece775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Thu, 17 Nov 2016 14:27:24 +0100 Subject: [PATCH 07/33] fix(rules): Use `true`/`false`, not `yes`/`no` Although `yes` and `no` are recognized as booleans by the pyyaml parser, the correct keywords are `true` and `false` (as highlighted by the newly added `truthy` rule). This commit replaces the use of `yes`/`no` by `true`/`false` and advertise it in the docs, but also makes sure this change is backward-compatible (so that `yes` and `no` still work). --- tests/rules/test_comments.py | 14 +++++----- tests/rules/test_document_end.py | 6 ++-- tests/rules/test_document_start.py | 8 +++--- tests/rules/test_indentation.py | 44 +++++++++++++++--------------- tests/rules/test_line_length.py | 12 ++++---- tests/test_config.py | 39 ++++++++++++++++++++++++++ tests/test_spec_examples.py | 6 ++-- yamllint/conf/default.yaml | 12 ++++---- yamllint/conf/relaxed.yaml | 2 +- yamllint/rules/comments.py | 4 +-- yamllint/rules/document_end.py | 8 +++--- yamllint/rules/document_start.py | 8 +++--- yamllint/rules/indentation.py | 12 ++++---- yamllint/rules/line_length.py | 10 +++---- 14 files changed, 112 insertions(+), 73 deletions(-) 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..e435304 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' @@ -1130,7 +1130,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 +1438,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 +1505,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 +1534,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 +1557,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 +1588,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 +1644,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 +1682,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 +1723,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 +1761,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 +1805,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 +1827,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 +1855,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 +1883,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 +1901,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 +1919,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_config.py b/tests/test_config.py index ef93ec7..5f06ef1 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -62,6 +62,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' diff --git a/tests/test_spec_examples.py b/tests/test_spec_examples.py index cdc2bdb..f3e9239 100644 --- a/tests/test_spec_examples.py +++ b/tests/test_spec_examples.py @@ -67,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'), @@ -115,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'), } diff --git a/yamllint/conf/default.yaml b/yamllint/conf/default.yaml index e4d720b..0a22e81 100644 --- a/yamllint/conf/default.yaml +++ b/yamllint/conf/default.yaml @@ -16,14 +16,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 +32,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/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..db62f38 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**: :: 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**: :: From 3bc72d4c4068b812a223aa13b0f3729c66d094c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Fri, 18 Nov 2016 11:55:09 +0100 Subject: [PATCH 08/33] feat(CI): Enforce strict checking of YAML files Use the `--strict` flag to check all rules on local YAML files, to prevent all problems (including warnings). This includes the newly added `truthy` rule. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0ff3297..c39f155 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ install: - pip install . script: - flake8 . - - yamllint $(git ls-files '*.yaml' '*.yml') + - yamllint --strict $(git ls-files '*.yaml' '*.yml') - coverage run --source=yamllint setup.py test after_success: coveralls From e909692f882cbf24f9b24795021f2316b49d5229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Fri, 18 Nov 2016 14:28:46 +0100 Subject: [PATCH 09/33] docs(truthy): Fix typo --- yamllint/rules/truthy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 42eda540145e3d7772daaee417958ab3c9510b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Wed, 30 Nov 2016 09:37:52 +0100 Subject: [PATCH 10/33] yamllint version 1.6.0 --- yamllint/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yamllint/__init__.py b/yamllint/__init__.py index 1219cca..45c28c8 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.6.0' APP_DESCRIPTION = __doc__ __author__ = u'Adrien Vergé' From a7dbfb08b35f1d3bc3cfc505488dc47adaf91adc Mon Sep 17 00:00:00 2001 From: bootswithdefer Date: Fri, 23 Dec 2016 12:04:45 -0700 Subject: [PATCH 11/33] support for pre-commit from yelp --- hooks.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 hooks.yaml 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)$ From c332c8e3d49691b3d4cf925ad07af47992cfed52 Mon Sep 17 00:00:00 2001 From: Russell Teague Date: Wed, 11 Jan 2017 13:52:19 -0500 Subject: [PATCH 12/33] Minor cosmetic typo --- yamllint/linter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yamllint/linter.py b/yamllint/linter.py index 1698556..012bcbd 100644 --- a/yamllint/linter.py +++ b/yamllint/linter.py @@ -63,7 +63,7 @@ class LintProblem(object): return '%d:%d: %s' % (self.line, self.column, self.message) -def get_costemic_problems(buffer, conf): +def get_cosmetic_problems(buffer, conf): rules = conf.enabled_rules() # Split token rules from line rules @@ -193,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): # 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): From 228c47ab770eb9981b64211edff846d053a6ad74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Sat, 25 Feb 2017 22:35:23 +0100 Subject: [PATCH 13/33] fix(indentation): Fix seq indent detection with consistent spaces In the case when the conf is as follows: indentation: spaces: consistent indent-sequences: true and there is no indented block before the first block sequence, and this block sequence is not indented, then the spaces number is computed as zero (while it obviously shouldn't be). This causes such a document to fail on 4th line, instead of 2nd: a: - b c: - d This commit fixes that, and adds corresponding tests. Fixes: #39 --- tests/rules/test_indentation.py | 138 +++++++++++++++++++++++++++++++- yamllint/rules/indentation.py | 14 +++- 2 files changed, 150 insertions(+), 2 deletions(-) diff --git a/tests/rules/test_indentation.py b/tests/rules/test_indentation.py index e435304..2733ea7 100644 --- a/tests/rules/test_indentation.py +++ b/tests/rules/test_indentation.py @@ -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' diff --git a/yamllint/rules/indentation.py b/yamllint/rules/indentation.py index db62f38..432c23c 100644 --- a/yamllint/rules/indentation.py +++ b/yamllint/rules/indentation.py @@ -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: From c037d3e5866f5582ab7a9039cb0b487b0b1173a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Sat, 25 Feb 2017 22:41:42 +0100 Subject: [PATCH 14/33] yamllint version 1.6.1 --- yamllint/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yamllint/__init__.py b/yamllint/__init__.py index 45c28c8..3d01b72 100644 --- a/yamllint/__init__.py +++ b/yamllint/__init__.py @@ -22,7 +22,7 @@ indentation, etc.""" APP_NAME = 'yamllint' -APP_VERSION = '1.6.0' +APP_VERSION = '1.6.1' APP_DESCRIPTION = __doc__ __author__ = u'Adrien Vergé' From 2b26cbc56bca30adeba9a60107487e68f93b96fd Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Tue, 28 Feb 2017 00:22:49 +0100 Subject: [PATCH 15/33] Fix typos --- docs/disable_with_comments.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From f507319419c73b5bcb182dc5eef931562821d47e Mon Sep 17 00:00:00 2001 From: "Daniel M. Capella" Date: Tue, 28 Feb 2017 04:29:08 -0500 Subject: [PATCH 16/33] Doc: Add ALE Vim plugin --- docs/text_editors.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/text_editors.rst b/docs/text_editors.rst index 8909aa9..d9fec46 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``: :: From 4b2b57aa32d99b29458492a68172e066744c590e Mon Sep 17 00:00:00 2001 From: Jim Riggs Date: Fri, 17 Feb 2017 13:58:38 -0600 Subject: [PATCH 17/33] Rules: Add min-spaces-inside-empty and max-spaces-inside-empty Add min-spaces-inside-empty and max-spaces-inside-empty to braces and brackets to allow separate handling for empty and non-empty objects. --- tests/rules/test_braces.py | 195 +++++++++++++++++++++++++++++++++-- tests/rules/test_brackets.py | 195 +++++++++++++++++++++++++++++++++-- yamllint/conf/default.yaml | 4 + yamllint/rules/braces.py | 48 ++++++++- yamllint/rules/brackets.py | 49 ++++++++- 5 files changed, 471 insertions(+), 20 deletions(-) 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/yamllint/conf/default.yaml b/yamllint/conf/default.yaml index 0a22e81..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 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'], From 38d14c7314085439feb0a3d55cbb3d6fa7fab866 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Tue, 21 Mar 2017 16:00:58 +0700 Subject: [PATCH 18/33] Add __main__ Allows execution using python -m yamllint --- yamllint/__main__.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 yamllint/__main__.py 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() From d6a81f1b23b50de7cea35c1964d955f06ea7b28e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Tue, 21 Mar 2017 11:32:07 +0100 Subject: [PATCH 19/33] Add tests for `python -m yamllint` --- tests/test_module.py | 82 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 tests/test_module.py diff --git a/tests/test_module.py b/tests/test_module.py new file mode 100644 index 0000000..70cc42e --- /dev/null +++ b/tests/test_module.py @@ -0,0 +1,82 @@ +# -*- 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 unittest + + +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]) From a825645cbed8d9ff5d5a81928a3c7136ec3f3179 Mon Sep 17 00:00:00 2001 From: Krzysztof Magosa Date: Sat, 25 Mar 2017 18:53:09 +0100 Subject: [PATCH 20/33] Add information about Emacs integration --- docs/text_editors.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/text_editors.rst b/docs/text_editors.rst index d9fec46..da3e96f 100644 --- a/docs/text_editors.rst +++ b/docs/text_editors.rst @@ -27,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 ------------------ From 4ae829c0625ce04bc59e59d7cfd9d85de67c9efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Tue, 25 Apr 2017 17:09:50 +0200 Subject: [PATCH 21/33] yamllint version 1.7.0 --- yamllint/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yamllint/__init__.py b/yamllint/__init__.py index 3d01b72..b510404 100644 --- a/yamllint/__init__.py +++ b/yamllint/__init__.py @@ -22,7 +22,7 @@ indentation, etc.""" APP_NAME = 'yamllint' -APP_VERSION = '1.6.1' +APP_VERSION = '1.7.0' APP_DESCRIPTION = __doc__ __author__ = u'Adrien Vergé' From 30dfa789239f0f6862c5f8297291dd369084eeda Mon Sep 17 00:00:00 2001 From: sedrubal Date: Sun, 28 May 2017 22:59:33 +0200 Subject: [PATCH 22/33] Use argparse mutually_exclusive_group for --config-file and --config-data This does the same as your solution :wink: --- tests/test_cli.py | 7 +++++-- yamllint/cli.py | 17 +++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 8a5bab5..86cc75e 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -145,8 +145,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() diff --git a/yamllint/cli.py b/yamllint/cli.py index 1a22f73..f207b88 100644 --- a/yamllint/cli.py +++ b/yamllint/cli.py @@ -78,11 +78,13 @@ 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') @@ -97,11 +99,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( From 7d638d47b919549bace53c3e3eae823d05ab805d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Wed, 28 Jun 2017 12:09:26 +0200 Subject: [PATCH 23/33] tests(cli): Refactor temp test workspace recreation Make it simpler and re-usable. --- tests/common.py | 20 ++++++++++++ tests/test_cli.py | 79 +++++++++++++++++++---------------------------- 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/tests/common.py b/tests/common.py index ed626d0..b5ba469 100644 --- a/tests/common.py +++ b/tests/common.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os +import tempfile import unittest import yaml @@ -49,3 +51,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/test_cli.py b/tests/test_cli.py index 86cc75e..b1300a7 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -23,62 +23,45 @@ import locale import os import pty import shutil -import tempfile import unittest import sys from yamllint import cli +from tests.common import build_temp_workspace + 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') - - # file with only one warning - with open(os.path.join(self.wd, 'warn.yaml'), 'w') as f: - f.write('key: value\n') - - # .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')) + self.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'), + }) def tearDown(self): shutil.rmtree(self.wd) From 342d7b49dd4dc88e7f24eecf1ad53198fa1545af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Wed, 28 Jun 2017 13:30:12 +0200 Subject: [PATCH 24/33] tests(cli): Create a temp test workspace only once Do not re-create it for every test in the class. --- tests/test_cli.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index b1300a7..ad0749f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -32,8 +32,11 @@ from tests.common import build_temp_workspace class CommandLineTestCase(unittest.TestCase): - def setUp(self): - self.wd = build_temp_workspace({ + @classmethod + def setUpClass(cls): + super(CommandLineTestCase, cls).setUpClass() + + cls.wd = build_temp_workspace({ # .yaml file at root 'a.yaml': '---\n' '- 1 \n' @@ -63,8 +66,11 @@ class CommandLineTestCase(unittest.TestCase): u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'), }) - def tearDown(self): - shutil.rmtree(self.wd) + @classmethod + def tearDownClass(cls): + super(CommandLineTestCase, cls).tearDownClass() + + shutil.rmtree(cls.wd) def test_find_files_recursively(self): self.assertEqual( From df26cc0438f996c03a9b61aca3a8b35166b09a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Tue, 6 Jun 2017 21:52:59 +0200 Subject: [PATCH 25/33] feat(config): Add support to ignore paths on per-rule basis Example of configuration to use this feature: # 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/* Closes #43. --- README.rst | 21 ++++++++ docs/configuration.rst | 54 ++++++++++++++++++++ setup.py | 2 +- tests/test_config.py | 110 +++++++++++++++++++++++++++++++++++++++-- yamllint/cli.py | 3 +- yamllint/config.py | 32 ++++++++++-- yamllint/linter.py | 17 ++++--- 7 files changed, 222 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index 5393adf..1f5dac1 100644 --- a/README.rst +++ b/README.rst @@ -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 44aad82..0815789 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -114,3 +114,57 @@ 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/setup.py b/setup.py index 8a865f4..9c54fcd 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ setup( entry_points={'console_scripts': ['yamllint=yamllint.cli:run']}, package_data={'yamllint': ['conf/*.yaml'], 'tests': ['yaml-1.2-spec-examples/*']}, - install_requires=['pyyaml'], + install_requires=['pathspec', 'pyyaml'], tests_require=['nose'], test_suite='nose.collector', ) diff --git a/tests/test_config.py b/tests/test_config.py index 5f06ef1..d6485cc 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -14,10 +14,20 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +try: + from cStringIO import StringIO +except ImportError: + from io import StringIO +import os +import shutil +import sys import 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 +40,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): @@ -170,7 +180,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' @@ -187,7 +197,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' @@ -207,7 +217,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' @@ -225,7 +235,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): @@ -270,3 +280,93 @@ 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) + + 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/yamllint/cli.py b/yamllint/cli.py index f207b88..418cf35 100644 --- a/yamllint/cli.py +++ b/yamllint/cli.py @@ -126,10 +126,11 @@ def run(argv=None): 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(): 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 012bcbd..c8eff8d 100644 --- a/yamllint/linter.py +++ b/yamllint/linter.py @@ -63,8 +63,8 @@ class LintProblem(object): return '%d:%d: %s' % (self.line, self.column, self.message) -def get_cosmetic_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'] @@ -185,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' @@ -193,7 +193,7 @@ def _run(buffer, conf): # right line syntax_error = get_syntax_error(buffer) - for problem in get_cosmetic_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): @@ -215,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. @@ -223,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') From ae337165293418a33bf763241d1937593b2e42d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Wed, 28 Jun 2017 15:03:24 +0200 Subject: [PATCH 26/33] chore(tests): Also run tests on Python 3.6 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c39f155..b66d3c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - 3.3 - 3.4 - 3.5 + - 3.6 - nightly install: - pip install pyyaml flake8 coveralls From a052cf7dbab1ea5c6327d53cc26a72246e69b7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Wed, 28 Jun 2017 15:18:40 +0200 Subject: [PATCH 27/33] chore(tests): Add flake8-import-order linter plugin --- .travis.yml | 2 +- setup.cfg | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b66d3c1..34201cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: - 3.6 - nightly install: - - pip install pyyaml flake8 coveralls + - pip install pyyaml flake8 flake8-import-order coveralls - pip install . script: - flake8 . diff --git a/setup.cfg b/setup.cfg index 2a9acf1..4d5142d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ [bdist_wheel] universal = 1 + +[flake8] +import-order-style = pep8 From 5060917e408bf95fecc1ec59938b6e6badae4762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Wed, 28 Jun 2017 15:20:24 +0200 Subject: [PATCH 28/33] style(cli): Space import sections --- yamllint/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/yamllint/cli.py b/yamllint/cli.py index 418cf35..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 From f9709bc6e691c80812205fdaa0d6b5521d57baf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Wed, 28 Jun 2017 15:30:39 +0200 Subject: [PATCH 29/33] yamllint version 1.8.0 --- yamllint/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yamllint/__init__.py b/yamllint/__init__.py index b510404..fb1d7b3 100644 --- a/yamllint/__init__.py +++ b/yamllint/__init__.py @@ -22,7 +22,7 @@ indentation, etc.""" APP_NAME = 'yamllint' -APP_VERSION = '1.7.0' +APP_VERSION = '1.8.0' APP_DESCRIPTION = __doc__ __author__ = u'Adrien Vergé' From 7a8cfeed6d0e813b1706c32fb4d5a0149cd8645e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Tue, 4 Jul 2017 20:37:39 +0200 Subject: [PATCH 30/33] chore(deps): Require pathspec >= 0.5.3 This new version adds support for Python 2.6. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9c54fcd..5d841ca 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ setup( entry_points={'console_scripts': ['yamllint=yamllint.cli:run']}, package_data={'yamllint': ['conf/*.yaml'], 'tests': ['yaml-1.2-spec-examples/*']}, - install_requires=['pathspec', 'pyyaml'], + install_requires=['pathspec >=0.5.3', 'pyyaml'], tests_require=['nose'], test_suite='nose.collector', ) From 1a961bd4b0bcd7c208e8e5dbe3f5247f90e285e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Tue, 4 Jul 2017 22:07:32 +0200 Subject: [PATCH 31/33] chore(tests): Also run tests on Python 2.6 --- .travis.yml | 4 +++- setup.py | 3 +-- tests/common.py | 7 ++++++- tests/test_cli.py | 7 ++++++- tests/test_config.py | 7 ++++++- tests/test_linter.py | 8 ++++++-- tests/test_module.py | 8 +++++++- tests/test_parser.py | 7 ++++++- 8 files changed, 41 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 34201cf..bd365c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ --- language: python python: + - 2.6 - 2.7 - 3.3 - 3.4 @@ -9,9 +10,10 @@ python: - nightly install: - pip install pyyaml flake8 flake8-import-order coveralls + - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi - pip install . script: - - flake8 . + - 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: diff --git a/setup.py b/setup.py index 5d841ca..e87980f 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,5 @@ setup( package_data={'yamllint': ['conf/*.yaml'], 'tests': ['yaml-1.2-spec-examples/*']}, install_requires=['pathspec >=0.5.3', 'pyyaml'], - tests_require=['nose'], - test_suite='nose.collector', + test_suite='tests', ) diff --git a/tests/common.py b/tests/common.py index b5ba469..11dd235 100644 --- a/tests/common.py +++ b/tests/common.py @@ -16,7 +16,12 @@ import os import tempfile -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_cli.py b/tests/test_cli.py index ad0749f..bec7078 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -23,14 +23,19 @@ import locale import os import pty import shutil -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): @classmethod def setUpClass(cls): diff --git a/tests/test_config.py b/tests/test_config.py index d6485cc..a23da10 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -21,7 +21,11 @@ except ImportError: import os import shutil import sys -import unittest +try: + assert sys.version_info >= (2, 7) + import unittest +except: + import unittest2 as unittest from yamllint import cli from yamllint import config @@ -334,6 +338,7 @@ class IgnorePathConfigTestCase(unittest.TestCase): 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): 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 index 70cc42e..678f40c 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -18,9 +18,15 @@ import os import shutil import subprocess import tempfile -import unittest +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-') 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 From 3c4013fda1cd26a876a7a671e1ae0fcf58d859d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Tue, 4 Jul 2017 22:19:34 +0200 Subject: [PATCH 32/33] docs(CHANGELOG): Add a changelog Closes #57 --- CHANGELOG.rst | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 CHANGELOG.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..501cc25 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,8 @@ +Changelog +========= + +1.8.0 (2017-06-28) +------------------ + +- Refactor argparse with mutually_exclusive_group +- Add support to ignore paths in configuration From d99bb9fec3cd37923e19c7c9931891ac9d3510cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Tue, 4 Jul 2017 22:22:48 +0200 Subject: [PATCH 33/33] yamllint version 1.8.1 --- CHANGELOG.rst | 7 +++++++ yamllint/__init__.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 501cc25..0b2f9ff 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,13 @@ 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) ------------------ diff --git a/yamllint/__init__.py b/yamllint/__init__.py index fb1d7b3..e53dce1 100644 --- a/yamllint/__init__.py +++ b/yamllint/__init__.py @@ -22,7 +22,7 @@ indentation, etc.""" APP_NAME = 'yamllint' -APP_VERSION = '1.8.0' +APP_VERSION = '1.8.1' APP_DESCRIPTION = __doc__ __author__ = u'Adrien Vergé'