Compare commits

..

8 Commits

Author SHA1 Message Date
Adrien Vergé
318a12bbe6 yamllint version 1.13.0 2018-11-14 19:04:58 +01:00
Adrien Vergé
66adaee66c docs: Add documentation on the new -f colored option 2018-11-14 19:02:52 +01:00
sedrubal
5062b91cac cli: Add -f colored to force colors
`-f standard` shows non-colored output,
`-f colored` shows colored output,
`-f auto` is the new default, it chooses `standard` or `colored`
depending on terminal capabilities.
2018-10-22 10:35:35 +02:00
sedrubal
3ef85739e3 Use isinstance(x, y) instead of type(x) == y
Fixes pylint C0123.
2018-10-20 10:02:16 +02:00
Adrien Vergé
dc4a9f4fff yamllint version 1.12.1 2018-10-17 10:22:32 +02:00
Adrien Vergé
8354d50016 quoted-strings: Fix broken rule
Original implementation was completely broken. Documentation and actual
behavior were different. Numbers and booleans were detected as wrong, as
well as explicit types.

Fixes #136 and #130.
2018-10-17 10:18:25 +02:00
Adrien Vergé
524d721f0d Update .gitignore to exclude build/ 2018-10-11 15:35:26 +02:00
Adrien Vergé
e864f57d37 docs: Fix missing quoted-strings module in documentation 2018-10-11 15:27:58 +02:00
14 changed files with 157 additions and 72 deletions

1
.gitignore vendored
View File

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

View File

@@ -1,6 +1,19 @@
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) 1.12.0 (2018-10-04)
------------------- -------------------

View File

@@ -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).

View File

@@ -68,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:

View File

@@ -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
--------------- ---------------

View File

