Compare commits

..

12 Commits

Author SHA1 Message Date
Adrien Vergé
d8d1d92545 yamllint version 1.1.0 2016-03-04 17:03:38 +01:00
Adrien Vergé
7688567faa cli: Add the -d option to provide inline conf 2016-03-04 16:53:26 +01:00
Adrien Vergé
4e188f8801 Conf: Add a new pre-defined conf 'relaxed'
It is more tolerant than 'default'.
2016-03-04 16:50:40 +01:00
Adrien Vergé
5693b1dddf Rules: indentation: Add 'consistent' option for 'spaces'
Using `spaces: consistent` allows any number of spaces, as long as it
remains the same within the file.
2016-03-04 16:03:53 +01:00
Adrien Vergé
fa420499c7 Config: Allow types in multiple choices
For instance, allow rules with:

    CONF = {'choice': (int, 'hardcoded-string'),
            'string-or-bool': (str, bool)}
2016-03-04 16:03:46 +01:00
Adrien Vergé
adefe38a0d yamllint version 1.0.4 2016-03-04 12:48:31 +01:00
Adrien Vergé
7e11082353 Distribution: Restore spec examples in package_data
Put `tests/yaml-1.2-spec-examples/*` back in `setup.py`'s `package_data`
because they need to be installed when running `python setup.py build`,
so Debian packaging script `dh_auto_test -O--buildsystem=pybuild`
doesn't fail.

See also commit e6dc67f.
2016-03-04 12:33:56 +01:00
Adrien Vergé
29c1c60143 Tests: Use absolute path to spec examples 2016-03-04 12:15:26 +01:00
Adrien Vergé
b879e9a98f Distribution: Add LICENSE and README to manifest 2016-02-26 09:57:06 +01:00
Adrien Vergé
5956b20545 yamllint version 1.0.3 2016-02-25 14:48:13 +01:00
Adrien Vergé
10ad302e2f Tests: Explicit encoding for spec examples
YAML specification examples contain unusual characters, let's explicit
`encoding='utf-8'` to prevent bugs.
2016-02-25 10:44:05 +01:00
Adrien Vergé
73d9322813 linter: Test run on str, unicode, bytes and stream
Previously it was not tested, and broke on Python 2 `unicode` inputs.
2016-02-25 10:41:17 +01:00
14 changed files with 384 additions and 57 deletions

View File

