Merge tag 'v1.13.0' into packaging

packaging
Philipp Huebner 6 years ago
commit becf9fe98b

1
.gitignore vendored

@ -3,3 +3,4 @@ __pycache__
/docs/_build /docs/_build
/dist /dist
/yamllint.egg-info /yamllint.egg-info
/build

@ -9,14 +9,19 @@ python:
- 3.6 - 3.6
- nightly - nightly
install: install:
- pip install pyyaml flake8 flake8-import-order coveralls - pip install pyyaml flake8 flake8-import-order
- if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi
- if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then pip install coveralls; fi
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then pip install sphinx; fi - if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then pip install sphinx; 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 - if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then
coverage run --source=yamllint setup.py test;
else
python setup.py test;
fi
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then - if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then
python setup.py build_sphinx; python setup.py build_sphinx;
fi fi

@ -1,6 +1,25 @@
Changelog Changelog
========= =========
1.13.0 (2018-11-14)
-------------------
- Use `isinstance(x, y)` instead of `type(x) == y`
- Add a new `-f colored` option
- Update documentation about colored output when run from CLI
1.12.1 (2018-10-17)
-------------------
- Fix the `quoted-strings` rule, broken implementation
- Fix missing documentation for the `quoted-strings` rule
1.12.0 (2018-10-04)
-------------------
- Add a new `quoted-strings` rule
- Update installation documentation for pip, CentOS, Debian, Ubuntu, Mac OS
1.11.1 (2018-04-06) 1.11.1 (2018-04-06)
------------------- -------------------

@ -38,7 +38,8 @@ Screenshot
Installation Installation
^^^^^^^^^^^^ ^^^^^^^^^^^^
On Fedora / CentOS: On Fedora / CentOS (note: `EPEL <https://fedoraproject.org/wiki/EPEL>`_ is
required on CentOS):
.. code:: bash .. code:: bash
@ -50,11 +51,17 @@ On Debian 8+ / Ubuntu 16.04+:
sudo apt-get install yamllint sudo apt-get install yamllint
On Mac OS 10.11+:
.. code:: bash
brew install yamllint
Alternatively using pip, the Python package manager: Alternatively using pip, the Python package manager:
.. code:: bash .. code:: bash
sudo pip install yamllint pip install --user yamllint
Usage Usage
^^^^^ ^^^^^

@ -102,8 +102,8 @@ Errors and warnings
------------------- -------------------
Problems detected by yamllint can be raised either as errors or as 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`` The CLI will output them (with different colors when using the ``colored``
output format). output format, or ``auto`` when run from a terminal).
By default the script will exit with 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). more error(s).

@ -16,25 +16,24 @@ On Debian 8+ / Ubuntu 16.04+:
sudo apt-get install yamllint sudo apt-get install yamllint
On older Debian / Ubuntu versions: On Mac OS 10.11+:
.. code:: bash .. code:: bash
sudo add-apt-repository -y ppa:adrienverge/ppa && sudo apt-get update brew install yamllint
sudo apt-get install yamllint
Alternatively using pip, the Python package manager: Alternatively using pip, the Python package manager:
.. code:: bash .. code:: bash
sudo pip install yamllint pip install --user yamllint
If you prefer installing from source, you can run, from the source directory: If you prefer installing from source, you can run, from the source directory:
.. code:: bash .. code:: bash
python setup.py sdist python setup.py sdist
sudo pip install dist/yamllint-*.tar.gz pip install --user dist/yamllint-*.tar.gz
Running yamllint Running yamllint
---------------- ----------------
@ -69,6 +68,10 @@ The output will look like (colors are not displayed here):
10:1 error too many blank lines (4 > 2) (empty-lines) 10:1 error too many blank lines (4 > 2) (empty-lines)
11:4 error too many spaces inside braces (braces) 11:4 error too many spaces inside braces (braces)
By default, the output of yamllint is colored when run from a terminal, and pure
text in other cases. Add the ``-f standard`` arguments to force non-colored output.
Use the ``-f colored`` arguments to force colored output.
Add the ``-f parsable`` arguments if you need an output format parsable by a Add the ``-f parsable`` arguments if you need an output format parsable by a
machine (for instance for :doc:`syntax highlighting in text editors machine (for instance for :doc:`syntax highlighting in text editors
<text_editors>`). The output will then look like: <text_editors>`). The output will then look like:

