Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b6f024448 | ||
|
|
75b4758c95 | ||
|
|
0e98df2643 | ||
|
|
d4189083d0 | ||
|
|
67d13d60ae | ||
|
|
96465008ab | ||
|
|
847f7e3fff | ||
|
|
6a24781f96 |
@@ -14,12 +14,16 @@ yamllint my_file.yml my_other_file.yaml ...
|
||||
```
|
||||
|
||||
```sh
|
||||
yamllint -c ~/myconfig my_file.yml
|
||||
yamllint .
|
||||
```
|
||||
|
||||
```sh
|
||||
yamllint -c ~/myconfig file.yml
|
||||
```
|
||||
|
||||
```sh
|
||||
# To output a format parsable (by editors like Vim, emacs, etc.)
|
||||
yamllint -f parsable my_file.yml
|
||||
yamllint -f parsable file.yml
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
62
bin/yamllint
62
bin/yamllint
@@ -15,68 +15,10 @@
|
||||
# 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 __future__ import print_function
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
import argparse
|
||||
|
||||
from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION
|
||||
from yamllint import config
|
||||
from yamllint.errors import YamlLintConfigError
|
||||
from yamllint import lint
|
||||
from yamllint import output
|
||||
from yamllint import cli
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(prog=APP_NAME,
|
||||
description=APP_DESCRIPTION)
|
||||
parser.add_argument('files', metavar='FILES', nargs='+',
|
||||
help='files to check')
|
||||
parser.add_argument('-c', '--config', dest='config_file', action='store',
|
||||
help='path to a custom configuration')
|
||||
parser.add_argument('-f', '--format',
|
||||
choices=('parsable', 'standard'), default='standard',
|
||||
help='format for parsing output')
|
||||
parser.add_argument('-v', '--version', action='version',
|
||||
version='%s %s' % (APP_NAME, APP_VERSION))
|
||||
|
||||
# TODO: read from stdin when no filename?
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.config_file is not None:
|
||||
conf = config.parse_config_from_file(args.config_file)
|
||||
elif os.path.isfile('.yamllint'):
|
||||
conf = config.parse_config_from_file('.yamllint')
|
||||
else:
|
||||
conf = config.parse_config('extends: default')
|
||||
except YamlLintConfigError as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
return_code = 0
|
||||
|
||||
for file in args.files:
|
||||
if args.format != 'parsable':
|
||||
print('\033[4m%s\033[0m' % file)
|
||||
|
||||
try:
|
||||
with open(file) as f:
|
||||
for problem in lint(f, conf):
|
||||
if args.format == 'parsable':
|
||||
print(output.parsable_format(problem, file))
|
||||
else:
|
||||
print(output.standard_format(problem, file))
|
||||
|
||||
if return_code == 0 and problem.level == 'error':
|
||||
return_code = 1
|
||||
except EnvironmentError as e:
|
||||
print(e)
|
||||
return_code = -1
|
||||
|
||||
if args.format != 'parsable':
|
||||
print('')
|
||||
|
||||
sys.exit(return_code)
|
||||
cli.run(sys.argv[1:])
|
||||
|
||||
@@ -118,7 +118,7 @@ class ColonTestCase(RuleTestCase):
|
||||
'...\n', conf, problem=(3, 8))
|
||||
|
||||
def test_before_with_explicit_block_mappings(self):
|
||||
conf = 'colons: {max-spaces-before: 0, max-spaces-after: -1}'
|
||||
conf = 'colons: {max-spaces-before: 0, max-spaces-after: 1}'
|
||||
self.check('---\n'
|
||||
'object:\n'
|
||||
' ? key\n'
|
||||
@@ -129,6 +129,30 @@ class ColonTestCase(RuleTestCase):
|
||||
' ? key\n'
|
||||
' : value\n'
|
||||
'...\n', conf, problem=(2, 7))
|
||||
self.check('---\n'
|
||||
'? >\n'
|
||||
' multi-line\n'
|
||||
' key\n'
|
||||
': >\n'
|
||||
' multi-line\n'
|
||||
' value\n'
|
||||
'...\n', conf)
|
||||
self.check('---\n'
|
||||
'- ? >\n'
|
||||
' multi-line\n'
|
||||
' key\n'
|
||||
' : >\n'
|
||||
' multi-line\n'
|
||||
' value\n'
|
||||
'...\n', conf)
|
||||
self.check('---\n'
|
||||
'- ? >\n'
|
||||
' multi-line\n'
|
||||
' key\n'
|
||||
' : >\n'
|
||||
' multi-line\n'
|
||||
' value\n'
|
||||
'...\n', conf, problem=(5, 5))
|
||||
|
||||
def test_after_enabled(self):
|
||||
conf = 'colons: {max-spaces-before: -1, max-spaces-after: 1}'
|
||||
|
||||
@@ -131,3 +131,19 @@ class CommentsTestCase(RuleTestCase):
|
||||
' require-starting-space: yes\n'
|
||||
' min-spaces-from-content: 2\n')
|
||||
self.check('# comment\n', conf)
|
||||
|
||||
def test_multi_line_scalar(self):
|
||||
conf = ('comments:\n'
|
||||
' require-starting-space: yes\n'
|
||||
' min-spaces-from-content: 2\n'
|
||||
'trailing-spaces: disable\n')
|
||||
self.check('---\n'
|
||||
'string: >\n'
|
||||
' this is plain text\n'
|
||||
'\n'
|
||||
'# comment\n', conf)
|
||||
self.check('---\n'
|
||||
'- string: >\n'
|
||||
' this is plain text\n'
|
||||
' \n'
|
||||
' # comment\n', conf)
|
||||
|
||||
@@ -452,3 +452,270 @@ class IndentationTestCase(RuleTestCase):
|
||||
' 2,\n'
|
||||
' 3\n'
|
||||
']\n', conf, problem1=(4, 4), problem2=(5, 2))
|
||||
|
||||
def test_explicit_block_mappings(self):
|
||||
conf = 'indentation: {spaces: 4}'
|
||||
self.check('---\n'
|
||||
'object:\n'
|
||||
' ? key\n'
|
||||
' :\n'
|
||||
' value\n'
|
||||
'...\n', conf)
|
||||
self.check('---\n'
|
||||
'object:\n'
|
||||
' ? key\n'
|
||||
' :\n'
|
||||
' value\n'
|
||||
'...\n', conf, problem=(5, 8))
|
||||
self.check('---\n'
|
||||
'object:\n'
|
||||
' ?\n'
|
||||
' key\n'
|
||||
' :\n'
|
||||
' value\n'
|
||||
'...\n', conf)
|
||||
self.check('---\n'
|
||||
'object:\n'
|
||||
' ?\n'
|
||||
' key\n'
|
||||
' :\n'
|
||||
' value\n'
|
||||
'...\n', conf, problem1=(4, 8), problem2=(6, 10))
|
||||
self.check('---\n'
|
||||
'object:\n'
|
||||
' ?\n'
|
||||
' key\n'
|
||||
' :\n'
|
||||
' value\n'
|
||||
'...\n', conf, problem1=(4, 10), problem2=(6, 8))
|
||||
|
||||
|
||||
class ScalarIndentationTestCase(RuleTestCase):
|
||||
rule_id = 'indentation'
|
||||
|
||||
def test_basics_plain(self):
|
||||
conf = ('indentation: {spaces: 2}\n'
|
||||
'document-start: disable\n')
|
||||
self.check('multi\n'
|
||||
'line\n', conf)
|
||||
self.check('multi\n'
|
||||
' line\n', conf, problem=(2, 2))
|
||||
self.check('- multi\n'
|
||||
' line\n', conf)
|
||||
self.check('- multi\n'
|
||||
' line\n', conf, problem=(2, 4))
|
||||
self.check('a key: multi\n'
|
||||
' line\n', conf)
|
||||
self.check('a key: multi\n'
|
||||
' line\n', conf, problem=(2, 9))
|
||||
self.check('a key:\n'
|
||||
' multi\n'
|
||||
' line\n', conf)
|
||||
self.check('a key:\n'
|
||||
' multi\n'
|
||||
' line\n', conf, problem=(3, 4))
|
||||
|
||||
def test_basics_quoted(self):
|
||||
conf = ('indentation: {spaces: 2}\n'
|
||||
'document-start: disable\n')
|
||||
self.check('"multi\n'
|
||||
' line"\n', conf)
|
||||
self.check('"multi\n'
|
||||
'line"\n', conf, problem=(2, 1))
|
||||
self.check('"multi\n'
|
||||
' line"\n', conf, problem=(2, 3))
|
||||
self.check('- "multi\n'
|
||||
' line"\n', conf)
|
||||
self.check('- "multi\n'
|
||||
' line"\n', conf, problem=(2, 3))
|
||||
self.check('- "multi\n'
|
||||
' line"\n', conf, problem=(2, 5))
|
||||
self.check('a key: "multi\n'
|
||||
' line"\n', conf)
|
||||
self.check('a key: "multi\n'
|
||||
' line"\n', conf, problem=(2, 8))
|
||||
self.check('a key: "multi\n'
|
||||
' line"\n', conf, problem=(2, 10))
|
||||
self.check('a key:\n'
|
||||
' "multi\n'
|
||||
' line"\n', conf)
|
||||
self.check('a key:\n'
|
||||
' "multi\n'
|
||||
' line"\n', conf, problem=(3, 3))
|
||||
self.check('a key:\n'
|
||||
' "multi\n'
|
||||
' line"\n', conf, problem=(3, 5))
|
||||
|
||||
def test_basics_folded_style(self):
|
||||
conf = ('indentation: {spaces: 2}\n'
|
||||
'document-start: disable\n')
|
||||
self.check('>\n'
|
||||
' multi\n'
|
||||
' line\n', conf)
|
||||
self.check('- >\n'
|
||||
' multi\n'
|
||||
' line\n', conf)
|
||||
self.check('- key: >\n'
|
||||
' multi\n'
|
||||
' line\n', conf)
|
||||
self.check('- key:\n'
|
||||
' >\n'
|
||||
' multi\n'
|
||||
' line\n', conf)
|
||||
self.check('- ? >\n'
|
||||
' multi-line\n'
|
||||
' key\n'
|
||||
' : >\n'
|
||||
' multi-line\n'
|
||||
' value\n', conf)
|
||||
self.check('- ?\n'
|
||||
' >\n'
|
||||
' multi-line\n'
|
||||
' key\n'
|
||||
' :\n'
|
||||
' >\n'
|
||||
' multi-line\n'
|
||||
' value\n', conf)
|
||||
|
||||
def test_basics_literal_style(self):
|
||||
conf = ('indentation: {spaces: 2}\n'
|
||||
'document-start: disable\n')
|
||||
self.check('|\n'
|
||||
' multi\n'
|
||||
' line\n', conf)
|
||||
self.check('- |\n'
|
||||
' multi\n'
|
||||
' line\n', conf)
|
||||
self.check('- key: |\n'
|
||||
' multi\n'
|
||||
' line\n', conf)
|
||||
self.check('- key:\n'
|
||||
' |\n'
|
||||
' multi\n'
|
||||
' line\n', conf)
|
||||
self.check('- ? |\n'
|
||||
' multi-line\n'
|
||||
' key\n'
|
||||
' : |\n'
|
||||
' multi-line\n'
|
||||
' value\n', conf)
|
||||
self.check('- ?\n'
|
||||
' |\n'
|
||||
' multi-line\n'
|
||||
' key\n'
|
||||
' :\n'
|
||||
' |\n'
|
||||
' multi-line\n'
|
||||
' value\n', conf)
|
||||
|
||||
# The following "paragraph" examples are inspired from
|
||||
# http://stackoverflow.com/questions/3790454/in-yaml-how-do-i-break-a-string-over-multiple-lines
|
||||
|
||||
def test_paragraph_plain(self):
|
||||
conf = ('indentation: {spaces: 2}\n'
|
||||
'document-start: disable\n')
|
||||
self.check('- long text: very "long"\n'
|
||||
' \'string\' with\n'
|
||||
'\n'
|
||||
' paragraph gap, \\n and\n'
|
||||
' spaces.\n', conf)
|
||||
self.check('- long text: very "long"\n'
|
||||
' \'string\' with\n'
|
||||
'\n'
|
||||
' paragraph gap, \\n and\n'
|
||||
' spaces.\n', conf,
|
||||
problem1=(2, 5), problem2=(4, 5), problem3=(5, 5))
|
||||
self.check('- long text:\n'
|
||||
' very "long"\n'
|
||||
' \'string\' with\n'
|
||||
'\n'
|
||||
' paragraph gap, \\n and\n'
|
||||
' spaces.\n', conf)
|
||||
|
||||
def test_paragraph_double_quoted(self):
|
||||
conf = ('indentation: {spaces: 2}\n'
|
||||
'document-start: disable\n')
|
||||
self.check('- long text: "very \\"long\\"\n'
|
||||
' \'string\' with\n'
|
||||
'\n'
|
||||
' paragraph gap, \\n and\n'
|
||||
' spaces."\n', conf)
|
||||
self.check('- long text: "very \\"long\\"\n'
|
||||
' \'string\' with\n'
|
||||
'\n'
|
||||
' paragraph gap, \\n and\n'
|
||||
' spaces."\n', conf,
|
||||
problem1=(2, 5), problem2=(4, 5), problem3=(5, 5))
|
||||
self.check('- long text: "very \\"long\\"\n'
|
||||
'\'string\' with\n'
|
||||
'\n'
|
||||
'paragraph gap, \\n and\n'
|
||||
'spaces."\n', conf,
|
||||
problem1=(2, 1), problem2=(4, 1), problem3=(5, 1))
|
||||
self.check('- long text:\n'
|
||||
' "very \\"long\\"\n'
|
||||
' \'string\' with\n'
|
||||
'\n'
|
||||
' paragraph gap, \\n and\n'
|
||||
' spaces."\n', conf)
|
||||
|
||||
def test_paragraph_single_quoted(self):
|
||||
conf = ('indentation: {spaces: 2}\n'
|
||||
'document-start: disable\n')
|
||||
self.check('- long text: \'very "long"\n'
|
||||
' \'\'string\'\' with\n'
|
||||
'\n'
|
||||
' paragraph gap, \\n and\n'
|
||||
' spaces.\'\n', conf)
|
||||
self.check('- long text: \'very "long"\n'
|
||||
' \'\'string\'\' with\n'
|
||||
'\n'
|
||||
' paragraph gap, \\n and\n'
|
||||
' spaces.\'\n', conf,
|
||||
problem1=(2, 5), problem2=(4, 5), problem3=(5, 5))
|
||||
self.check('- long text: \'very "long"\n'
|
||||
'\'\'string\'\' with\n'
|
||||
'\n'
|
||||
'paragraph gap, \\n and\n'
|
||||
'spaces.\'\n', conf,
|
||||
problem1=(2, 1), problem2=(4, 1), problem3=(5, 1))
|
||||
self.check('- long text:\n'
|
||||
' \'very "long"\n'
|
||||
' \'\'string\'\' with\n'
|
||||
'\n'
|
||||
' paragraph gap, \\n and\n'
|
||||
' spaces.\'\n', conf)
|
||||
|
||||
def test_paragraph_folded(self):
|
||||
conf = ('indentation: {spaces: 2}\n'
|
||||
'document-start: disable\n')
|
||||
self.check('- long text: >\n'
|
||||
' very "long"\n'
|
||||
' \'string\' with\n'
|
||||
'\n'
|
||||
' paragraph gap, \\n and\n'
|
||||
' spaces.\n', conf)
|
||||
self.check('- long text: >\n'
|
||||
' very "long"\n'
|
||||
' \'string\' with\n'
|
||||
'\n'
|
||||
' paragraph gap, \\n and\n'
|
||||
' spaces.\n', conf,
|
||||
problem1=(3, 6), problem2=(5, 7), problem3=(6, 8))
|
||||
|
||||
def test_paragraph_literal(self):
|
||||
conf = ('indentation: {spaces: 2}\n'
|
||||
'document-start: disable\n')
|
||||
self.check('- long text: |\n'
|
||||
' very "long"\n'
|
||||
' \'string\' with\n'
|
||||
'\n'
|
||||
' paragraph gap, \\n and\n'
|
||||
' spaces.\n', conf)
|
||||
self.check('- long text: |\n'
|
||||
' very "long"\n'
|
||||
' \'string\' with\n'
|
||||
'\n'
|
||||
' paragraph gap, \\n and\n'
|
||||
' spaces.\n', conf,
|
||||
problem1=(3, 6), problem2=(5, 7), problem3=(6, 8))
|
||||
|
||||
@@ -22,7 +22,7 @@ from yamllint import parser
|
||||
|
||||
|
||||
APP_NAME = 'yamllint'
|
||||
APP_VERSION = '0.3.0'
|
||||
APP_VERSION = '0.4.0'
|
||||
APP_DESCRIPTION = 'A linter for YAML files.'
|
||||
|
||||
__author__ = 'Adrien Vergé'
|
||||
|
||||
119
yamllint/cli.py
Normal file
119
yamllint/cli.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- 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/>.
|
||||
|
||||
from __future__ import print_function
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
import argparse
|
||||
|
||||
from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION
|
||||
from yamllint import config
|
||||
from yamllint.errors import YamlLintConfigError
|
||||
from yamllint import lint
|
||||
|
||||
|
||||
def find_files_recursively(items):
|
||||
for item in items:
|
||||
if os.path.isdir(item):
|
||||
for root, dirnames, filenames in os.walk(item):
|
||||
for filename in [f for f in filenames
|
||||
if f.endswith(('.yml', '.yaml'))]:
|
||||
yield os.path.join(root, filename)
|
||||
else:
|
||||
yield item
|
||||
|
||||
|
||||
class Format(object):
|
||||
@staticmethod
|
||||
def parsable(problem, filename):
|
||||
return ('%(file)s:%(line)s:%(column)s: [%(level)s] %(message)s' %
|
||||
{'file': filename,
|
||||
'line': problem.line,
|
||||
'column': problem.column,
|
||||
'level': problem.level,
|
||||
'message': problem.message})
|
||||
|
||||
@staticmethod
|
||||
def standard(problem, filename):
|
||||
line = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column)
|
||||
line += max(20 - len(line), 0) * ' '
|
||||
if problem.level == 'warning':
|
||||
line += '\033[33m%s\033[0m' % problem.level
|
||||
else:
|
||||
line += '\033[31m%s\033[0m' % problem.level
|
||||
line += max(38 - len(line), 0) * ' '
|
||||
line += problem.desc
|
||||
if problem.rule:
|
||||
line += ' \033[2m(%s)\033[0m' % problem.rule
|
||||
return line
|
||||
|
||||
|
||||
def run(argv):
|
||||
parser = argparse.ArgumentParser(prog=APP_NAME,
|
||||
description=APP_DESCRIPTION)
|
||||
parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+',
|
||||
help='files to check')
|
||||
parser.add_argument('-c', '--config', dest='config_file', action='store',
|
||||
help='path to a custom configuration')
|
||||
parser.add_argument('-f', '--format',
|
||||
choices=('parsable', 'standard'), default='standard',
|
||||
help='format for parsing output')
|
||||
parser.add_argument('-v', '--version', action='version',
|
||||
version='%s %s' % (APP_NAME, APP_VERSION))
|
||||
|
||||
# TODO: read from stdin when no filename?
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
try:
|
||||
if args.config_file is not None:
|
||||
conf = config.parse_config_from_file(args.config_file)
|
||||
elif os.path.isfile('.yamllint'):
|
||||
conf = config.parse_config_from_file('.yamllint')
|
||||
else:
|
||||
conf = config.parse_config('extends: default')
|
||||
except YamlLintConfigError as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
return_code = 0
|
||||
|
||||
for file in find_files_recursively(args.files):
|
||||
try:
|
||||
first = True
|
||||
with open(file) as f:
|
||||
for problem in lint(f, conf):
|
||||
if args.format == 'parsable':
|
||||
print(Format.parsable(problem, file))
|
||||
else:
|
||||
if first:
|
||||
print('\033[4m%s\033[0m' % file)
|
||||
first = False
|
||||
|
||||
print(Format.standard(problem, file))
|
||||
|
||||
if return_code == 0 and problem.level == 'error':
|
||||
return_code = 1
|
||||
|
||||
if not first and args.format != 'parsable':
|
||||
print('')
|
||||
except EnvironmentError as e:
|
||||
print(e)
|
||||
return_code = -1
|
||||
|
||||
sys.exit(return_code)
|
||||
@@ -1,38 +0,0 @@
|
||||
# -*- 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/>.
|
||||
|
||||
|
||||
def parsable_format(problem, filename):
|
||||
return ('%(file)s:%(line)s:%(column)s: [%(level)s] %(message)s' %
|
||||
{'file': filename,
|
||||
'line': problem.line,
|
||||
'column': problem.column,
|
||||
'level': problem.level,
|
||||
'message': problem.message})
|
||||
|
||||
|
||||
def standard_format(problem, filename):
|
||||
line = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column)
|
||||
line += max(20 - len(line), 0) * ' '
|
||||
if problem.level == 'warning':
|
||||
line += '\033[33m%s\033[0m' % problem.level
|
||||
else:
|
||||
line += '\033[31m%s\033[0m' % problem.level
|
||||
line += max(38 - len(line), 0) * ' '
|
||||
line += problem.desc
|
||||
if problem.rule:
|
||||
line += ' \033[2m(%s)\033[0m' % problem.rule
|
||||
return line
|
||||
@@ -30,11 +30,13 @@ def check(conf, token, prev, next, context):
|
||||
for comment in get_comments_between_tokens(token, next):
|
||||
if (conf['min-spaces-from-content'] != -1 and
|
||||
not isinstance(token, yaml.StreamStartToken) and
|
||||
comment.line == token.end_mark.line + 1 and
|
||||
comment.pointer - token.end_mark.pointer <
|
||||
conf['min-spaces-from-content']):
|
||||
yield LintProblem(comment.line, comment.column,
|
||||
'too few spaces before comment')
|
||||
comment.line == token.end_mark.line + 1):
|
||||
# Sometimes token end marks are on the next line
|
||||
if token.end_mark.buffer[token.end_mark.pointer - 1] != '\n':
|
||||
if (comment.pointer - token.end_mark.pointer <
|
||||
conf['min-spaces-from-content']):
|
||||
yield LintProblem(comment.line, comment.column,
|
||||
'too few spaces before comment')
|
||||
|
||||
if (conf['require-starting-space'] and
|
||||
comment.pointer + 1 < len(comment.buffer) and
|
||||
|
||||
@@ -33,7 +33,10 @@ def spaces_after(token, prev, next, min=-1, max=-1,
|
||||
|
||||
def spaces_before(token, prev, next, min=-1, max=-1,
|
||||
min_desc=None, max_desc=None):
|
||||
if prev is not None and prev.end_mark.line == token.start_mark.line:
|
||||
if (prev is not None and prev.end_mark.line == token.start_mark.line and
|
||||
# Discard tokens (only scalars?) that end at the start of next line
|
||||
(prev.end_mark.pointer == 0 or
|
||||
prev.end_mark.buffer[prev.end_mark.pointer - 1] != '\n')):
|
||||
spaces = token.start_mark.pointer - prev.end_mark.pointer
|
||||
if max != - 1 and spaces > max:
|
||||
return LintProblem(token.start_mark.line + 1,
|
||||
|
||||
@@ -35,6 +35,72 @@ class Parent(object):
|
||||
self.explicit_key = False
|
||||
|
||||
|
||||
def check_scalar_indentation(conf, token, context):
|
||||
if token.start_mark.line == token.end_mark.line:
|
||||
return
|
||||
|
||||
if token.plain:
|
||||
expected_indent = token.start_mark.column
|
||||
elif token.style in ('"', "'"):
|
||||
expected_indent = token.start_mark.column + 1
|
||||
elif token.style in ('>', '|'):
|
||||
if context['stack'][-1].type == B_SEQ:
|
||||
# - >
|
||||
# multi
|
||||
# line
|
||||
expected_indent = token.start_mark.column + conf['spaces']
|
||||
elif context['stack'][-1].type == KEY:
|
||||
assert context['stack'][-1].explicit_key
|
||||
# - ? >
|
||||
# multi-line
|
||||
# key
|
||||
# : >
|
||||
# multi-line
|
||||
# value
|
||||
expected_indent = token.start_mark.column + conf['spaces']
|
||||
elif context['stack'][-1].type == VAL:
|
||||
if token.start_mark.line + 1 > context['cur_line']:
|
||||
# - key:
|
||||
# >
|
||||
# multi
|
||||
# line
|
||||
expected_indent = context['stack'][-1].indent + conf['spaces']
|
||||
elif context['stack'][-2].explicit_key:
|
||||
# - ? key
|
||||
# : >
|
||||
# multi-line
|
||||
# value
|
||||
expected_indent = token.start_mark.column + conf['spaces']
|
||||
else:
|
||||
# - key: >
|
||||
# multi
|
||||
# line
|
||||
expected_indent = context['stack'][-2].indent + conf['spaces']
|
||||
else:
|
||||
expected_indent = context['stack'][-1].indent + conf['spaces']
|
||||
|
||||
line_no = token.start_mark.line + 1
|
||||
|
||||
line_start = token.start_mark.pointer
|
||||
while True:
|
||||
line_start = token.start_mark.buffer.find(
|
||||
'\n', line_start, token.end_mark.pointer - 1) + 1
|
||||
if line_start == 0:
|
||||
break
|
||||
line_no += 1
|
||||
|
||||
indent = 0
|
||||
while token.start_mark.buffer[line_start + indent] == ' ':
|
||||
indent += 1
|
||||
if token.start_mark.buffer[line_start + indent] == '\n':
|
||||
continue
|
||||
|
||||
if indent != expected_indent:
|
||||
yield LintProblem(line_no, indent + 1,
|
||||
'wrong indentation: expected %d but found %d' %
|
||||
(expected_indent, indent))
|
||||
|
||||
|
||||
def check(conf, token, prev, next, context):
|
||||
if 'stack' not in context:
|
||||
context['stack'] = [Parent(ROOT, 0)]
|
||||
@@ -42,11 +108,13 @@ def check(conf, token, prev, next, context):
|
||||
|
||||
# Step 1: Lint
|
||||
|
||||
if (not isinstance(token, (yaml.StreamStartToken, yaml.StreamEndToken)) and
|
||||
not isinstance(token, yaml.BlockEndToken) and
|
||||
not (isinstance(token, yaml.ScalarToken) and token.value == '') and
|
||||
token.start_mark.line + 1 > context['cur_line']):
|
||||
needs_lint = (
|
||||
not isinstance(token, (yaml.StreamStartToken, yaml.StreamEndToken)) and
|
||||
not isinstance(token, yaml.BlockEndToken) and
|
||||
not (isinstance(token, yaml.ScalarToken) and token.value == '') and
|
||||
token.start_mark.line + 1 > context['cur_line'])
|
||||
|
||||
if needs_lint:
|
||||
found_indentation = token.start_mark.column
|
||||
expected = context['stack'][-1].indent
|
||||
|
||||
@@ -63,10 +131,17 @@ def check(conf, token, prev, next, context):
|
||||
'wrong indentation: expected %d but found %d' %
|
||||
(expected, found_indentation))
|
||||
|
||||
if isinstance(token, yaml.ScalarToken):
|
||||
for problem in check_scalar_indentation(conf, token, context):
|
||||
yield problem
|
||||
|
||||
# Step 2.a:
|
||||
|
||||
if needs_lint:
|
||||
context['cur_line_indent'] = found_indentation
|
||||
context['cur_line'] = token.end_mark.line + 1
|
||||
|
||||
# Step 2: Update state
|
||||
# Step 2.b: Update state
|
||||
|
||||
if isinstance(token, yaml.BlockMappingStartToken):
|
||||
assert isinstance(next, yaml.KeyToken)
|
||||
|
||||
Reference in New Issue
Block a user