@@ -1,2 +1,3 @@
include LICENSE
include README.rst
include docs/*
include tests/yaml-1.2-spec-examples/*

View File

@@ -57,6 +57,9 @@ Usage
.. code:: bash
# Use a pre-defined lint configuration
yamllint -d relaxed file.yml
# Use a custom lint configuration
yamllint -c ~/myconfig file.yml

View File

@@ -8,7 +8,7 @@ settings can be gathered in a configuration file.
To use a custom configuration file, either name it ``.yamllint`` in your working
directory, or use the ``-c`` option:
::
.. code:: bash
yamllint -c ~/myconfig file.yml
@@ -22,6 +22,15 @@ 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.
It can be chosen using:
.. code:: bash
yamllint -d relaxed file.yml
Extending the default configuration
-----------------------------------
@@ -63,6 +72,21 @@ strict on block sequences indentation:
indentation:
indent-sequences: whatever
Custom configuration without a config file
------------------------------------------
It is possible -- although not recommended -- to pass custom configuration
options to yamllint with the ``-d`` (short for ``--config-data``) option.
Its content can either be the name of a pre-defined conf (example: ``default``
or ``relaxed``) or a serialized YAML object describing the configuration.
For instance:
.. code:: bash
yamllint -d "{extends: relaxed, rules: {line-length: {max: 120}}}" file.yml
Errors and warnings
-------------------

View File

@@ -44,7 +44,8 @@ setup(
packages=find_packages(),
entry_points={'console_scripts': ['yamllint=yamllint.cli:run']},
package_data={'yamllint': ['conf/*.yml']},
package_data={'yamllint': ['conf/*.yml'],
'tests': ['yaml-1.2-spec-examples/*']},
install_requires=['pyyaml'],
tests_require=['nose'],
test_suite='nose.collector',

View File

@@ -496,6 +496,87 @@ class IndentationTestCase(RuleTestCase):
' date: 1991\n'
'...\n', conf)
def test_consistent(self):
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: whatever}\n'
'document-start: disable\n')
self.check('---\n'
'object:\n'
' k1:\n'
' - a\n'
' - b\n'
' k2: v2\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
self.check('---\n'
'object:\n'
' k1:\n'
' - a\n'
' - b\n'
' k2: v2\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
self.check('---\n'
'object:\n'
' k1:\n'
' - a\n'
' - b\n'
' k2: v2\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
self.check('first is not indented:\n'
' value is indented\n', conf)
self.check('first is not indented:\n'
' value:\n'
' is indented\n', conf)
self.check('- first is already indented:\n'
' value is indented too\n', conf)
self.check('- first is already indented:\n'
' value:\n'
' is indented too\n', conf)
self.check('- first is already indented:\n'
' value:\n'
' is indented too\n', conf, problem=(3, 14))
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem=(7, 5))
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf)
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf)
def test_indent_sequences_whatever(self):
conf = 'indentation: {spaces: 4, indent-sequences: whatever}'
self.check('---\n'
@@ -1528,3 +1609,39 @@ class ScalarIndentationTestCase(RuleTestCase):
' paragraph gap, \\n and\n'
' spaces.\n', conf,
problem1=(3, 6), problem2=(5, 7), problem3=(6, 8))
def test_consistent(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\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, problem=(2, 3))
self.check('a key: multi\n'
' line\n', conf, problem=(2, 9))
self.check('a key:\n'
' multi\n'
' line\n', conf, problem=(3, 4))
self.check('- C code: void main() {\n'
' printf("foo");\n'
' }\n', conf, problem=(2, 15))
self.check('- C code:\n'
' void main() {\n'
' printf("foo");\n'
' }\n', conf, problem=(3, 9))
self.check('>\n'
' multi\n'
' line\n', conf)
self.check('>\n'
' multi\n'
' line\n', conf)
self.check('>\n'
' multi\n'
' line\n', conf, problem=(3, 7))

View File

@@ -58,6 +58,53 @@ class SimpleConfigTestCase(unittest.TestCase):
' max-spaces-after: 1\n'
' abcdef: yes\n')
def test_validate_rule_conf(self):
class Rule(object):
ID = 'fake'
self.assertEqual(config.validate_rule_conf(Rule, False), False)
self.assertEqual(config.validate_rule_conf(Rule, 'disable'), False)
config.validate_rule_conf(Rule, {'level': 'error'})
config.validate_rule_conf(Rule, {'level': 'warning'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'level': 'warn'})
Rule.CONF = {'length': int}
config.validate_rule_conf(Rule, {'length': 8})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'height': 8})
Rule.CONF = {'a': bool, 'b': int}
config.validate_rule_conf(Rule, {'a': True, 'b': 0})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'a': True})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'b': 0})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'a': 1, 'b': 0})
Rule.CONF = {'choice': (True, 88, 'str')}
config.validate_rule_conf(Rule, {'choice': True})
config.validate_rule_conf(Rule, {'choice': 88})
config.validate_rule_conf(Rule, {'choice': 'str'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': False})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 99})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 'abc'})
Rule.CONF = {'choice': (int, 'hardcoded')}
config.validate_rule_conf(Rule, {'choice': 42})
config.validate_rule_conf(Rule, {'choice': 'hardcoded'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': False})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 'abc'})
class ExtendedConfigTestCase(unittest.TestCase):
def test_extend_add_rule(self):

46
tests/test_linter.py Normal file
View File

@@ -0,0 +1,46 @@
# -*- 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 io
import unittest
from yamllint.config import YamlLintConfig
from yamllint import linter
class LinterTestCase(unittest.TestCase):
def fake_config(self):
return YamlLintConfig('extends: default')
def test_run_on_string(self):
linter.run('test: document', self.fake_config())
def test_run_on_bytes(self):
linter.run(b'test: document', self.fake_config())
def test_run_on_unicode(self):
linter.run(u'test: document', self.fake_config())
def test_run_on_stream(self):
linter.run(io.StringIO(u'hello'), self.fake_config())
def test_run_on_int(self):
self.assertRaises(TypeError, linter.run, 42, self.fake_config())
def test_run_on_list(self):
self.assertRaises(TypeError, linter.run,
['h', 'e', 'l', 'l', 'o'], self.fake_config())

View File

@@ -14,6 +14,7 @@
# 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 io import open
import os
from tests.common import RuleTestCase
@@ -120,7 +121,8 @@ conf_overrides = {
'example-10.2': ('indentation: {indent-sequences: no}\n'),
}
files = os.listdir('tests/yaml-1.2-spec-examples')
files = os.listdir(os.path.join(os.path.dirname(os.path.realpath(__file__)),
'yaml-1.2-spec-examples'))
assert len(files) == 132
@@ -178,7 +180,7 @@ for file in files:
if file in pyyaml_blacklist:
continue
with open('tests/yaml-1.2-spec-examples/' + file) as f:
with open('tests/yaml-1.2-spec-examples/' + file, encoding='utf-8') as f:
conf = conf_general + conf_overrides.get(file, '')
setattr(SpecificationTestCase, 'test_' + file,
_gen_test(f.read(), conf))

View File

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

View File

@@ -66,8 +66,11 @@ def run(argv=None):
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('-c', '--config-file', dest='config_file',
action='store', help='path to a custom configuration')
parser.add_argument('-d', '--config-data', dest='config_data',
action='store',
help='custom configuration (as YAML source)')
parser.add_argument('-f', '--format',
choices=('parsable', 'standard'), default='standard',
help='format for parsing output')
@@ -78,8 +81,17 @@ def run(argv=None):
args = parser.parse_args(argv)
if args.config_file is not None and args.config_data is not None:
print('Options --config-file and --config-data cannot be used '
'simultaneously.', file=sys.stderr)
sys.exit(-1)
try:
if args.config_file is not None:
if args.config_data is not None:
if ':' not in args.config_data:
args.config_data = 'extends: ' + args.config_data
conf = YamlLintConfig(content=args.config_data)
elif args.config_file is not None:
conf = YamlLintConfig(file=args.config_file)
elif os.path.isfile('.yamllint'):
conf = YamlLintConfig(file='.yamllint')

29
yamllint/conf/relaxed.yml Normal file
View File

@@ -0,0 +1,29 @@
---
extends: default
rules:
braces:
level: warning
max-spaces-inside: 1
brackets:
level: warning
max-spaces-inside: 1
colons:
level: warning
commas:
level: warning
comments: disable
comments-indentation: disable
document-end: disable
document-start: disable
empty-lines:
level: warning
hyphens: disable
indentation:
spaces: consistent
indent-sequences: whatever
check-multi-line-strings: no
line-length:
level: warning
max: 100

View File

@@ -100,7 +100,8 @@ def validate_rule_conf(rule, conf):
'invalid config: unknown option "%s" for rule "%s"' %
(optkey, rule.ID))
if type(options[optkey]) == tuple:
if conf[optkey] not in options[optkey]:
if (conf[optkey] not in options[optkey] and
type(conf[optkey]) not in options[optkey]):
raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be in %s'
% (optkey, rule.ID, options[optkey]))

View File

@@ -128,7 +128,7 @@ def run(input, conf):
:param input: buffer, string or stream to read from
:param conf: yamllint configuration object
"""
if type(input) == str:
if type(input) in (type(b''), type(u'')): # compat with Python 2 & 3
return _run(input, conf)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
# We need to have everything in memory to parse correctly

View File

@@ -19,7 +19,10 @@ Use this rule to control the indentation.
.. rubric:: Options
* ``spaces`` defines the number of spaces that represent an indentation level.
* ``spaces`` defines the indentation width, in spaces. Set either to an integer
(e.g. ``2`` or ``4``, representing the number of spaces in an indentation
level) or to ``consistent`` to allow any number, as long as it remains the
same within the file.
* ``indent-sequences`` defines whether block sequences should be indented or
not (when in a mapping, this indentation is not mandatory -- some people
perceive the ``-`` as part of the indentation). Possible values: ``yes``,
@@ -73,6 +76,28 @@ Use this rule to control the indentation.
- haystack:
needle
#. With ``indentation: {spaces: consistent}``
the following code snippet would **PASS**:
::
history:
- name: Unix
date: 1969
- name: Linux
date: 1991
nest:
recurse:
- haystack:
needle
the following code snippet would **FAIL**:
::
some:
Russian:
dolls
#. With ``indentation: {spaces: 2, indent-sequences: no}``
the following code snippet would **PASS**:
@@ -150,7 +175,7 @@ from yamllint.rules.common import is_explicit_key, get_real_end_line
ID = 'indentation'
TYPE = 'token'
CONF = {'spaces': int,
CONF = {'spaces': (int, 'consistent'),
'indent-sequences': (True, False, 'whatever'),
'check-multi-line-strings': bool}
@@ -174,45 +199,53 @@ 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
def compute_expected_indent(found_indent):
def detect_indent(base_indent):
if type(context['spaces']) is not int:
context['spaces'] = found_indent - base_indent
return base_indent + context['spaces']
if token.plain:
return token.start_mark.column
elif token.style in ('"', "'"):
return token.start_mark.column + 1
elif token.style in ('>', '|'):
if context['stack'][-1].type == B_SEQ:
# - >
# multi
# line
return detect_indent(token.start_mark.column)
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']
return detect_indent(token.start_mark.column)
elif context['stack'][-1].type == VAL:
if token.start_mark.line + 1 > context['cur_line']:
# - key:
# >
# multi
# line
return detect_indent(context['stack'][-1].indent)
elif context['stack'][-2].explicit_key:
# - ? key
# : >
# multi-line
# value
return detect_indent(token.start_mark.column)
else:
# - key: >
# multi
# line
return detect_indent(context['stack'][-2].indent)
else:
# - key: >
# multi
# line
expected_indent = context['stack'][-2].indent + conf['spaces']
else:
expected_indent = context['stack'][-1].indent + conf['spaces']
return detect_indent(context['stack'][-1].indent)
expected_indent = None
line_no = token.start_mark.line + 1
@@ -230,6 +263,9 @@ def check_scalar_indentation(conf, token, context):
if token.start_mark.buffer[line_start + indent] == '\n':
continue
if expected_indent is None:
expected_indent = compute_expected_indent(indent)
if indent != expected_indent:
yield LintProblem(line_no, indent + 1,
'wrong indentation: expected %d but found %d' %
@@ -240,6 +276,7 @@ def check(conf, token, prev, next, nextnext, context):
if 'stack' not in context:
context['stack'] = [Parent(ROOT, 0)]
context['cur_line'] = -1
context['spaces'] = conf['spaces']
# Step 1: Lint
@@ -250,6 +287,12 @@ def check(conf, token, prev, next, nextnext, context):
first_in_line = (is_visible and
token.start_mark.line + 1 > context['cur_line'])
def detect_indent(next):
if type(context['spaces']) is not int:
context['spaces'] = (next.start_mark.column -
context['stack'][-1].indent)
return context['spaces']
if first_in_line:
found_indentation = token.start_mark.column
expected = context['stack'][-1].indent
@@ -260,7 +303,7 @@ def check(conf, token, prev, next, nextnext, context):
elif (context['stack'][-1].type == KEY and
context['stack'][-1].explicit_key and
not isinstance(token, yaml.ValueToken)):
expected += conf['spaces']
expected += detect_indent(token)
if found_indentation != expected:
yield LintProblem(token.start_mark.line + 1, found_indentation + 1,
@@ -294,7 +337,7 @@ def check(conf, token, prev, next, nextnext, context):
# - ?
# a
# : 1
indent = token.start_mark.column + conf['spaces']
indent = token.start_mark.column + detect_indent(next)
context['stack'].append(Parent(B_MAP, indent))
@@ -306,7 +349,7 @@ def check(conf, token, prev, next, nextnext, context):
# - {
# a: 1, b: 2
# }
indent = context['cur_line_indent'] + conf['spaces']
indent = context['cur_line_indent'] + detect_indent(next)
context['stack'].append(Parent(F_MAP, indent,
line_indent=context['cur_line_indent']))
@@ -340,7 +383,7 @@ def check(conf, token, prev, next, nextnext, context):
# -
# key:
# value
indent = token.start_mark.column + conf['spaces']
indent = token.start_mark.column + detect_indent(next)
context['stack'].append(Parent(B_ENT, indent))
@@ -352,7 +395,7 @@ def check(conf, token, prev, next, nextnext, context):
# - [
# a, b
# ]
indent = context['cur_line_indent'] + conf['spaces']
indent = context['cur_line_indent'] + detect_indent(next)
context['stack'].append(Parent(F_SEQ, indent,
line_indent=context['cur_line_indent']))
@@ -390,7 +433,7 @@ def check(conf, token, prev, next, nextnext, context):
# ? k
# :
# value
indent = context['stack'][-1].indent + conf['spaces']
indent = context['stack'][-1].indent + detect_indent(next)
elif next.start_mark.line == prev.start_mark.line:
# k: value
indent = next.start_mark.column
@@ -404,7 +447,7 @@ def check(conf, token, prev, next, nextnext, context):
if conf['indent-sequences'] is False:
indent = context['stack'][-1].indent
elif conf['indent-sequences'] is True:
indent = context['stack'][-1].indent + conf['spaces']
indent = context['stack'][-1].indent + detect_indent(next)
else: # 'whatever'
if next.start_mark.column == context['stack'][-1].indent:
# key:
@@ -415,11 +458,12 @@ def check(conf, token, prev, next, nextnext, context):
# key:
# - e1
# - e2
indent = context['stack'][-1].indent + conf['spaces']
indent = (context['stack'][-1].indent +
detect_indent(next))
else:
# k:
# value
indent = context['stack'][-1].indent + conf['spaces']
indent = context['stack'][-1].indent + detect_indent(next)
context['stack'].append(Parent(VAL, indent))