@ -104,6 +104,11 @@ octal-values
.. automodule:: yamllint.rules.octal_values .. automodule:: yamllint.rules.octal_values
quoted-strings
--------------
.. automodule:: yamllint.rules.quoted_strings
trailing-spaces trailing-spaces
--------------- ---------------

@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 ClearScore
#
# 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 QuotedTestCase(RuleTestCase):
rule_id = 'quoted-strings'
def test_disabled(self):
conf = 'quoted-strings: disable'
self.check('---\n'
'foo: bar\n', conf)
self.check('---\n'
'foo: "bar"\n', conf)
self.check('---\n'
'foo: \'bar\'\n', conf)
self.check('---\n'
'bar: 123\n', conf)
def test_quote_type_any(self):
conf = 'quoted-strings: {quote-type: any}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n' # fails
'string2: "foo"\n'
'string3: \'bar\'\n'
'string4: !!str genericstring\n'
'string5: !!str 456\n'
'string6: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n',
conf, problem=(4, 10))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf, problem1=(9, 3))
def test_quote_type_single(self):
conf = 'quoted-strings: {quote-type: single}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n' # fails
'string2: "foo"\n' # fails
'string3: \'bar\'\n'
'string4: !!str genericstring\n'
'string5: !!str 456\n'
'string6: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n',
conf, problem1=(4, 10), problem2=(5, 10))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf, problem1=(9, 3), problem2=(12, 3))
def test_quote_type_double(self):
conf = 'quoted-strings: {quote-type: double}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n' # fails
'string2: "foo"\n'
'string3: \'bar\'\n' # fails
'string4: !!str genericstring\n'
'string5: !!str 456\n'
'string6: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n',
conf, problem1=(4, 10), problem2=(6, 10))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf, problem1=(9, 3))

@ -351,7 +351,7 @@ class CommandLineTestCase(unittest.TestCase):
'\n' % file)) '\n' % file))
self.assertEqual(err, '') self.assertEqual(err, '')
def test_run_colored_output(self): def test_run_default_format_output_in_tty(self):
file = os.path.join(self.wd, 'a.yaml') file = os.path.join(self.wd, 'a.yaml')
# Create a pseudo-TTY and redirect stdout to it # Create a pseudo-TTY and redirect stdout to it
@ -383,3 +383,59 @@ class CommandLineTestCase(unittest.TestCase):
'no new line character at the end of file ' 'no new line character at the end of file '
'\033[2m(new-line-at-end-of-file)\033[0m\n' '\033[2m(new-line-at-end-of-file)\033[0m\n'
'\n' % file)) '\n' % file))
def test_run_default_format_output_without_tty(self):
file = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run((file, ))
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'%s\n'
' 2:4 error trailing spaces (trailing-spaces)\n'
' 3:4 error no new line character at the end of file '
'(new-line-at-end-of-file)\n'
'\n' % file))
self.assertEqual(err, '')
def test_run_auto_output_without_tty_output(self):
file = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run((file, '--format', 'auto'))
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'%s\n'
' 2:4 error trailing spaces (trailing-spaces)\n'
' 3:4 error no new line character at the end of file '
'(new-line-at-end-of-file)\n'
'\n' % file))
self.assertEqual(err, '')
def test_run_format_colored(self):
file = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run((file, '--format', 'colored'))
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'\033[4m%s\033[0m\n'
' \033[2m2:4\033[0m \033[31merror\033[0m '
'trailing spaces \033[2m(trailing-spaces)\033[0m\n'
' \033[2m3:4\033[0m \033[31merror\033[0m '
'no new line character at the end of file '
'\033[2m(new-line-at-end-of-file)\033[0m\n'
'\n' % file))
self.assertEqual(err, '')

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

