Merge tag 'v1.10.0' into packaging

packaging
Philipp Huebner 7 years ago
commit 2c3097ca78

@ -9,12 +9,15 @@ python:
- 3.6 - 3.6
- nightly - nightly
install: install:
- pip install pyyaml flake8 flake8-import-order coveralls - pip install pyyaml flake8 flake8-import-order coveralls sphinx
- if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi
- pip install . - pip install .
script: script:
- if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then flake8 .; fi - if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then flake8 .; fi
- yamllint --strict $(git ls-files '*.yaml' '*.yml') - yamllint --strict $(git ls-files '*.yaml' '*.yml')
- coverage run --source=yamllint setup.py test - coverage run --source=yamllint setup.py test
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then
python setup.py build_sphinx;
fi
after_success: after_success:
coveralls coveralls

@ -1,6 +1,16 @@
Changelog Changelog
========= =========
1.10.0 (2017-11-05)
-------------------
- Fix colored output on Windows
- Check documentation compilation on continuous integration
- Add a new `empty-values` rule
- Make sure test files are included in dist bundle
- Tests: Use en_US.UTF-8 locale when C.UTF-8 not available
- Tests: Dynamically detect Python executable path
1.9.0 (2017-10-16) 1.9.0 (2017-10-16)
------------------ ------------------

