Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60b72daad4 | ||
|
|
773bb8a648 | ||
|
|
d3cd8ba332 | ||
|
|
e56a7c788c | ||
|
|
d017631aff | ||
|
|
5b98cd2053 | ||
|
|
82dd7dbf16 | ||
|
|
4533b8ae49 | ||
|
|
a2c68fdf9b | ||
|
|
82ed191bc9 | ||
|
|
92ff315fb4 | ||
|
|
f4cebdc054 | ||
|
|
d174f9e3e3 | ||
|
|
c8ba8f7e99 |
@@ -31,7 +31,10 @@ Unless told otherwise, yamllint uses its ``default`` configuration:
|
||||
Details on rules can be found on :doc:`the rules page <rules>`.
|
||||
|
||||
There is another pre-defined configuration named ``relaxed``. As its name
|
||||
suggests, it is more tolerant.
|
||||
suggests, it is more tolerant:
|
||||
|
||||
.. literalinclude:: ../yamllint/conf/relaxed.yaml
|
||||
:language: yaml
|
||||
|
||||
It can be chosen using:
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import locale
|
||||
|
||||
|
||||
locale.setlocale(locale.LC_ALL, 'C')
|
||||
|
||||
@@ -35,6 +35,10 @@ class CommentsTestCase(RuleTestCase):
|
||||
' #comment 3 bis\n'
|
||||
' # comment 3 ter\n'
|
||||
'\n'
|
||||
'################################\n'
|
||||
'## comment 4\n'
|
||||
'##comment 5\n'
|
||||
'\n'
|
||||
'string: "Une longue phrase." # this is French\n', conf)
|
||||
|
||||
def test_starting_space(self):
|
||||
@@ -52,7 +56,11 @@ class CommentsTestCase(RuleTestCase):
|
||||
'# comment 2\n'
|
||||
'# comment 3\n'
|
||||
' # comment 3 bis\n'
|
||||
' # comment 3 ter\n', conf)
|
||||
' # comment 3 ter\n'
|
||||
'\n'
|
||||
'################################\n'
|
||||
'## comment 4\n'
|
||||
'## comment 5\n', conf)
|
||||
self.check('---\n'
|
||||
'#comment\n'
|
||||
'\n'
|
||||
@@ -63,9 +71,14 @@ class CommentsTestCase(RuleTestCase):
|
||||
'# comment 2\n'
|
||||
'#comment 3\n'
|
||||
' #comment 3 bis\n'
|
||||
' # comment 3 ter\n', conf,
|
||||
' # comment 3 ter\n'
|
||||
'\n'
|
||||
'################################\n'
|
||||
'## comment 4\n'
|
||||
'##comment 5\n', conf,
|
||||
problem1=(2, 2), problem2=(6, 13),
|
||||
problem4=(9, 2), problem5=(10, 4))
|
||||
problem3=(9, 2), problem4=(10, 4),
|
||||
problem5=(15, 3))
|
||||
|
||||
def test_spaces_from_content(self):
|
||||
conf = ('comments:\n'
|
||||
@@ -106,13 +119,18 @@ class CommentsTestCase(RuleTestCase):
|
||||
' #comment 3 bis\n'
|
||||
' # comment 3 ter\n'
|
||||
'\n'
|
||||
'################################\n'
|
||||
'## comment 4\n'
|
||||
'##comment 5\n'
|
||||
'\n'
|
||||
'string: "Une longue phrase." # this is French\n', conf,
|
||||
problem1=(2, 2),
|
||||
problem2=(4, 7),
|
||||
problem3=(6, 11), problem4=(6, 12),
|
||||
problem5=(9, 2),
|
||||
problem6=(10, 4),
|
||||
problem7=(13, 30))
|
||||
problem7=(15, 3),
|
||||
problem8=(17, 30))
|
||||
|
||||
def test_empty_comment(self):
|
||||
conf = ('comments:\n'
|
||||
@@ -132,6 +150,14 @@ class CommentsTestCase(RuleTestCase):
|
||||
' min-spaces-from-content: 2\n')
|
||||
self.check('# comment\n', conf)
|
||||
|
||||
def test_last_line(self):
|
||||
conf = ('comments:\n'
|
||||
' require-starting-space: yes\n'
|
||||
' min-spaces-from-content: 2\n'
|
||||
'new-line-at-end-of-file: disable\n')
|
||||
self.check('# comment with no newline char:\n'
|
||||
'#', conf)
|
||||
|
||||
def test_multi_line_scalar(self):
|
||||
conf = ('comments:\n'
|
||||
' require-starting-space: yes\n'
|
||||
|
||||
@@ -84,6 +84,9 @@ class LineLengthTestCase(RuleTestCase):
|
||||
'another:\n'
|
||||
' - https://localhost/very/very/long/url\n'
|
||||
'...\n', conf)
|
||||
self.check('---\n'
|
||||
'long_line: http://localhost/very/very/long/url\n', conf,
|
||||
problem=(2, 21))
|
||||
|
||||
conf = 'line-length: {max: 20, allow-non-breakable-words: no}'
|
||||
self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21))
|
||||
@@ -106,3 +109,39 @@ class LineLengthTestCase(RuleTestCase):
|
||||
'another:\n'
|
||||
' - https://localhost/very/very/long/url\n'
|
||||
'...\n', conf, problem=(5, 21))
|
||||
self.check('---\n'
|
||||
'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'
|
||||
'trailing-spaces: disable')
|
||||
self.check('---\n'
|
||||
'loooooooooong+word+and+some+space+at+the+end \n',
|
||||
conf, problem=(2, 21))
|
||||
|
||||
def test_non_breakable_inline_mappings(self):
|
||||
conf = 'line-length: {max: 20, ' \
|
||||
'allow-non-breakable-inline-mappings: yes}'
|
||||
self.check('---\n'
|
||||
'long_line: http://localhost/very/very/long/url\n'
|
||||
'long line: http://localhost/very/very/long/url\n', conf)
|
||||
self.check('---\n'
|
||||
'- long line: http://localhost/very/very/long/url\n', conf)
|
||||
|
||||
self.check('---\n'
|
||||
'long_line: http://localhost/short/url + word\n'
|
||||
'long line: http://localhost/short/url + word\n',
|
||||
conf, problem1=(2, 21), problem2=(3, 21))
|
||||
|
||||
conf = ('line-length: {max: 20,'
|
||||
' allow-non-breakable-inline-mappings: yes}\n'
|
||||
'trailing-spaces: disable')
|
||||
self.check('---\n'
|
||||
'long_line: and+some+space+at+the+end \n',
|
||||
conf, problem=(2, 21))
|
||||
self.check('---\n'
|
||||
'long line: and+some+space+at+the+end \n',
|
||||
conf, problem=(2, 21))
|
||||
self.check('---\n'
|
||||
'- long line: and+some+space+at+the+end \n',
|
||||
conf, problem=(2, 21))
|
||||
|
||||
@@ -18,7 +18,10 @@ try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
import fcntl
|
||||
import locale
|
||||
import os
|
||||
import pty
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
@@ -64,6 +67,15 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
f.write('---\n'
|
||||
'key: value\n')
|
||||
|
||||
# non-ASCII chars
|
||||
os.mkdir(os.path.join(self.wd, 'non-ascii'))
|
||||
with open(os.path.join(self.wd, 'non-ascii', 'utf-8'), 'wb') as f:
|
||||
f.write((u'---\n'
|
||||
u'- hétérogénéité\n'
|
||||
u'# 19.99 €\n'
|
||||
u'- お早う御座います。\n'
|
||||
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'))
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.wd)
|
||||
|
||||
@@ -261,6 +273,26 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
self.assertEqual(out, '')
|
||||
self.assertEqual(err, '')
|
||||
|
||||
def test_run_non_ascii_file(self):
|
||||
file = os.path.join(self.wd, 'non-ascii', 'utf-8')
|
||||
|
||||
# Make sure the default localization conditions on this "system"
|
||||
# support UTF-8 encoding.
|
||||
loc = locale.getlocale()
|
||||
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
|
||||
|
||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||
with self.assertRaises(SystemExit) as ctx:
|
||||
cli.run(('-f', 'parsable', file))
|
||||
|
||||
locale.setlocale(locale.LC_ALL, loc)
|
||||
|
||||
self.assertEqual(ctx.exception.code, 0)
|
||||
|
||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
||||
self.assertEqual(out, '')
|
||||
self.assertEqual(err, '')
|
||||
|
||||
def test_run_multiple_files(self):
|
||||
items = [os.path.join(self.wd, 'empty.yml'),
|
||||
os.path.join(self.wd, 's')]
|
||||
@@ -278,7 +310,7 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
'(key-duplicates)\n') % file)
|
||||
self.assertEqual(err, '')
|
||||
|
||||
def test_run_colored_output(self):
|
||||
def test_run_piped_output_nocolor(self):
|
||||
file = os.path.join(self.wd, 'a.yaml')
|
||||
|
||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||
@@ -288,6 +320,38 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
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_colored_output(self):
|
||||
file = os.path.join(self.wd, 'a.yaml')
|
||||
|
||||
# Create a pseudo-TTY and redirect stdout to it
|
||||
master, slave = pty.openpty()
|
||||
sys.stdout = sys.stderr = os.fdopen(slave, 'w')
|
||||
|
||||
with self.assertRaises(SystemExit) as ctx:
|
||||
cli.run((file, ))
|
||||
sys.stdout.flush()
|
||||
|
||||
self.assertEqual(ctx.exception.code, 1)
|
||||
|
||||
# Read output from TTY
|
||||
output = os.fdopen(master, 'r')
|
||||
flag = fcntl.fcntl(master, fcntl.F_GETFD)
|
||||
fcntl.fcntl(master, fcntl.F_SETFL, flag | os.O_NONBLOCK)
|
||||
|
||||
out = output.read().replace('\r\n', '\n')
|
||||
|
||||
sys.stdout.close()
|
||||
sys.stderr.close()
|
||||
output.close()
|
||||
|
||||
self.assertEqual(out, (
|
||||
'\033[4m%s\033[0m\n'
|
||||
' \033[2m2:4\033[0m \033[31merror\033[0m '
|
||||
@@ -296,4 +360,3 @@ class CommandLineTestCase(unittest.TestCase):
|
||||
'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, '')
|
||||
|
||||
@@ -44,3 +44,15 @@ class LinterTestCase(unittest.TestCase):
|
||||
def test_run_on_list(self):
|
||||
self.assertRaises(TypeError, linter.run,
|
||||
['h', 'e', 'l', 'l', 'o'], self.fake_config())
|
||||
|
||||
def test_run_on_non_ascii_chars(self):
|
||||
s = (u'- hétérogénéité\n'
|
||||
u'# 19.99 €\n')
|
||||
linter.run(s, self.fake_config())
|
||||
linter.run(s.encode('utf-8'), self.fake_config())
|
||||
linter.run(s.encode('iso-8859-15'), self.fake_config())
|
||||
|
||||
s = (u'- お早う御座います。\n'
|
||||
u'# الأَبْجَدِيَّة العَرَبِيَّة\n')
|
||||
linter.run(s, self.fake_config())
|
||||
linter.run(s.encode('utf-8'), self.fake_config())
|
||||
|
||||
@@ -22,7 +22,7 @@ indentation, etc."""
|
||||
|
||||
|
||||
APP_NAME = 'yamllint'
|
||||
APP_VERSION = '1.3.0'
|
||||
APP_VERSION = '1.4.0'
|
||||
APP_DESCRIPTION = __doc__
|
||||
|
||||
__author__ = u'Adrien Vergé'
|
||||
|
||||
@@ -48,6 +48,17 @@ class Format(object):
|
||||
|
||||
@staticmethod
|
||||
def standard(problem, filename):
|
||||
line = ' %d:%d' % (problem.line, problem.column)
|
||||
line += max(12 - len(line), 0) * ' '
|
||||
line += problem.level
|
||||
line += max(21 - len(line), 0) * ' '
|
||||
line += problem.desc
|
||||
if problem.rule:
|
||||
line += ' (%s)' % problem.rule
|
||||
return line
|
||||
|
||||
@staticmethod
|
||||
def standard_color(problem, filename):
|
||||
line = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column)
|
||||
line += max(20 - len(line), 0) * ' '
|
||||
if problem.level == 'warning':
|
||||
@@ -119,11 +130,17 @@ def run(argv=None):
|
||||
for problem in linter.run(f, conf):
|
||||
if args.format == 'parsable':
|
||||
print(Format.parsable(problem, file))
|
||||
else:
|
||||
elif sys.stdout.isatty():
|
||||
if first:
|
||||
print('\033[4m%s\033[0m' % file)
|
||||
first = False
|
||||
|
||||
print(Format.standard_color(problem, file))
|
||||
else:
|
||||
if first:
|
||||
print(file)
|
||||
first = False
|
||||
|
||||
print(Format.standard(problem, file))
|
||||
|
||||
if return_code == 0 and problem.level == 'error':
|
||||
|
||||
@@ -38,6 +38,7 @@ rules:
|
||||
line-length:
|
||||
max: 80
|
||||
allow-non-breakable-words: yes
|
||||
allow-non-breakable-inline-mappings: no
|
||||
new-line-at-end-of-file: enable
|
||||
new-lines:
|
||||
type: unix
|
||||
|
||||
@@ -25,3 +25,4 @@ rules:
|
||||
indent-sequences: consistent
|
||||
line-length:
|
||||
level: warning
|
||||
allow-non-breakable-inline-mappings: yes
|
||||
|
||||
@@ -71,7 +71,10 @@ def get_costemic_problems(buffer, conf):
|
||||
self.all_rules = set([r.ID for r in rules])
|
||||
|
||||
def process_comment(self, comment):
|
||||
comment = repr(comment)
|
||||
try:
|
||||
comment = str(comment)
|
||||
except UnicodeError:
|
||||
return # this certainly wasn't a yamllint directive comment
|
||||
|
||||
if re.match(r'^# yamllint disable( rule:\S+)*\s*$', comment):
|
||||
rules = [item[5:] for item in comment[18:].split(' ')][1:]
|
||||
@@ -95,7 +98,10 @@ def get_costemic_problems(buffer, conf):
|
||||
|
||||
class DisableLineDirective(DisableDirective):
|
||||
def process_comment(self, comment):
|
||||
comment = repr(comment)
|
||||
try:
|
||||
comment = str(comment)
|
||||
except UnicodeError:
|
||||
return # this certainly wasn't a yamllint directive comment
|
||||
|
||||
if re.match(r'^# yamllint disable-line( rule:\S+)*\s*$', comment):
|
||||
rules = [item[5:] for item in comment[23:].split(' ')][1:]
|
||||
|
||||
@@ -49,7 +49,7 @@ class Comment(object):
|
||||
self.token_after = token_after
|
||||
self.comment_before = comment_before
|
||||
|
||||
def __repr__(self):
|
||||
def __str__(self):
|
||||
end = self.buffer.find('\n', self.pointer)
|
||||
if end == -1:
|
||||
end = self.buffer.find('\0', self.pointer)
|
||||
|
||||
@@ -35,6 +35,12 @@ Use this rule to control the position and formatting of comments.
|
||||
# This sentence
|
||||
# is a block comment
|
||||
|
||||
the following code snippet would **PASS**:
|
||||
::
|
||||
|
||||
##############################
|
||||
## This is some documentation
|
||||
|
||||
the following code snippet would **FAIL**:
|
||||
::
|
||||
|
||||
@@ -71,9 +77,13 @@ def check(conf, comment):
|
||||
yield LintProblem(comment.line_no, comment.column_no,
|
||||
'too few spaces before comment')
|
||||
|
||||
if (conf['require-starting-space'] and
|
||||
comment.pointer + 1 < len(comment.buffer) and
|
||||
comment.buffer[comment.pointer + 1] != ' ' and
|
||||
comment.buffer[comment.pointer + 1] != '\n'):
|
||||
yield LintProblem(comment.line_no, comment.column_no + 1,
|
||||
'missing starting space in comment')
|
||||
if conf['require-starting-space']:
|
||||
text_start = comment.pointer + 1
|
||||
while (comment.buffer[text_start] == '#' and
|
||||
text_start < len(comment.buffer)):
|
||||
text_start += 1
|
||||
if (text_start < len(comment.buffer) and
|
||||
comment.buffer[text_start] not in (' ', '\n', '\0')):
|
||||
yield LintProblem(comment.line_no,
|
||||
comment.column_no + text_start - comment.pointer,
|
||||
'missing starting space in comment')
|
||||
|
||||
@@ -23,6 +23,8 @@ Use this rule to set a limit to lines length.
|
||||
* ``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.
|
||||
* ``allow-non-breakable-inline-mappings`` implies ``allow-non-breakable-words``
|
||||
and extends it to also allow non-breakable words in inline mappings.
|
||||
|
||||
.. rubric:: Examples
|
||||
|
||||
@@ -61,6 +63,19 @@ Use this rule to set a limit to lines length.
|
||||
|
||||
- this line is waaaaaaaaaaaaaay too long but could be easily split...
|
||||
|
||||
and the following code snippet would also **FAIL**:
|
||||
::
|
||||
|
||||
- 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}``
|
||||
|
||||
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}``
|
||||
|
||||
the following code snippet would **FAIL**:
|
||||
@@ -73,17 +88,34 @@ Use this rule to set a limit to lines length.
|
||||
"""
|
||||
|
||||
|
||||
import yaml
|
||||
|
||||
from yamllint.linter import LintProblem
|
||||
|
||||
|
||||
ID = 'line-length'
|
||||
TYPE = 'line'
|
||||
CONF = {'max': int,
|
||||
'allow-non-breakable-words': bool}
|
||||
'allow-non-breakable-words': bool,
|
||||
'allow-non-breakable-inline-mappings': bool}
|
||||
|
||||
|
||||
def check_inline_mapping(line):
|
||||
loader = yaml.SafeLoader(line.content)
|
||||
while loader.peek_token():
|
||||
if isinstance(loader.get_token(), yaml.BlockMappingStartToken):
|
||||
while loader.peek_token():
|
||||
if isinstance(loader.get_token(), yaml.ValueToken):
|
||||
t = loader.get_token()
|
||||
if isinstance(t, yaml.ScalarToken):
|
||||
return ' ' not in line.content[t.start_mark.column:]
|
||||
return False
|
||||
|
||||
|
||||
def check(conf, line):
|
||||
if line.end - line.start > conf['max']:
|
||||
conf['allow-non-breakable-words'] |= \
|
||||
conf['allow-non-breakable-inline-mappings']
|
||||
if conf['allow-non-breakable-words']:
|
||||
start = line.start
|
||||
while start < line.end and line.buffer[start] == ' ':
|
||||
@@ -96,6 +128,10 @@ def check(conf, line):
|
||||
if line.buffer.find(' ', start, line.end) == -1:
|
||||
return
|
||||
|
||||
if (conf['allow-non-breakable-inline-mappings'] and
|
||||
check_inline_mapping(line)):
|
||||
return
|
||||
|
||||
yield LintProblem(line.line_no, conf['max'] + 1,
|
||||
'line too long (%d > %d characters)' %
|
||||
(line.end - line.start, conf['max']))
|
||||
|
||||
Reference in New Issue
Block a user