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
|
```sh
|
||||||
yamllint -c ~/myconfig my_file.yml
|
yamllint .
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yamllint -c ~/myconfig file.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# To output a format parsable (by editors like Vim, emacs, etc.)
|
# To output a format parsable (by editors like Vim, emacs, etc.)
|
||||||
yamllint -f parsable my_file.yml
|
yamllint -f parsable file.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|||||||
62
bin/yamllint
62
bin/yamllint
@@ -15,68 +15,10 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
import os.path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import argparse
|
from yamllint import cli
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser(prog=APP_NAME,
|
cli.run(sys.argv[1:])
|
||||||
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)
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class ColonTestCase(RuleTestCase):
|
|||||||
'...\n', conf, problem=(3, 8))
|
'...\n', conf, problem=(3, 8))
|
||||||
|
|
||||||
def test_before_with_explicit_block_mappings(self):
|
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'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
' ? key\n'
|
' ? key\n'
|
||||||
@@ -129,6 +129,30 @@ class ColonTestCase(RuleTestCase):
|
|||||||
' ? key\n'
|
' ? key\n'
|
||||||
' : value\n'
|
' : value\n'
|
||||||
'...\n', conf, problem=(2, 7))
|
'...\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):
|
def test_after_enabled(self):
|
||||||
conf = 'colons: {max-spaces-before: -1, max-spaces-after: 1}'
|
conf = 'colons: {max-spaces-before: -1, max-spaces-after: 1}'
|
||||||
|
|||||||
@@ -131,3 +131,19 @@ class CommentsTestCase(RuleTestCase):
|
|||||||
' require-starting-space: yes\n'
|
' require-starting-space: yes\n'
|
||||||
' min-spaces-from-content: 2\n')
|
' min-spaces-from-content: 2\n')
|
||||||
self.check('# comment\n', conf)
|
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'
|
' 2,\n'
|
||||||
' 3\n'
|
' 3\n'
|
||||||
']\n', conf, problem1=(4, 4), problem2=(5, 2))
|
']\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_NAME = 'yamllint'
|
||||||
APP_VERSION = '0.3.0'
|
APP_VERSION = '0.4.0'
|
||||||
APP_DESCRIPTION = 'A linter for YAML files.'
|
APP_DESCRIPTION = 'A linter for YAML files.'
|
||||||
|
|
||||||
__author__ = 'Adrien Vergé'
|
__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):
|
for comment in get_comments_between_tokens(token, next):
|
||||||
if (conf['min-spaces-from-content'] != -1 and
|
if (conf['min-spaces-from-content'] != -1 and
|
||||||
not isinstance(token, yaml.StreamStartToken) and
|
not isinstance(token, yaml.StreamStartToken) and
|
||||||
comment.line == token.end_mark.line + 1 and
|
comment.line == token.end_mark.line + 1):
|
||||||
comment.pointer - token.end_mark.pointer <
|
# Sometimes token end marks are on the next line
|
||||||
conf['min-spaces-from-content']):
|
if token.end_mark.buffer[token.end_mark.pointer - 1] != '\n':
|
||||||
yield LintProblem(comment.line, comment.column,
|
if (comment.pointer - token.end_mark.pointer <
|
||||||
'too few spaces before comment')
|
conf['min-spaces-from-content']):
|
||||||
|
yield LintProblem(comment.line, comment.column,
|
||||||
|
'too few spaces before comment')
|
||||||
|
|
||||||
if (conf['require-starting-space'] and
|
if (conf['require-starting-space'] and
|
||||||
comment.pointer + 1 < len(comment.buffer) 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,
|
def spaces_before(token, prev, next, min=-1, max=-1,
|
||||||
min_desc=None, max_desc=None):
|
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
|
spaces = token.start_mark.pointer - prev.end_mark.pointer
|
||||||
if max != - 1 and spaces > max:
|
if max != - 1 and spaces > max:
|
||||||
return LintProblem(token.start_mark.line + 1,
|
return LintProblem(token.start_mark.line + 1,
|
||||||
|
|||||||
@@ -35,6 +35,72 @@ class Parent(object):
|
|||||||
self.explicit_key = False
|
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):
|
def check(conf, token, prev, next, context):
|
||||||
if 'stack' not in context:
|
if 'stack' not in context:
|
||||||
context['stack'] = [Parent(ROOT, 0)]
|
context['stack'] = [Parent(ROOT, 0)]
|
||||||
@@ -42,11 +108,13 @@ def check(conf, token, prev, next, context):
|
|||||||
|
|
||||||
# Step 1: Lint
|
# Step 1: Lint
|
||||||
|
|
||||||
if (not isinstance(token, (yaml.StreamStartToken, yaml.StreamEndToken)) and
|
needs_lint = (
|
||||||
not isinstance(token, yaml.BlockEndToken) and
|
not isinstance(token, (yaml.StreamStartToken, yaml.StreamEndToken)) and
|
||||||
not (isinstance(token, yaml.ScalarToken) and token.value == '') and
|
not isinstance(token, yaml.BlockEndToken) and
|
||||||
token.start_mark.line + 1 > context['cur_line']):
|
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
|
found_indentation = token.start_mark.column
|
||||||
expected = context['stack'][-1].indent
|
expected = context['stack'][-1].indent
|
||||||
|
|
||||||
@@ -63,10 +131,17 @@ def check(conf, token, prev, next, context):
|
|||||||
'wrong indentation: expected %d but found %d' %
|
'wrong indentation: expected %d but found %d' %
|
||||||
(expected, found_indentation))
|
(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_indent'] = found_indentation
|
||||||
context['cur_line'] = token.end_mark.line + 1
|
context['cur_line'] = token.end_mark.line + 1
|
||||||
|
|
||||||
# Step 2: Update state
|
# Step 2.b: Update state
|
||||||
|
|
||||||
if isinstance(token, yaml.BlockMappingStartToken):
|
if isinstance(token, yaml.BlockMappingStartToken):
|
||||||
assert isinstance(next, yaml.KeyToken)
|
assert isinstance(next, yaml.KeyToken)
|
||||||
|
|||||||
Reference in New Issue
Block a user