Compare commits

...

9 Commits

Author SHA1 Message Date
Adrien Vergé
60b72daad4 yamllint version 1.4.0 2016-09-19 13:53:03 +02:00
Adrien Vergé
773bb8a648 Merge pull request #17 from allanlewis/improve-unbreakable
line_length: Allow mapping values with long unbreakable lines
2016-09-19 13:51:44 +02:00
Adrien Vergé
d3cd8ba332 line-length: Generalize ...-inline-mappings for corner cases
This commit refactors the `allow-non-breakable-inline-mappings` logic to
use YAML tokens and avoid crashes or erroneous reports on cases like:

```yaml
- {a: "http://localhost/very/very/very/very/very/very/long/url"
   }
```

```yaml
dict:
  {a: long long long long long long long, b: nospace}
```

```yaml
- long_line: http://localhost/very/very/long/url
```

```yaml
long_line: and+some+space+at+the+end       <-- extra spaces
```

For reference see:
https://github.com/adrienverge/yamllint/pull/17#issuecomment-247805799
2016-09-19 12:39:52 +01:00
Allan Lewis
e56a7c788c line_length: Extract inline logic to new config option
This commit extracts the inline mappings logic defined in the previous
commit to a separate config option, as suggested by @adrienverge. I'll
squash this into the previous commit if the change is accepted. (I named
the option slightly differently to what was suggested as I think my
proposal reads better without consulting the docs: I'd be happy to
reconsider this.)
2016-09-19 12:39:52 +01:00
Allan Lewis
d017631aff line_length: Allow mapping values with long unbreakable lines 2016-09-12 16:31:58 +01:00
Adrien Vergé
5b98cd2053 feat(comments): Allow comments with multiple hash chars
This change make the `comments` rule accept comments that start with
multiple pound signs, e.g.:

    ##############################
    ## This is some documentation

Closes: #12
2016-08-12 11:58:57 +02:00
Adrien Vergé
82dd7dbf16 Merge pull request #16 from adrienverge/coloured_output_on_tty
feat(cli): Colour output only on TTY
2016-08-12 11:58:00 +02:00
Adrien Vergé
4533b8ae49 doc(config): Show relaxed conf contents
Closes: #15
2016-08-12 11:17:18 +02:00
Adrien Vergé
a2c68fdf9b feat(cli): Colour output only on TTY
When piping yamllint output to a file, "coloured" characters aren't
interpreted and pollute text formatting with glyphs like:

  �[4m./global.yaml�[0m
    �[2m1439:52�[0m   �[31merror�[0m    no new line character...

With this commit, stdout is checked: if it's a TTY then output is
coloured, otherwise output is simple text.

Closes: #14
2016-08-12 11:03:41 +02:00
10 changed files with 182 additions and 16 deletions

View File

@@ -31,7 +31,10 @@ Unless told otherwise, yamllint uses its ``default`` configuration:
Details on rules can be found on :doc:`the rules page <rules>`. Details on rules can be found on :doc:`the rules page <rules>`.
There is another pre-defined configuration named ``relaxed``. As its name 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: It can be chosen using:

View File

@@ -35,6 +35,10 @@ class CommentsTestCase(RuleTestCase):
' #comment 3 bis\n' ' #comment 3 bis\n'
' # comment 3 ter\n' ' # comment 3 ter\n'
'\n' '\n'
'################################\n'
'## comment 4\n'
'##comment 5\n'
'\n'
'string: "Une longue phrase." # this is French\n', conf) 'string: "Une longue phrase." # this is French\n', conf)
def test_starting_space(self): def test_starting_space(self):
@@ -52,7 +56,11 @@ class CommentsTestCase(RuleTestCase):
'# comment 2\n' '# comment 2\n'
'# comment 3\n' '# comment 3\n'
' # comment 3 bis\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' self.check('---\n'
'#comment\n' '#comment\n'
'\n' '\n'
@@ -63,9 +71,14 @@ class CommentsTestCase(RuleTestCase):
'# comment 2\n' '# comment 2\n'
'#comment 3\n' '#comment 3\n'
' #comment 3 bis\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), 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): def test_spaces_from_content(self):
conf = ('comments:\n' conf = ('comments:\n'
@@ -106,13 +119,18 @@ class CommentsTestCase(RuleTestCase):
' #comment 3 bis\n' ' #comment 3 bis\n'
' # comment 3 ter\n' ' # comment 3 ter\n'
'\n' '\n'
'################################\n'
'## comment 4\n'
'##comment 5\n'
'\n'
'string: "Une longue phrase." # this is French\n', conf, 'string: "Une longue phrase." # this is French\n', conf,
problem1=(2, 2), problem1=(2, 2),
problem2=(4, 7), problem2=(4, 7),
problem3=(6, 11), problem4=(6, 12), problem3=(6, 11), problem4=(6, 12),
problem5=(9, 2), problem5=(9, 2),
problem6=(10, 4), problem6=(10, 4),
problem7=(13, 30)) problem7=(15, 3),
problem8=(17, 30))
def test_empty_comment(self): def test_empty_comment(self):
conf = ('comments:\n' conf = ('comments:\n'
@@ -132,6 +150,14 @@ class CommentsTestCase(RuleTestCase):
' min-spaces-from-content: 2\n') ' min-spaces-from-content: 2\n')
self.check('# comment\n', conf) 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): def test_multi_line_scalar(self):
conf = ('comments:\n' conf = ('comments:\n'
' require-starting-space: yes\n' ' require-starting-space: yes\n'

View File

@@ -84,6 +84,9 @@ class LineLengthTestCase(RuleTestCase):
'another:\n' 'another:\n'
' - https://localhost/very/very/long/url\n' ' - https://localhost/very/very/long/url\n'
'...\n', conf) '...\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}' conf = 'line-length: {max: 20, allow-non-breakable-words: no}'
self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21)) self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21))
@@ -106,3 +109,39 @@ class LineLengthTestCase(RuleTestCase):
'another:\n' 'another:\n'
' - https://localhost/very/very/long/url\n' ' - https://localhost/very/very/long/url\n'
'...\n', conf, problem=(5, 21)) '...\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))

