diff --git a/CHANGELOG.rst b/CHANGELOG.rst index de0e50c..105fccd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,14 @@ Changelog ========= +1.16.0 (2019-06-07) +------------------- + +- Add FreeBSD installation instructions +- Fix the ``line`` rule to correctly handle DOS new lines +- Add the ``allowed-values`` option to the ``truthy`` rule +- Allow configuration options to be a list of enums + 1.15.0 (2019-02-11) ------------------- diff --git a/README.rst b/README.rst index 7f59956..0bebb78 100644 --- a/README.rst +++ b/README.rst @@ -38,38 +38,16 @@ Screenshot Installation ^^^^^^^^^^^^ -On Fedora / CentOS (note: `EPEL `_ is -required on CentOS): - -.. code:: bash - - sudo dnf install yamllint - -On Debian 8+ / Ubuntu 16.04+: - -.. code:: bash - - sudo apt-get install yamllint - -On FreeBSD: - -.. code:: sh - - pkg install py27-yamllint - pkg install py36-yamllint - -On Mac OS 10.11+: - -.. code:: bash - - brew install yamllint - -Alternatively using pip, the Python package manager: +Using pip, the Python package manager: .. code:: bash pip install --user yamllint +yamllint is also packaged for all major operating systems, see installation +examples (``dnf``, ``apt-get``...) `in the documentation +`_. + Usage ^^^^^ diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 427a9f3..9cf5f17 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -4,7 +4,8 @@ Quickstart Installing yamllint ------------------- -On Fedora / CentOS: +On Fedora / CentOS (note: `EPEL `_ is +required on CentOS): .. code:: bash @@ -22,6 +23,18 @@ On Mac OS 10.11+: brew install yamllint +On FreeBSD: + +.. code:: sh + + pkg install py36-yamllint + +On OpenBSD: + +.. code:: sh + + doas pkg_add py3-yamllint + Alternatively using pip, the Python package manager: .. code:: bash diff --git a/tests/rules/test_truthy.py b/tests/rules/test_truthy.py index b0402ed..82e4f6c 100644 --- a/tests/rules/test_truthy.py +++ b/tests/rules/test_truthy.py @@ -49,6 +49,54 @@ class TruthyTestCase(RuleTestCase): problem3=(7, 3), problem4=(7, 7), problem5=(8, 3), problem6=(8, 7)) + def test_different_allowed_values(self): + conf = ('truthy:\n' + ' allowed-values: ["yes", "no"]\n') + self.check('---\n' + 'key1: foo\n' + 'key2: yes\n' + 'key3: bar\n' + 'key4: no\n', conf) + self.check('---\n' + 'key1: true\n' + 'key2: Yes\n' + 'key3: false\n' + 'key4: no\n' + 'key5: yes\n', + conf, + problem1=(2, 7), problem2=(3, 7), + problem3=(4, 7)) + + def test_combined_allowed_values(self): + conf = ('truthy:\n' + ' allowed-values: ["yes", "no", "true", "false"]\n') + self.check('---\n' + 'key1: foo\n' + 'key2: yes\n' + 'key3: bar\n' + 'key4: no\n', conf) + self.check('---\n' + 'key1: true\n' + 'key2: Yes\n' + 'key3: false\n' + 'key4: no\n' + 'key5: yes\n', + conf, problem1=(3, 7)) + + def test_no_allowed_values(self): + conf = ('truthy:\n' + ' allowed-values: []\n') + self.check('---\n' + 'key1: foo\n' + 'key2: bar\n', conf) + self.check('---\n' + 'key1: true\n' + 'key2: yes\n' + 'key3: false\n' + 'key4: no\n', conf, + problem1=(2, 7), problem2=(3, 7), + problem3=(4, 7), problem4=(5, 7)) + def test_explicit_types(self): conf = 'truthy: enable\n' self.check('---\n' diff --git a/tests/test_config.py b/tests/test_config.py index f5d9402..d676382 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -174,6 +174,25 @@ class SimpleConfigTestCase(unittest.TestCase): self.assertRaises(config.YamlLintConfigError, config.validate_rule_conf, Rule, {'choice': 'abc'}) + Rule.CONF = {'multiple': ['item1', 'item2', 'item3']} + Rule.DEFAULT = {'multiple': ['item1']} + config.validate_rule_conf(Rule, {'multiple': []}) + config.validate_rule_conf(Rule, {'multiple': ['item2']}) + config.validate_rule_conf(Rule, {'multiple': ['item2', 'item3']}) + config.validate_rule_conf(Rule, {}) + self.assertRaises(config.YamlLintConfigError, + config.validate_rule_conf, Rule, + {'multiple': 'item1'}) + self.assertRaises(config.YamlLintConfigError, + config.validate_rule_conf, Rule, + {'multiple': ['']}) + self.assertRaises(config.YamlLintConfigError, + config.validate_rule_conf, Rule, + {'multiple': ['item1', 4]}) + self.assertRaises(config.YamlLintConfigError, + config.validate_rule_conf, Rule, + {'multiple': ['item4']}) + class ExtendedConfigTestCase(unittest.TestCase): def test_extend_on_object(self): diff --git a/yamllint/__init__.py b/yamllint/__init__.py index 91c19eb..932054e 100644 --- a/yamllint/__init__.py +++ b/yamllint/__init__.py @@ -22,7 +22,7 @@ indentation, etc.""" APP_NAME = 'yamllint' -APP_VERSION = '1.15.0' +APP_VERSION = '1.16.0' APP_DESCRIPTION = __doc__ __author__ = u'Adrien Vergé' diff --git a/yamllint/config.py b/yamllint/config.py index b4a7c1d..2cafe8a 100644 --- a/yamllint/config.py +++ b/yamllint/config.py @@ -134,12 +134,26 @@ def validate_rule_conf(rule, conf): raise YamlLintConfigError( 'invalid config: unknown option "%s" for rule "%s"' % (optkey, rule.ID)) + # Example: CONF = {option: (bool, 'mixed')} + # → {option: true} → {option: mixed} if isinstance(options[optkey], tuple): 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])) + # Example: CONF = {option: ['flag1', 'flag2']} + # → {option: [flag1]} → {option: [flag1, flag2]} + elif isinstance(options[optkey], list): + if (type(conf[optkey]) is not list or + any(flag not in options[optkey] + for flag in conf[optkey])): + raise YamlLintConfigError( + ('invalid config: option "%s" of "%s" should only ' + 'contain values in %s') + % (optkey, rule.ID, str(options[optkey]))) + # Example: CONF = {option: int} + # → {option: 42} else: if not isinstance(conf[optkey], options[optkey]): raise YamlLintConfigError( diff --git a/yamllint/rules/truthy.py b/yamllint/rules/truthy.py index b738dc2..3cf058b 100644 --- a/yamllint/rules/truthy.py +++ b/yamllint/rules/truthy.py @@ -15,13 +15,22 @@ # along with this program. If not, see . """ -Use this rule to forbid non-explictly typed truthy values other than ``true`` -and ``false``, for example ``YES``, ``False`` and ``off``. +Use this rule to forbid non-explictly typed truthy values other than allowed +ones (by default: ``true`` and ``false``), for example ``YES`` or ``off``. This can be useful to prevent surprises from YAML parsers transforming ``[yes, FALSE, Off]`` into ``[true, false, false]`` or ``{y: 1, yes: 2, on: 3, true: 4, True: 5}`` into ``{y: 1, true: 5}``. +.. rubric:: Options + +* ``allowed-values`` defines the list of truthy values which will be ignored + during linting. The default is ``['true', 'false']``, but can be changed to + any list containing: ``'TRUE'``, ``'True'``, ``'true'``, ``'FALSE'``, + ``'False'``, ``'false'``, ``'YES'``, ``'Yes'``, ``'yes'``, ``'NO'``, + ``'No'``, ``'no'``, ``'ON'``, ``'On'``, ``'on'``, ``'OFF'``, ``'Off'``, + ``'off'``. + .. rubric:: Examples #. With ``truthy: {}`` @@ -63,29 +72,55 @@ This can be useful to prevent surprises from YAML parsers transforming yes: 1 on: 2 True: 3 + +#. With ``truthy: {allowed-values: ["yes", "no"]}`` + + the following code snippet would **PASS**: + :: + + - yes + - no + - "true" + - 'false' + - foo + - bar + + the following code snippet would **FAIL**: + :: + + - true + - false + - on + - off """ import yaml from yamllint.linter import LintProblem -ID = 'truthy' -TYPE = 'token' TRUTHY = ['YES', 'Yes', 'yes', 'NO', 'No', 'no', - 'TRUE', 'True', # 'true' is a boolean - 'FALSE', 'False', # 'false' is a boolean + 'TRUE', 'True', 'true', + 'FALSE', 'False', 'false', 'ON', 'On', 'on', 'OFF', 'Off', 'off'] +ID = 'truthy' +TYPE = 'token' +CONF = {'allowed-values': list(TRUTHY)} +DEFAULT = {'allowed-values': ['true', 'false']} + + def check(conf, token, prev, next, nextnext, context): if prev and isinstance(prev, yaml.tokens.TagToken): return if isinstance(token, yaml.tokens.ScalarToken): - if token.value in TRUTHY and token.style is None: + if (token.value in (set(TRUTHY) - set(conf['allowed-values'])) and + token.style is None): yield LintProblem(token.start_mark.line + 1, token.start_mark.column + 1, - "truthy value should be true or false") + "truthy value should be one of [" + + ", ".join(sorted(conf['allowed-values'])) + "]")