@ -96,8 +96,8 @@ def run(argv=None):
action='store', action='store',
help='custom configuration (as YAML source)') help='custom configuration (as YAML source)')
parser.add_argument('-f', '--format', parser.add_argument('-f', '--format',
choices=('parsable', 'standard'), default='standard', choices=('parsable', 'standard', 'colored', 'auto'),
help='format for parsing output') default='auto', help='format for parsing output')
parser.add_argument('-s', '--strict', parser.add_argument('-s', '--strict',
action='store_true', action='store_true',
help='return non-zero exit code on warnings ' help='return non-zero exit code on warnings '
@ -143,7 +143,8 @@ 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 supports_color(): elif args.format == 'colored' or \
(args.format == 'auto' and 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,7 @@ rules:
max: 2 max: 2
max-start: 0 max-start: 0
max-end: 0 max-end: 0
quoted-strings: disable
empty-values: empty-values:
forbid-in-block-mappings: false forbid-in-block-mappings: false
forbid-in-flow-mappings: false forbid-in-flow-mappings: false

@ -52,7 +52,7 @@ class YamlLintConfig(object):
assert isinstance(base_config, YamlLintConfig) assert isinstance(base_config, YamlLintConfig)
for rule in self.rules: for rule in self.rules:
if (type(self.rules[rule]) == dict and if (isinstance(self.rules[rule], dict) and
rule in base_config.rules and rule in base_config.rules and
base_config.rules[rule] is not False): base_config.rules[rule] is not False):
base_config.rules[rule].update(self.rules[rule]) base_config.rules[rule].update(self.rules[rule])
@ -70,7 +70,7 @@ class YamlLintConfig(object):
except Exception as e: except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e) raise YamlLintConfigError('invalid config: %s' % e)
if type(conf) != dict: if not isinstance(conf, dict):
raise YamlLintConfigError('invalid config: not a dict') raise YamlLintConfigError('invalid config: not a dict')
self.rules = conf.get('rules', {}) self.rules = conf.get('rules', {})
@ -85,7 +85,7 @@ class YamlLintConfig(object):
raise YamlLintConfigError('invalid config: %s' % e) raise YamlLintConfigError('invalid config: %s' % e)
if 'ignore' in conf: if 'ignore' in conf:
if type(conf['ignore']) != str: if not isinstance(conf['ignore'], str):
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: ignore should contain file patterns') 'invalid config: ignore should contain file patterns')
self.ignore = pathspec.PathSpec.from_lines( self.ignore = pathspec.PathSpec.from_lines(
@ -107,10 +107,10 @@ def validate_rule_conf(rule, conf):
elif conf == 'enable': elif conf == 'enable':
conf = {} conf = {}
if type(conf) == dict: if isinstance(conf, dict):
if ('ignore' in conf and if ('ignore' in conf and
type(conf['ignore']) != pathspec.pathspec.PathSpec): not isinstance(conf['ignore'], pathspec.pathspec.PathSpec)):
if type(conf['ignore']) != str: if not isinstance(conf['ignore'], str):
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: ignore should contain file patterns') 'invalid config: ignore should contain file patterns')
conf['ignore'] = pathspec.PathSpec.from_lines( conf['ignore'] = pathspec.PathSpec.from_lines(
@ -130,14 +130,14 @@ def validate_rule_conf(rule, conf):
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: unknown option "%s" for rule "%s"' % 'invalid config: unknown option "%s" for rule "%s"' %
(optkey, rule.ID)) (optkey, rule.ID))
if type(options[optkey]) == tuple: if isinstance(options[optkey], tuple):
if (conf[optkey] not in options[optkey] and if (conf[optkey] not in options[optkey] and
type(conf[optkey]) not in options[optkey]): type(conf[optkey]) not in options[optkey]):
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be in %s' 'invalid config: option "%s" of "%s" should be in %s'
% (optkey, rule.ID, options[optkey])) % (optkey, rule.ID, options[optkey]))
else: else:
if type(conf[optkey]) != options[optkey]: if not isinstance(conf[optkey], options[optkey]):
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be %s' 'invalid config: option "%s" of "%s" should be %s'
% (optkey, rule.ID, options[optkey].__name__)) % (optkey, rule.ID, options[optkey].__name__))

@ -226,7 +226,7 @@ def run(input, conf, filepath=None):
if conf.is_file_ignored(filepath): if conf.is_file_ignored(filepath):
return () return ()
if type(input) in (type(b''), type(u'')): # compat with Python 2 & 3 if isinstance(input, (type(b''), type(u''))): # compat with Python 2 & 3
return _run(input, conf, filepath) return _run(input, conf, filepath)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
# We need to have everything in memory to parse correctly # We need to have everything in memory to parse correctly