View File

@@ -18,8 +18,10 @@ try:
from cStringIO import StringIO from cStringIO import StringIO
except ImportError: except ImportError:
from io import StringIO from io import StringIO
import fcntl
import locale import locale
import os import os
import pty
import shutil import shutil
import tempfile import tempfile
import unittest import unittest
@@ -308,7 +310,7 @@ class CommandLineTestCase(unittest.TestCase):
'(key-duplicates)\n') % file) '(key-duplicates)\n') % file)
self.assertEqual(err, '') self.assertEqual(err, '')
def test_run_colored_output(self): def test_run_piped_output_nocolor(self):
file = os.path.join(self.wd, 'a.yaml') file = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO() sys.stdout, sys.stderr = StringIO(), StringIO()
@@ -318,6 +320,38 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual(ctx.exception.code, 1) self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue() 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, ( self.assertEqual(out, (
'\033[4m%s\033[0m\n' '\033[4m%s\033[0m\n'
' \033[2m2:4\033[0m \033[31merror\033[0m ' ' \033[2m2:4\033[0m \033[31merror\033[0m '
@@ -326,4 +360,3 @@ 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))
self.assertEqual(err, '')

View File

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

View File

@@ -48,6 +48,17 @@ class Format(object):
@staticmethod @staticmethod
def standard(problem, filename): 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 = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column)
line += max(20 - len(line), 0) * ' ' line += max(20 - len(line), 0) * ' '
if problem.level == 'warning': if problem.level == 'warning':
@@ -119,11 +130,17 @@ def run(argv=None):
for problem in linter.run(f, conf): for problem in linter.run(f, conf):
if args.format == 'parsable': if args.format == 'parsable':
print(Format.parsable(problem, file)) print(Format.parsable(problem, file))
else: elif sys.stdout.isatty():
if first: if first:
print('\033[4m%s\033[0m' % file) print('\033[4m%s\033[0m' % file)
first = False first = False
print(Format.standard_color(problem, file))
else:
if first:
print(file)
first = False
print(Format.standard(problem, file)) print(Format.standard(problem, file))
if return_code == 0 and problem.level == 'error': if return_code == 0 and problem.level == 'error':

View File

@@ -38,6 +38,7 @@ rules:
line-length: line-length:
max: 80 max: 80
allow-non-breakable-words: yes allow-non-breakable-words: yes
allow-non-breakable-inline-mappings: no
new-line-at-end-of-file: enable new-line-at-end-of-file: enable
new-lines: new-lines:
type: unix type: unix

View File

@@ -25,3 +25,4 @@ rules:
indent-sequences: consistent indent-sequences: consistent
line-length: line-length:
level: warning level: warning
allow-non-breakable-inline-mappings: yes

View File

@@ -35,6 +35,12 @@ Use this rule to control the position and formatting of comments.
# This sentence # This sentence
# is a block comment # is a block comment
the following code snippet would **PASS**:
::
##############################
## This is some documentation
the following code snippet would **FAIL**: the following code snippet would **FAIL**:
:: ::
@@ -71,9 +77,13 @@ def check(conf, comment):
yield LintProblem(comment.line_no, comment.column_no, yield LintProblem(comment.line_no, comment.column_no,
'too few spaces before comment') 'too few spaces before comment')
if (conf['require-starting-space'] and if conf['require-starting-space']:
comment.pointer + 1 < len(comment.buffer) and text_start = comment.pointer + 1
comment.buffer[comment.pointer + 1] != ' ' and while (comment.buffer[text_start] == '#' and
comment.buffer[comment.pointer + 1] != '\n'): text_start < len(comment.buffer)):
yield LintProblem(comment.line_no, comment.column_no + 1, text_start += 1
'missing starting space in comment') 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')

View File

@@ -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 * ``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 spaces inside) to overflow the limit. This is useful for long URLs, for
instance. Use ``yes`` to allow, ``no`` to forbid. 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 .. 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... - 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}`` #. With ``line-length: {max: 60, allow-non-breakable-words: no}``
the following code snippet would **FAIL**: 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 from yamllint.linter import LintProblem
ID = 'line-length' ID = 'line-length'
TYPE = 'line' TYPE = 'line'
CONF = {'max': int, 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): def check(conf, line):
if line.end - line.start > conf['max']: if line.end - line.start > conf['max']:
conf['allow-non-breakable-words'] |= \
conf['allow-non-breakable-inline-mappings']
if conf['allow-non-breakable-words']: if conf['allow-non-breakable-words']:
start = line.start start = line.start
while start < line.end and line.buffer[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: if line.buffer.find(' ', start, line.end) == -1:
return return
if (conf['allow-non-breakable-inline-mappings'] and
check_inline_mapping(line)):
return
yield LintProblem(line.line_no, conf['max'] + 1, yield LintProblem(line.line_no, conf['max'] + 1,
'line too long (%d > %d characters)' % 'line too long (%d > %d characters)' %
(line.end - line.start, conf['max'])) (line.end - line.start, conf['max']))