@ -1,3 +1,4 @@
include LICENSE include LICENSE
include README.rst include README.rst
include docs/* include docs/*
include tests/*.py tests/rules/*.py tests/yaml-1.2-spec-examples/*

@ -59,6 +59,11 @@ empty-lines
.. automodule:: yamllint.rules.empty_lines .. automodule:: yamllint.rules.empty_lines
empty-values
------------
.. automodule:: yamllint.rules.empty_values
hyphens hyphens
------- -------

@ -3,3 +3,9 @@ universal = 1
[flake8] [flake8]
import-order-style = pep8 import-order-style = pep8
[build_sphinx]
all-files = 1
source-dir = docs
build-dir = docs/_build
warning-is-error = 1

@ -44,8 +44,7 @@ setup(
packages=find_packages(exclude=['tests', 'tests.*']), packages=find_packages(exclude=['tests', 'tests.*']),
entry_points={'console_scripts': ['yamllint=yamllint.cli:run']}, entry_points={'console_scripts': ['yamllint=yamllint.cli:run']},
package_data={'yamllint': ['conf/*.yaml'], package_data={'yamllint': ['conf/*.yaml']},
'tests': ['yaml-1.2-spec-examples/*']},
install_requires=['pathspec >=0.5.3', 'pyyaml'], install_requires=['pathspec >=0.5.3', 'pyyaml'],
test_suite='tests', test_suite='tests',
) )

@ -20,7 +20,7 @@ import sys
try: try:
assert sys.version_info >= (2, 7) assert sys.version_info >= (2, 7)
import unittest import unittest
except: except AssertionError:
import unittest2 as unittest import unittest2 as unittest
import yaml import yaml

@ -0,0 +1,261 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Greg Dubicki
#
# 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 <http://www.gnu.org/licenses/>.
from tests.common import RuleTestCase
class EmptyValuesTestCase(RuleTestCase):
rule_id = 'empty-values'
def test_disabled(self):
conf = ('empty-values: disable\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'foo:\n', conf)
self.check('---\n'
'foo:\n'
' bar:\n', conf)
self.check('---\n'
'{a:}\n', conf)
self.check('---\n'
'foo: {a:}\n', conf)
self.check('---\n'
'- {a:}\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf)
self.check('---\n'
'{a: {b: , c: {d: 4, e:}}, f:}\n', conf)
def test_in_block_mappings_disabled(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n', conf)
self.check('---\n'
'foo:\n'
'bar: aaa\n', conf)
def test_in_block_mappings_single_line(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'implicitly-null:\n', conf, problem1=(2, 17))
self.check('---\n'
'implicitly-null:with-colons:in-key:\n', conf,
problem1=(2, 36))
self.check('---\n'
'implicitly-null:with-colons:in-key2:\n', conf,
problem1=(2, 37))
def test_in_block_mappings_all_lines(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
'bar:\n'
'foobar:\n', conf, problem1=(2, 5),
problem2=(3, 5), problem3=(4, 8))
def test_in_block_mappings_explicit_end_of_document(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
'...\n', conf, problem1=(2, 5))
def test_in_block_mappings_not_end_of_document(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
'bar:\n'
' aaa\n', conf, problem1=(2, 5))
def test_in_block_mappings_different_level(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
' bar:\n'
'aaa: bbb\n', conf, problem1=(3, 6))
def test_in_block_mappings_empty_flow_mapping(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'foo: {a:}\n', conf)
self.check('---\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf)
def test_in_block_mappings_empty_block_sequence(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
' -\n', conf)
def test_in_block_mappings_not_empty_or_explicit_null(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
' bar:\n'
' aaa\n', conf)
self.check('---\n'
'explicitly-null: null\n', conf)
self.check('---\n'
'explicitly-null:with-colons:in-key: null\n', conf)
self.check('---\n'
'false-null: nulL\n', conf)
self.check('---\n'
'empty-string: \'\'\n', conf)
self.check('---\n'
'nullable-boolean: false\n', conf)
self.check('---\n'
'nullable-int: 0\n', conf)
self.check('---\n'
'First occurrence: &anchor Foo\n'
'Second occurrence: *anchor\n', conf)
def test_in_block_mappings_various_explicit_null(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'null-alias: ~\n', conf)
self.check('---\n'
'null-key1: {?: val}\n', conf)
self.check('---\n'
'null-key2: {? !!null "": val}\n', conf)
def test_in_block_mappings_comments(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n'
'comments: disable\n')
self.check('---\n'
'empty: # comment\n'
'foo:\n'
' bar: # comment\n', conf,
problem1=(2, 7),
problem2=(4, 7))
def test_in_flow_mappings_disabled(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: false}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'{a:}\n', conf)
self.check('---\n'
'foo: {a:}\n', conf)
self.check('---\n'
'- {a:}\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf)
self.check('---\n'
'{a: {b: , c: {d: 4, e:}}, f:}\n', conf)
def test_in_flow_mappings_single_line(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'{a:}\n', conf,
problem=(2, 4))
self.check('---\n'
'foo: {a:}\n', conf,
problem=(2, 9))
self.check('---\n'
'- {a:}\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf,
problem1=(2, 6),
problem2=(3, 6),
problem3=(4, 12),
problem4=(5, 12))
self.check('---\n'
'{a: {b: , c: {d: 4, e:}}, f:}\n', conf,
problem1=(2, 8),
problem2=(2, 23),
problem3=(2, 29))
def test_in_flow_mappings_multi_line(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'foo: {\n'
' a:\n'
'}\n', conf,
problem=(3, 5))
self.check('---\n'
'{\n'
' a: {\n'
' b: ,\n'
' c: {\n'
' d: 4,\n'
' e:\n'
' }\n'
' },\n'
' f:\n'
'}\n', conf,
problem1=(4, 7),
problem2=(7, 9),
problem3=(10, 5))
def test_in_flow_mappings_various_explicit_null(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'{explicit-null: null}\n', conf)
self.check('---\n'
'{null-alias: ~}\n', conf)
self.check('---\n'
'null-key1: {?: val}\n', conf)
self.check('---\n'
'null-key2: {? !!null "": val}\n', conf)
def test_in_flow_mappings_comments(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n'
'comments: disable\n')
self.check('---\n'
'{\n'
' a: {\n'
' b: , # comment\n'
' c: {\n'
' d: 4, # comment\n'
' e: # comment\n'
' }\n'
' },\n'
' f: # comment\n'
'}\n', conf,
problem1=(4, 7),
problem2=(7, 9),
problem3=(10, 5))

@ -27,7 +27,7 @@ import sys
try: try:
assert sys.version_info >= (2, 7) assert sys.version_info >= (2, 7)
import unittest import unittest
except: except AssertionError:
import unittest2 as unittest import unittest2 as unittest
from yamllint import cli from yamllint import cli
@ -299,7 +299,10 @@ class CommandLineTestCase(unittest.TestCase):
# Make sure the default localization conditions on this "system" # Make sure the default localization conditions on this "system"
# support UTF-8 encoding. # support UTF-8 encoding.
loc = locale.getlocale() loc = locale.getlocale()
try:
locale.setlocale(locale.LC_ALL, 'C.UTF-8') locale.setlocale(locale.LC_ALL, 'C.UTF-8')
except locale.Error:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
sys.stdout, sys.stderr = StringIO(), StringIO() sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx: with self.assertRaises(SystemExit) as ctx:

@ -24,7 +24,7 @@ import sys
try: try:
assert sys.version_info >= (2, 7) assert sys.version_info >= (2, 7)
import unittest import unittest
except: except AssertionError:
import unittest2 as unittest import unittest2 as unittest
from yamllint import cli from yamllint import cli

@ -19,7 +19,7 @@ import sys
try: try:
assert sys.version_info >= (2, 7) assert sys.version_info >= (2, 7)
import unittest import unittest
except: except AssertionError:
import unittest2 as unittest import unittest2 as unittest
from yamllint.config import YamlLintConfig from yamllint.config import YamlLintConfig

@ -22,10 +22,13 @@ import sys
try: try:
assert sys.version_info >= (2, 7) assert sys.version_info >= (2, 7)
import unittest import unittest
except: except AssertionError:
import unittest2 as unittest import unittest2 as unittest
PYTHON = sys.executable or 'python'
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported') @unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
class ModuleTestCase(unittest.TestCase): class ModuleTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
@ -46,7 +49,7 @@ class ModuleTestCase(unittest.TestCase):
def test_run_module_no_args(self): def test_run_module_no_args(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx: with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output(['python', '-m', 'yamllint'], subprocess.check_output([PYTHON, '-m', 'yamllint'],
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
self.assertEqual(ctx.exception.returncode, 2) self.assertEqual(ctx.exception.returncode, 2)
self.assertRegexpMatches(ctx.exception.output.decode(), self.assertRegexpMatches(ctx.exception.output.decode(),
@ -54,7 +57,7 @@ class ModuleTestCase(unittest.TestCase):
def test_run_module_on_bad_dir(self): def test_run_module_on_bad_dir(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx: with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output(['python', '-m', 'yamllint', subprocess.check_output([PYTHON, '-m', 'yamllint',
'/does/not/exist'], '/does/not/exist'],
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
self.assertRegexpMatches(ctx.exception.output.decode(), self.assertRegexpMatches(ctx.exception.output.decode(),
@ -62,7 +65,7 @@ class ModuleTestCase(unittest.TestCase):
def test_run_module_on_file(self): def test_run_module_on_file(self):
out = subprocess.check_output( out = subprocess.check_output(
['python', '-m', 'yamllint', os.path.join(self.wd, 'warn.yaml')]) [PYTHON, '-m', 'yamllint', os.path.join(self.wd, 'warn.yaml')])
lines = out.decode().splitlines() lines = out.decode().splitlines()
self.assertIn('/warn.yaml', lines[0]) self.assertIn('/warn.yaml', lines[0])
self.assertEqual('\n'.join(lines[1:]), self.assertEqual('\n'.join(lines[1:]),
@ -71,7 +74,7 @@ class ModuleTestCase(unittest.TestCase):
def test_run_module_on_dir(self): def test_run_module_on_dir(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx: with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output(['python', '-m', 'yamllint', self.wd]) subprocess.check_output([PYTHON, '-m', 'yamllint', self.wd])
self.assertEqual(ctx.exception.returncode, 1) self.assertEqual(ctx.exception.returncode, 1)
files = ctx.exception.output.decode().split('\n\n') files = ctx.exception.output.decode().split('\n\n')

@ -18,7 +18,7 @@ import sys
try: try:
assert sys.version_info >= (2, 7) assert sys.version_info >= (2, 7)
import unittest import unittest
except: except AssertionError:
import unittest2 as unittest import unittest2 as unittest
import yaml import yaml

@ -22,7 +22,7 @@ indentation, etc."""
APP_NAME = 'yamllint' APP_NAME = 'yamllint'
APP_VERSION = '1.9.0' APP_VERSION = '1.10.0'
APP_DESCRIPTION = __doc__ APP_DESCRIPTION = __doc__
__author__ = u'Adrien Vergé' __author__ = u'Adrien Vergé'

@ -16,9 +16,9 @@
from __future__ import print_function from __future__ import print_function
import os.path import os
import sys import sys
import platform
import argparse import argparse
from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION
@ -38,6 +38,15 @@ def find_files_recursively(items):
yield item yield item
def supports_color():
supported_platform = not (platform.system() == 'Windows' and not
('ANSICON' in os.environ or
('TERM' in os.environ and
os.environ['TERM'] == 'ANSI')))
return (supported_platform and
hasattr(sys.stdout, 'isatty') and sys.stdout.isatty())
class Format(object): class Format(object):
@staticmethod @staticmethod
def parsable(problem, filename): def parsable(problem, filename):
@ -134,7 +143,7 @@ def run(argv=None):
for problem in linter.run(f, conf, filepath): for problem in linter.run(f, conf, filepath):
if args.format == 'parsable': if args.format == 'parsable':
print(Format.parsable(problem, file)) print(Format.parsable(problem, file))
elif sys.stdout.isatty(): elif supports_color():
if first: if first:
print('\033[4m%s\033[0m' % file) print('\033[4m%s\033[0m' % file)
first = False first = False

@ -32,6 +32,9 @@ rules:
max: 2 max: 2
max-start: 0 max-start: 0
max-end: 0 max-end: 0
empty-values:
forbid-in-block-mappings: false
forbid-in-flow-mappings: false
hyphens: hyphens:
max-spaces-after: 1 max-spaces-after: 1
indentation: indentation:

@ -24,6 +24,7 @@ from yamllint.rules import (
document_end, document_end,
document_start, document_start,
empty_lines, empty_lines,
empty_values,
hyphens, hyphens,
indentation, indentation,
key_duplicates, key_duplicates,
@ -45,6 +46,7 @@ _RULES = {
document_end.ID: document_end, document_end.ID: document_end,
document_start.ID: document_start, document_start.ID: document_start,
empty_lines.ID: empty_lines, empty_lines.ID: empty_lines,
empty_values.ID: empty_values,
hyphens.ID: hyphens, hyphens.ID: hyphens,
indentation.ID: indentation, indentation.ID: indentation,
key_duplicates.ID: key_duplicates, key_duplicates.ID: key_duplicates,

@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Greg Dubicki
#
# 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 <http://www.gnu.org/licenses/>.
"""
Use this rule to prevent nodes with empty content, that implicitly result in
``null`` values.
.. rubric:: Options
* Use ``forbid-in-block-mappings`` to prevent empty values in block mappings.
* Use ``forbid-in-flow-mappings`` to prevent empty values in flow mappings.
.. rubric:: Examples
#. With ``empty-values: {forbid-in-block-mappings: true}``
the following code snippets would **PASS**:
::
some-mapping:
sub-element: correctly indented
::
explicitly-null: null
the following code snippets would **FAIL**:
::
some-mapping:
sub-element: incorrectly indented
::
implicitly-null:
#. With ``empty-values: {forbid-in-flow-mappings: true}``
the following code snippet would **PASS**:
::
{prop: null}
{a: 1, b: 2, c: 3}
the following code snippets would **FAIL**:
::
{prop: }
::
{a: 1, b:, c: 3}
"""
import yaml
from yamllint.linter import LintProblem
ID = 'empty-values'
TYPE = 'token'
CONF = {'forbid-in-block-mappings': bool,
'forbid-in-flow-mappings': bool}
def check(conf, token, prev, next, nextnext, context):
if conf['forbid-in-block-mappings']:
if isinstance(token, yaml.ValueToken) and isinstance(next, (
yaml.KeyToken, yaml.BlockEndToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'empty value in block mapping')
if conf['forbid-in-flow-mappings']:
if isinstance(token, yaml.ValueToken) and isinstance(next, (
yaml.FlowEntryToken, yaml.FlowMappingEndToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'empty value in flow mapping')
Loading…
Cancel
Save