@ -33,6 +33,7 @@ from yamllint.rules import (
new_line_at_end_of_file, new_line_at_end_of_file,
new_lines, new_lines,
octal_values, octal_values,
quoted_strings,
trailing_spaces, trailing_spaces,
truthy, truthy,
) )
@ -56,6 +57,7 @@ _RULES = {
new_line_at_end_of_file.ID: new_line_at_end_of_file, new_line_at_end_of_file.ID: new_line_at_end_of_file,
new_lines.ID: new_lines, new_lines.ID: new_lines,
octal_values.ID: octal_values, octal_values.ID: octal_values,
quoted_strings.ID: quoted_strings,
trailing_spaces.ID: trailing_spaces, trailing_spaces.ID: trailing_spaces,
truthy.ID: truthy, truthy.ID: truthy,
} }

@ -86,14 +86,16 @@ CONF = {'present': bool}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):
if conf['present']: if conf['present']:
if (isinstance(token, yaml.StreamEndToken) and is_stream_end = isinstance(token, yaml.StreamEndToken)
not (isinstance(prev, yaml.DocumentEndToken) or is_start = isinstance(token, yaml.DocumentStartToken)
isinstance(prev, yaml.StreamStartToken))): prev_is_end_or_stream_start = isinstance(
prev, (yaml.DocumentEndToken, yaml.StreamStartToken)
)
if is_stream_end and not prev_is_end_or_stream_start:
yield LintProblem(token.start_mark.line, 1, yield LintProblem(token.start_mark.line, 1,
'missing document end "..."') 'missing document end "..."')
elif (isinstance(token, yaml.DocumentStartToken) and elif is_start and not prev_is_end_or_stream_start:
not (isinstance(prev, yaml.DocumentEndToken) or
isinstance(prev, yaml.StreamStartToken))):
yield LintProblem(token.start_mark.line + 1, 1, yield LintProblem(token.start_mark.line + 1, 1,
'missing document end "..."') 'missing document end "..."')

@ -224,7 +224,7 @@ def check_scalar_indentation(conf, token, context):
def compute_expected_indent(found_indent): def compute_expected_indent(found_indent):
def detect_indent(base_indent): def detect_indent(base_indent):
if type(context['spaces']) is not int: if not isinstance(context['spaces'], int):
context['spaces'] = found_indent - base_indent context['spaces'] = found_indent - base_indent
return base_indent + context['spaces'] return base_indent + context['spaces']
@ -312,7 +312,7 @@ def _check(conf, token, prev, next, nextnext, context):
token.start_mark.line + 1 > context['cur_line']) token.start_mark.line + 1 > context['cur_line'])
def detect_indent(base_indent, next): def detect_indent(base_indent, next):
if type(context['spaces']) is not int: if not isinstance(context['spaces'], int):
context['spaces'] = next.start_mark.column - base_indent context['spaces'] = next.start_mark.column - base_indent
return base_indent + context['spaces'] return base_indent + context['spaces']

@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 ClearScore
#
# 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 forbid any string values that are not quoted.
You can also enforce the type of the quote used using the ``quote-type`` option
(``single``, ``double`` or ``any``).
**Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
.. rubric:: Examples
#. With ``quoted-strings: {quote-type: any}``
the following code snippet would **PASS**:
::
foo: "bar"
bar: 'foo'
number: 123
boolean: true
the following code snippet would **FAIL**:
::
foo: bar
"""
import yaml
from yamllint.linter import LintProblem
ID = 'quoted-strings'
TYPE = 'token'
CONF = {'quote-type': ('any', 'single', 'double')}
def check(conf, token, prev, next, nextnext, context):
quote_type = conf['quote-type']
if (isinstance(token, yaml.tokens.ScalarToken) and
isinstance(prev, (yaml.ValueToken, yaml.TagToken))):
# Ignore explicit types, e.g. !!str testtest or !!int 42
if (prev and isinstance(prev, yaml.tokens.TagToken) and
prev.value[0] == '!!'):
return
# Ignore numbers, booleans, etc.
resolver = yaml.resolver.Resolver()
if resolver.resolve(yaml.nodes.ScalarNode, token.value,
(True, False)) != 'tag:yaml.org,2002:str':
return
# Ignore multi-line strings
if (not token.plain) and (token.style == "|" or token.style == ">"):
return
if ((quote_type == 'single' and token.style != "'") or
(quote_type == 'double' and token.style != '"') or
(quote_type == 'any' and token.style is None)):
yield LintProblem(
token.start_mark.line + 1,
token.start_mark.column + 1,
"string value is not quoted with %s quotes" % (quote_type))
Loading…
Cancel
Save