@@ -34,19 +34,19 @@ class QuotedTestCase(RuleTestCase):
def test_quote_type_any(self): def test_quote_type_any(self):
conf = 'quoted-strings: {quote-type: any}\n' conf = 'quoted-strings: {quote-type: any}\n'
self.check('---\n' self.check('---\n'
'string1: "foo"\n' 'boolean1: true\n'
'number1: 123\n' # fails 'number1: 123\n'
'string2: foo\n' # fails 'string1: foo\n' # fails
'string2: "foo"\n'
'string3: \'bar\'\n' 'string3: \'bar\'\n'
'string4: !!str genericstring\n' # fails 'string4: !!str genericstring\n'
'string5: !!str 456\n' # fails 'string5: !!str 456\n'
'string6: !!str "quotedgenericstring"\n' 'string6: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n' 'binary: !!binary binstring\n'
'integer: !!int intstring\n' 'integer: !!int intstring\n'
'boolean1: !!bool boolstring\n' 'boolean2: !!bool boolstring\n'
'boolean2: !!bool "quotedboolstring"\n', 'boolean3: !!bool "quotedboolstring"\n',
conf, problem1=(3, 10), problem2=(4, 10), conf, problem=(4, 10))
problem3=(6, 16), problem4=(7, 16))
self.check('---\n' self.check('---\n'
'multiline string 1: |\n' 'multiline string 1: |\n'
' line 1\n' ' line 1\n'
@@ -65,19 +65,19 @@ class QuotedTestCase(RuleTestCase):
def test_quote_type_single(self): def test_quote_type_single(self):
conf = 'quoted-strings: {quote-type: single}\n' conf = 'quoted-strings: {quote-type: single}\n'
self.check('---\n' self.check('---\n'
'string1: "foo"\n' # fails 'boolean1: true\n'
'number1: 123\n' # fails 'number1: 123\n'
'string2: foo\n' # fails 'string1: foo\n' # fails
'string2: "foo"\n' # fails
'string3: \'bar\'\n' 'string3: \'bar\'\n'
'string4: !!str genericstring\n' # fails 'string4: !!str genericstring\n'
'string5: !!str 456\n' # fails 'string5: !!str 456\n'
'string6: !!str "quotedgenericstring"\n' # fails 'string6: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n' 'binary: !!binary binstring\n'
'integer: !!int intstring\n' 'integer: !!int intstring\n'
'boolean1: !!bool boolstring\n' 'boolean2: !!bool boolstring\n'
'boolean2: !!bool "quotedboolstring"\n', 'boolean3: !!bool "quotedboolstring"\n',
conf, problem1=(2, 10), problem2=(3, 10), problem3=(4, 10), conf, problem1=(4, 10), problem2=(5, 10))
problem4=(6, 16), problem5=(7, 16), problem6=(8, 16))
self.check('---\n' self.check('---\n'
'multiline string 1: |\n' 'multiline string 1: |\n'
' line 1\n' ' line 1\n'
@@ -96,19 +96,19 @@ class QuotedTestCase(RuleTestCase):
def test_quote_type_double(self): def test_quote_type_double(self):
conf = 'quoted-strings: {quote-type: double}\n' conf = 'quoted-strings: {quote-type: double}\n'
self.check('---\n' self.check('---\n'
'string1: "foo"\n' 'boolean1: true\n'
'number1: 123\n' # fails 'number1: 123\n'
'string2: foo\n' # fails 'string1: foo\n' # fails
'string2: "foo"\n'
'string3: \'bar\'\n' # fails 'string3: \'bar\'\n' # fails
'string4: !!str genericstring\n' # fails 'string4: !!str genericstring\n'
'string5: !!str 456\n' # fails 'string5: !!str 456\n'
'string6: !!str "quotedgenericstring"\n' 'string6: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n' 'binary: !!binary binstring\n'
'integer: !!int intstring\n' 'integer: !!int intstring\n'
'boolean1: !!bool boolstring\n' 'boolean2: !!bool boolstring\n'
'boolean2: !!bool "quotedboolstring"\n', 'boolean3: !!bool "quotedboolstring"\n',
conf, problem1=(3, 10), problem2=(4, 10), problem3=(5, 10), conf, problem1=(4, 10), problem2=(6, 10))
problem4=(6, 16), problem5=(7, 16))
self.check('---\n' self.check('---\n'
'multiline string 1: |\n' 'multiline string 1: |\n'
' line 1\n' ' line 1\n'

View File

@@ -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, '')

View File

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

View File

@@ -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

View File

@@ -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__))

View File

@@ -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

View File

@@ -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 "..."')

View File

@@ -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']

View File

@@ -16,8 +16,8 @@
""" """
Use this rule to forbid any string values that are not quoted. Use this rule to forbid any string values that are not quoted.
You can also enforce the type of the quote used - single or double - using the You can also enforce the type of the quote used using the ``quote-type`` option
``quote-type`` option. (``single``, ``double`` or ``any``).
**Note**: Multi-line strings (with ``|`` or ``>``) will not be checked. **Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
@@ -51,17 +51,21 @@ CONF = {'quote-type': ('any', 'single', 'double')}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):
quote_type = conf['quote-type'] quote_type = conf['quote-type']
if prev and isinstance(prev, yaml.tokens.TagToken): if (isinstance(token, yaml.tokens.ScalarToken) and
if prev.value[1] != "str": isinstance(prev, (yaml.ValueToken, yaml.TagToken))):
# we ignore generic strings, e.g. somestring: !!str testtest # Ignore explicit types, e.g. !!str testtest or !!int 42
if (prev and isinstance(prev, yaml.tokens.TagToken) and
prev.value[0] == '!!'):
return return
if isinstance(token, yaml.tokens.ScalarToken): # Ignore numbers, booleans, etc.
if isinstance(prev, yaml.tokens.ValueToken) or \ resolver = yaml.resolver.Resolver()
isinstance(prev, yaml.tokens.TagToken): if resolver.resolve(yaml.nodes.ScalarNode, token.value,
if ((not token.plain) and (True, False)) != 'tag:yaml.org,2002:str':
((token.style == "|") or (token.style == ">"))): return
# we ignore multi-line strings
# Ignore multi-line strings
if (not token.plain) and (token.style == "|" or token.style == ">"):
return return
if ((quote_type == 'single' and token.style != "'") or if ((quote_type == 'single' and token.style != "'") or
@@ -70,5 +74,4 @@ def check(conf, token, prev, next, nextnext, context):
yield LintProblem( yield LintProblem(
token.start_mark.line + 1, token.start_mark.line + 1,
token.start_mark.column + 1, token.start_mark.column + 1,
"string value is not quoted with %s quotes" % (quote_type) "string value is not quoted with %s quotes" % (quote_type))
)