Hello community,

here is the log from the commit of package python-yamllint for 
openSUSE:Leap:15.2 checked in at 2020-04-20 12:55:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Leap:15.2/python-yamllint (Old)
 and      /work/SRC/openSUSE:Leap:15.2/.python-yamllint.new.2738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-yamllint"

Mon Apr 20 12:55:36 2020 rev:6 rq:795560 version:1.22.1

Changes:
--------
--- /work/SRC/openSUSE:Leap:15.2/python-yamllint/python-yamllint.changes        
2020-03-02 13:25:09.330612875 +0100
+++ 
/work/SRC/openSUSE:Leap:15.2/.python-yamllint.new.2738/python-yamllint.changes  
    2020-04-20 12:55:49.880760744 +0200
@@ -1,0 +2,12 @@
+Thu Apr 16 07:39:02 UTC 2020 - Tomáš Chvátal <tchva...@suse.com>
+
+- Update to 1.22.1:
+  * Fix quoted-strings rule with only-when-needed on corner cases
+  * Add check-keys option to the truthy rule
+  * Fix quoted-strings rule not working on sequences items
+  * Sunset Python 2
+  * Fix new-lines rule on Python 3 with DOS line endings
+  * Fix quoted-strings rule not working for string values matching scalars
+  * Add required: only-when-needed option to the quoted-strings rule
+
+-------------------------------------------------------------------

Old:
----
  yamllint-1.20.0.tar.gz

New:
----
  yamllint-1.22.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-yamllint.spec ++++++
--- /var/tmp/diff_new_pack.sRjDLH/_old  2020-04-20 12:55:50.280761372 +0200
+++ /var/tmp/diff_new_pack.sRjDLH/_new  2020-04-20 12:55:50.284761378 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-yamllint
-Version:        1.20.0
+Version:        1.22.1
 Release:        0
 Summary:        A linter for YAML files
 License:        GPL-3.0-only
@@ -30,6 +30,8 @@
 BuildRequires:  python-rpm-macros
 Requires:       python-PyYAML
 Requires:       python-pathspec >= 0.5.3
+Requires(post): update-alternatives
+Requires(postun): update-alternatives
 BuildArch:      noarch
 # SECTION test requirements
 BuildRequires:  %{python_module PyYAML}
@@ -53,14 +55,22 @@
 %install
 %python_install
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
+%python_clone -a %{buildroot}%{_bindir}/yamllint
 
 %check
-%python_exec -m unittest discover
+export LANG="en_US.UTF8"
+%python_exec -m unittest discover -v
+
+%post
+%python_install_alternative yamllint
+
+%postun
+%python_uninstall_alternative yamllint
 
 %files %{python_files}
 %doc README.rst
 %license LICENSE
-%python3_only %{_bindir}/yamllint
+%python_alternative %{_bindir}/yamllint
 %{python_sitelib}/*
 
 %changelog

++++++ yamllint-1.20.0.tar.gz -> yamllint-1.22.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yamllint-1.20.0/PKG-INFO new/yamllint-1.22.1/PKG-INFO
--- old/yamllint-1.20.0/PKG-INFO        2019-12-26 16:07:34.000000000 +0100
+++ new/yamllint-1.22.1/PKG-INFO        2020-04-15 07:57:48.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: yamllint
-Version: 1.20.0
+Version: 1.22.1
 Summary: A linter for YAML files.
 Home-page: https://github.com/adrienverge/yamllint
 Author: Adrien Vergé
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yamllint-1.20.0/README.rst 
new/yamllint-1.22.1/README.rst
--- old/yamllint-1.20.0/README.rst      2019-07-07 18:14:22.000000000 +0200
+++ new/yamllint-1.22.1/README.rst      2020-04-10 16:31:09.000000000 +0200
@@ -21,6 +21,10 @@
 
 Written in Python (compatible with Python 2 & 3).
 
+⚠ Python 2 upstream support stopped on January 1, 2020. yamllint will keep
+best-effort support for Python 2.7 until January 1, 2021. Passed that date,
+yamllint will drop all Python 2-related code.
+
 Documentation
 -------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yamllint-1.20.0/tests/rules/test_quoted_strings.py 
new/yamllint-1.22.1/tests/rules/test_quoted_strings.py
--- old/yamllint-1.20.0/tests/rules/test_quoted_strings.py      2018-10-17 
10:18:32.000000000 +0200
+++ new/yamllint-1.22.1/tests/rules/test_quoted_strings.py      2020-04-15 
07:54:25.000000000 +0200
@@ -22,6 +22,7 @@
 
     def test_disabled(self):
         conf = 'quoted-strings: disable'
+
         self.check('---\n'
                    'foo: bar\n', conf)
         self.check('---\n'
@@ -30,23 +31,34 @@
                    'foo: \'bar\'\n', conf)
         self.check('---\n'
                    'bar: 123\n', conf)
+        self.check('---\n'
+                   'bar: "123"\n', conf)
 
     def test_quote_type_any(self):
         conf = 'quoted-strings: {quote-type: any}\n'
+
         self.check('---\n'
                    'boolean1: true\n'
                    'number1: 123\n'
                    'string1: foo\n'                          # fails
                    'string2: "foo"\n'
-                   'string3: \'bar\'\n'
-                   'string4: !!str genericstring\n'
-                   'string5: !!str 456\n'
-                   'string6: !!str "quotedgenericstring"\n'
+                   'string3: "true"\n'
+                   'string4: "123"\n'
+                   'string5: \'bar\'\n'
+                   'string6: !!str genericstring\n'
+                   'string7: !!str 456\n'
+                   'string8: !!str "quotedgenericstring"\n'
                    'binary: !!binary binstring\n'
                    'integer: !!int intstring\n'
                    'boolean2: !!bool boolstring\n'
-                   'boolean3: !!bool "quotedboolstring"\n',
-                   conf, problem=(4, 10))
+                   'boolean3: !!bool "quotedboolstring"\n'
+                   'block-seq:\n'
+                   '  - foo\n'                               # fails
+                   '  - "foo"\n'
+                   'flow-seq: [foo, "foo"]\n'                # fails
+                   'flow-map: {a: foo, b: "foo"}\n',         # fails
+                   conf, problem1=(4, 10), problem2=(17, 5),
+                   problem3=(19, 12), problem4=(20, 15))
         self.check('---\n'
                    'multiline string 1: |\n'
                    '  line 1\n'
@@ -55,7 +67,7 @@
                    '  word 1\n'
                    '  word 2\n'
                    'multiline string 3:\n'
-                   '  word 1\n'
+                   '  word 1\n'               # fails
                    '  word 2\n'
                    'multiline string 4:\n'
                    '  "word 1\\\n'
@@ -64,20 +76,31 @@
 
     def test_quote_type_single(self):
         conf = 'quoted-strings: {quote-type: single}\n'
+
         self.check('---\n'
                    'boolean1: true\n'
                    'number1: 123\n'
                    'string1: foo\n'                          # fails
                    'string2: "foo"\n'                        # fails
-                   'string3: \'bar\'\n'
-                   'string4: !!str genericstring\n'
-                   'string5: !!str 456\n'
-                   'string6: !!str "quotedgenericstring"\n'
+                   'string3: "true"\n'                       # fails
+                   'string4: "123"\n'                        # fails
+                   'string5: \'bar\'\n'
+                   'string6: !!str genericstring\n'
+                   'string7: !!str 456\n'
+                   'string8: !!str "quotedgenericstring"\n'
                    'binary: !!binary binstring\n'
                    'integer: !!int intstring\n'
                    'boolean2: !!bool boolstring\n'
-                   'boolean3: !!bool "quotedboolstring"\n',
-                   conf, problem1=(4, 10), problem2=(5, 10))
+                   'boolean3: !!bool "quotedboolstring"\n'
+                   'block-seq:\n'
+                   '  - foo\n'                               # fails
+                   '  - "foo"\n'                             # fails
+                   'flow-seq: [foo, "foo"]\n'                # fails
+                   'flow-map: {a: foo, b: "foo"}\n',         # fails
+                   conf, problem1=(4, 10), problem2=(5, 10), problem3=(6, 10),
+                   problem4=(7, 10), problem5=(17, 5), problem6=(18, 5),
+                   problem7=(19, 12), problem8=(19, 17), problem9=(20, 15),
+                   problem10=(20, 23))
         self.check('---\n'
                    'multiline string 1: |\n'
                    '  line 1\n'
@@ -86,7 +109,7 @@
                    '  word 1\n'
                    '  word 2\n'
                    'multiline string 3:\n'
-                   '  word 1\n'
+                   '  word 1\n'               # fails
                    '  word 2\n'
                    'multiline string 4:\n'
                    '  "word 1\\\n'
@@ -95,20 +118,29 @@
 
     def test_quote_type_double(self):
         conf = 'quoted-strings: {quote-type: double}\n'
+
         self.check('---\n'
                    'boolean1: true\n'
                    'number1: 123\n'
                    'string1: foo\n'                          # fails
                    'string2: "foo"\n'
-                   'string3: \'bar\'\n'                      # fails
-                   'string4: !!str genericstring\n'
-                   'string5: !!str 456\n'
-                   'string6: !!str "quotedgenericstring"\n'
+                   'string3: "true"\n'
+                   'string4: "123"\n'
+                   'string5: \'bar\'\n'                      # fails
+                   'string6: !!str genericstring\n'
+                   'string7: !!str 456\n'
+                   'string8: !!str "quotedgenericstring"\n'
                    'binary: !!binary binstring\n'
                    'integer: !!int intstring\n'
                    'boolean2: !!bool boolstring\n'
-                   'boolean3: !!bool "quotedboolstring"\n',
-                   conf, problem1=(4, 10), problem2=(6, 10))
+                   'boolean3: !!bool "quotedboolstring"\n'
+                   'block-seq:\n'
+                   '  - foo\n'                               # fails
+                   '  - "foo"\n'
+                   'flow-seq: [foo, "foo"]\n'                # fails
+                   'flow-map: {a: foo, b: "foo"}\n',         # fails
+                   conf, problem1=(4, 10), problem2=(8, 10), problem3=(17, 5),
+                   problem4=(19, 12), problem5=(20, 15))
         self.check('---\n'
                    'multiline string 1: |\n'
                    '  line 1\n'
@@ -117,9 +149,211 @@
                    '  word 1\n'
                    '  word 2\n'
                    'multiline string 3:\n'
-                   '  word 1\n'
+                   '  word 1\n'               # fails
                    '  word 2\n'
                    'multiline string 4:\n'
                    '  "word 1\\\n'
                    '   word 2"\n',
                    conf, problem1=(9, 3))
+
+    def test_any_quotes_not_required(self):
+        conf = 'quoted-strings: {quote-type: any, required: false}\n'
+
+        self.check('---\n'
+                   'boolean1: true\n'
+                   'number1: 123\n'
+                   'string1: foo\n'
+                   'string2: "foo"\n'
+                   'string3: "true"\n'
+                   'string4: "123"\n'
+                   'string5: \'bar\'\n'
+                   'string6: !!str genericstring\n'
+                   'string7: !!str 456\n'
+                   'string8: !!str "quotedgenericstring"\n'
+                   'binary: !!binary binstring\n'
+                   'integer: !!int intstring\n'
+                   'boolean2: !!bool boolstring\n'
+                   'boolean3: !!bool "quotedboolstring"\n'
+                   'block-seq:\n'
+                   '  - foo\n'                               # fails
+                   '  - "foo"\n'
+                   'flow-seq: [foo, "foo"]\n'                # fails
+                   'flow-map: {a: foo, b: "foo"}\n',         # fails
+                   conf)
+        self.check('---\n'
+                   'multiline string 1: |\n'
+                   '  line 1\n'
+                   '  line 2\n'
+                   'multiline string 2: >\n'
+                   '  word 1\n'
+                   '  word 2\n'
+                   'multiline string 3:\n'
+                   '  word 1\n'
+                   '  word 2\n'
+                   'multiline string 4:\n'
+                   '  "word 1\\\n'
+                   '   word 2"\n',
+                   conf)
+
+    def test_single_quotes_not_required(self):
+        conf = 'quoted-strings: {quote-type: single, required: false}\n'
+
+        self.check('---\n'
+                   'boolean1: true\n'
+                   'number1: 123\n'
+                   'string1: foo\n'
+                   'string2: "foo"\n'                        # fails
+                   'string3: "true"\n'                       # fails
+                   'string4: "123"\n'                        # fails
+                   'string5: \'bar\'\n'
+                   'string6: !!str genericstring\n'
+                   'string7: !!str 456\n'
+                   'string8: !!str "quotedgenericstring"\n'
+                   'binary: !!binary binstring\n'
+                   'integer: !!int intstring\n'
+                   'boolean2: !!bool boolstring\n'
+                   'boolean3: !!bool "quotedboolstring"\n'
+                   'block-seq:\n'
+                   '  - foo\n'                               # fails
+                   '  - "foo"\n'
+                   'flow-seq: [foo, "foo"]\n'                # fails
+                   'flow-map: {a: foo, b: "foo"}\n',         # fails
+                   conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10),
+                   problem4=(18, 5), problem5=(19, 17), problem6=(20, 23))
+        self.check('---\n'
+                   'multiline string 1: |\n'
+                   '  line 1\n'
+                   '  line 2\n'
+                   'multiline string 2: >\n'
+                   '  word 1\n'
+                   '  word 2\n'
+                   'multiline string 3:\n'
+                   '  word 1\n'
+                   '  word 2\n'
+                   'multiline string 4:\n'
+                   '  "word 1\\\n'            # fails
+                   '   word 2"\n',
+                   conf, problem1=(12, 3))
+
+    def test_only_when_needed(self):
+        conf = 'quoted-strings: {required: only-when-needed}\n'
+
+        self.check('---\n'
+                   'boolean1: true\n'
+                   'number1: 123\n'
+                   'string1: foo\n'
+                   'string2: "foo"\n'                        # fails
+                   'string3: "true"\n'
+                   'string4: "123"\n'
+                   'string5: \'bar\'\n'                      # fails
+                   'string6: !!str genericstring\n'
+                   'string7: !!str 456\n'
+                   'string8: !!str "quotedgenericstring"\n'
+                   'binary: !!binary binstring\n'
+                   'integer: !!int intstring\n'
+                   'boolean2: !!bool boolstring\n'
+                   'boolean3: !!bool "quotedboolstring"\n'
+                   'block-seq:\n'
+                   '  - foo\n'
+                   '  - "foo"\n'                             # fails
+                   'flow-seq: [foo, "foo"]\n'                # fails
+                   'flow-map: {a: foo, b: "foo"}\n',         # fails
+                   conf, problem1=(5, 10), problem2=(8, 10), problem3=(18, 5),
+                   problem4=(19, 17), problem5=(20, 23))
+        self.check('---\n'
+                   'multiline string 1: |\n'
+                   '  line 1\n'
+                   '  line 2\n'
+                   'multiline string 2: >\n'
+                   '  word 1\n'
+                   '  word 2\n'
+                   'multiline string 3:\n'
+                   '  word 1\n'
+                   '  word 2\n'
+                   'multiline string 4:\n'
+                   '  "word 1\\\n'            # fails
+                   '   word 2"\n',
+                   conf, problem1=(12, 3))
+
+    def test_only_when_needed_single_quotes(self):
+        conf = ('quoted-strings: {quote-type: single,\n'
+                '                 required: only-when-needed}\n')
+
+        self.check('---\n'
+                   'boolean1: true\n'
+                   'number1: 123\n'
+                   'string1: foo\n'
+                   'string2: "foo"\n'                        # fails
+                   'string3: "true"\n'                       # fails
+                   'string4: "123"\n'                        # fails
+                   'string5: \'bar\'\n'                      # fails
+                   'string6: !!str genericstring\n'
+                   'string7: !!str 456\n'
+                   'string8: !!str "quotedgenericstring"\n'
+                   'binary: !!binary binstring\n'
+                   'integer: !!int intstring\n'
+                   'boolean2: !!bool boolstring\n'
+                   'boolean3: !!bool "quotedboolstring"\n'
+                   'block-seq:\n'
+                   '  - foo\n'
+                   '  - "foo"\n'                             # fails
+                   'flow-seq: [foo, "foo"]\n'                # fails
+                   'flow-map: {a: foo, b: "foo"}\n',         # fails
+                   conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10),
+                   problem4=(8, 10), problem5=(18, 5), problem6=(19, 17),
+                   problem7=(20, 23))
+        self.check('---\n'
+                   'multiline string 1: |\n'
+                   '  line 1\n'
+                   '  line 2\n'
+                   'multiline string 2: >\n'
+                   '  word 1\n'
+                   '  word 2\n'
+                   'multiline string 3:\n'
+                   '  word 1\n'
+                   '  word 2\n'
+                   'multiline string 4:\n'
+                   '  "word 1\\\n'            # fails
+                   '   word 2"\n',
+                   conf, problem1=(12, 3))
+
+    def test_only_when_needed_corner_cases(self):
+        conf = 'quoted-strings: {required: only-when-needed}\n'
+
+        self.check('---\n'
+                   '- ""\n'
+                   '- "- item"\n'
+                   '- "key: value"\n'
+                   '- "%H:%M:%S"\n'
+                   '- "%wheel ALL=(ALL) NOPASSWD: ALL"\n'
+                   '- \'"quoted"\'\n'
+                   '- "\'foo\' == \'bar\'"\n'
+                   '- "\'Mac\' in ansible_facts.product_name"\n',
+                   conf)
+        self.check('---\n'
+                   'k1: ""\n'
+                   'k2: "- item"\n'
+                   'k3: "key: value"\n'
+                   'k4: "%H:%M:%S"\n'
+                   'k5: "%wheel ALL=(ALL) NOPASSWD: ALL"\n'
+                   'k6: \'"quoted"\'\n'
+                   'k7: "\'foo\' == \'bar\'"\n'
+                   'k8: "\'Mac\' in ansible_facts.product_name"\n',
+                   conf)
+
+        self.check('---\n'
+                   '- ---\n'
+                   '- "---"\n'                     # fails
+                   '- ----------\n'
+                   '- "----------"\n'              # fails
+                   '- :wq\n'
+                   '- ":wq"\n',                    # fails
+                   conf, problem1=(3, 3), problem2=(5, 3), problem3=(7, 3))
+        self.check('---\n'
+                   'k1: ---\n'
+                   'k2: "---"\n'                   # fails
+                   'k3: ----------\n'
+                   'k4: "----------"\n'            # fails
+                   'k5: :wq\n'
+                   'k6: ":wq"\n',                  # fails
+                   conf, problem1=(3, 5), problem2=(5, 5), problem3=(7, 5))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yamllint-1.20.0/tests/rules/test_truthy.py 
new/yamllint-1.22.1/tests/rules/test_truthy.py
--- old/yamllint-1.20.0/tests/rules/test_truthy.py      2019-06-07 
09:59:31.000000000 +0200
+++ new/yamllint-1.22.1/tests/rules/test_truthy.py      2020-04-08 
12:31:08.000000000 +0200
@@ -114,3 +114,33 @@
                    'boolean5: !!bool off\n'
                    'boolean6: !!bool NO\n',
                    conf)
+
+    def test_check_keys_disabled(self):
+        conf = ('truthy:\n'
+                '  allowed-values: []\n'
+                '  check-keys: false\n'
+                'key-duplicates: disable\n')
+        self.check('---\n'
+                   'YES: 0\n'
+                   'Yes: 0\n'
+                   'yes: 0\n'
+                   'No: 0\n'
+                   'No: 0\n'
+                   'no: 0\n'
+                   'TRUE: 0\n'
+                   'True: 0\n'
+                   'true: 0\n'
+                   'FALSE: 0\n'
+                   'False: 0\n'
+                   'false: 0\n'
+                   'ON: 0\n'
+                   'On: 0\n'
+                   'on: 0\n'
+                   'OFF: 0\n'
+                   'Off: 0\n'
+                   'off: 0\n'
+                   'YES:\n'
+                   '  Yes:\n'
+                   '    yes:\n'
+                   '      on: 0\n',
+                   conf)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yamllint-1.20.0/tests/test_cli.py 
new/yamllint-1.22.1/tests/test_cli.py
--- old/yamllint-1.20.0/tests/test_cli.py       2019-12-26 16:04:47.000000000 
+0100
+++ new/yamllint-1.22.1/tests/test_cli.py       2020-03-31 12:32:06.000000000 
+0200
@@ -32,6 +32,29 @@
 from yamllint import config
 
 
+class RunContext(object):
+    """Context manager for ``cli.run()`` to capture exit code and streams."""
+
+    def __init__(self, case):
+        self.stdout = self.stderr = None
+        self._raises_ctx = case.assertRaises(SystemExit)
+
+    def __enter__(self):
+        self._raises_ctx.__enter__()
+        sys.stdout = self.outstream = StringIO()
+        sys.stderr = self.errstream = StringIO()
+        return self
+
+    def __exit__(self, *exc_info):
+        self.stdout, sys.stdout = self.outstream.getvalue(), sys.__stdout__
+        self.stderr, sys.stderr = self.errstream.getvalue(), sys.__stderr__
+        return self._raises_ctx.__exit__(*exc_info)
+
+    @property
+    def returncode(self):
+        return self._raises_ctx.exception.code
+
+
 class CommandLineTestCase(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
@@ -59,12 +82,15 @@
             'no-yaml.json': '---\n'
                             'key: value\n',
             # non-ASCII chars
-            'non-ascii/utf-8': (
+            'non-ascii/éçäγλνπ¥/utf-8': (
                 u'---\n'
                 u'- hétérogénéité\n'
                 u'# 19.99 €\n'
                 u'- お早う御座います。\n'
                 u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
+            # dos line endings yaml
+            'dos.yml': '---\r\n'
+                       'dos: true',
         })
 
     @classmethod
@@ -78,6 +104,7 @@
         self.assertEqual(
             sorted(cli.find_files_recursively([self.wd], conf)),
             [os.path.join(self.wd, 'a.yaml'),
+             os.path.join(self.wd, 'dos.yml'),
              os.path.join(self.wd, 'empty.yml'),
              os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
              os.path.join(self.wd, 'sub/ok.yaml'),
@@ -123,7 +150,8 @@
                                      '  - \'*.yml\'\n')
         self.assertEqual(
             sorted(cli.find_files_recursively([self.wd], conf)),
-            [os.path.join(self.wd, 'empty.yml')]
+            [os.path.join(self.wd, 'dos.yml'),
+             os.path.join(self.wd, 'empty.yml')]
         )
 
         conf = config.YamlLintConfig('extends: default\n'
@@ -140,9 +168,10 @@
         self.assertEqual(
             sorted(cli.find_files_recursively([self.wd], conf)),
             [os.path.join(self.wd, 'a.yaml'),
+             os.path.join(self.wd, 'dos.yml'),
              os.path.join(self.wd, 'empty.yml'),
              os.path.join(self.wd, 'no-yaml.json'),
-             os.path.join(self.wd, 'non-ascii/utf-8'),
+             os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8'),
              os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
              os.path.join(self.wd, 'sub/ok.yaml'),
              os.path.join(self.wd, 'warn.yaml')]
@@ -156,9 +185,10 @@
         self.assertEqual(
             sorted(cli.find_files_recursively([self.wd], conf)),
             [os.path.join(self.wd, 'a.yaml'),
+             os.path.join(self.wd, 'dos.yml'),
              os.path.join(self.wd, 'empty.yml'),
              os.path.join(self.wd, 'no-yaml.json'),
-             os.path.join(self.wd, 'non-ascii/utf-8'),
+             os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8'),
              os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
              os.path.join(self.wd, 'sub/ok.yaml'),
              os.path.join(self.wd, 'warn.yaml')]
@@ -170,205 +200,148 @@
                                      '  - \'**/utf-8\'\n')
         self.assertEqual(
             sorted(cli.find_files_recursively([self.wd], conf)),
-            [os.path.join(self.wd, 'non-ascii/utf-8')]
+            [os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8')]
         )
 
     def test_run_with_bad_arguments(self):
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
+        with RunContext(self) as ctx:
             cli.run(())
+        self.assertNotEqual(ctx.returncode, 0)
+        self.assertEqual(ctx.stdout, '')
+        self.assertRegexpMatches(ctx.stderr, r'^usage')
 
-        self.assertNotEqual(ctx.exception.code, 0)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, '')
-        self.assertRegexpMatches(err, r'^usage')
-
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
+        with RunContext(self) as ctx:
             cli.run(('--unknown-arg', ))
+        self.assertNotEqual(ctx.returncode, 0)
+        self.assertEqual(ctx.stdout, '')
+        self.assertRegexpMatches(ctx.stderr, r'^usage')
 
-        self.assertNotEqual(ctx.exception.code, 0)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, '')
-        self.assertRegexpMatches(err, r'^usage')
-
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
+        with RunContext(self) as ctx:
             cli.run(('-c', './conf.yaml', '-d', 'relaxed', 'file'))
-
-        self.assertNotEqual(ctx.exception.code, 0)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, '')
+        self.assertNotEqual(ctx.returncode, 0)
+        self.assertEqual(ctx.stdout, '')
         self.assertRegexpMatches(
-            err.splitlines()[-1],
+            ctx.stderr.splitlines()[-1],
             r'^yamllint: error: argument -d\/--config-data: '
             r'not allowed with argument -c\/--config-file$'
         )
 
         # checks if reading from stdin and files are mutually exclusive
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
+        with RunContext(self) as ctx:
             cli.run(('-', 'file'))
-
-        self.assertNotEqual(ctx.exception.code, 0)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, '')
-        self.assertRegexpMatches(err, r'^usage')
+        self.assertNotEqual(ctx.returncode, 0)
+        self.assertEqual(ctx.stdout, '')
+        self.assertRegexpMatches(ctx.stderr, r'^usage')
 
     def test_run_with_bad_config(self):
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
+        with RunContext(self) as ctx:
             cli.run(('-d', 'rules: {a: b}', 'file'))
-
-        self.assertEqual(ctx.exception.code, -1)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, '')
-        self.assertRegexpMatches(err, r'^invalid config: no such rule')
+        self.assertEqual(ctx.returncode, -1)
+        self.assertEqual(ctx.stdout, '')
+        self.assertRegexpMatches(ctx.stderr, r'^invalid config: no such rule')
 
     def test_run_with_empty_config(self):
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
+        with RunContext(self) as ctx:
             cli.run(('-d', '', 'file'))
-
-        self.assertEqual(ctx.exception.code, -1)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, '')
-        self.assertRegexpMatches(err, r'^invalid config: not a dict')
+        self.assertEqual(ctx.returncode, -1)
+        self.assertEqual(ctx.stdout, '')
+        self.assertRegexpMatches(ctx.stderr, r'^invalid config: not a dict')
 
     def test_run_with_config_file(self):
         with open(os.path.join(self.wd, 'config'), 'w') as f:
             f.write('rules: {trailing-spaces: disable}')
 
-        with self.assertRaises(SystemExit) as ctx:
+        with RunContext(self) as ctx:
             cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
-        self.assertEqual(ctx.exception.code, 0)
+        self.assertEqual(ctx.returncode, 0)
 
         with open(os.path.join(self.wd, 'config'), 'w') as f:
             f.write('rules: {trailing-spaces: enable}')
 
-        with self.assertRaises(SystemExit) as ctx:
+        with RunContext(self) as ctx:
             cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
-        self.assertEqual(ctx.exception.code, 1)
+        self.assertEqual(ctx.returncode, 1)
 
     def test_run_with_user_global_config_file(self):
         home = os.path.join(self.wd, 'fake-home')
-        os.mkdir(home)
-        dir = os.path.join(home, '.config')
-        os.mkdir(dir)
-        dir = os.path.join(dir, 'yamllint')
-        os.mkdir(dir)
+        dir = os.path.join(home, '.config', 'yamllint')
+        os.makedirs(dir)
         config = os.path.join(dir, 'config')
 
-        temp = os.environ['HOME']
+        self.addCleanup(os.environ.update, HOME=os.environ['HOME'])
         os.environ['HOME'] = home
 
         with open(config, 'w') as f:
             f.write('rules: {trailing-spaces: disable}')
 
-        with self.assertRaises(SystemExit) as ctx:
+        with RunContext(self) as ctx:
             cli.run((os.path.join(self.wd, 'a.yaml'), ))
-        self.assertEqual(ctx.exception.code, 0)
+        self.assertEqual(ctx.returncode, 0)
 
         with open(config, 'w') as f:
             f.write('rules: {trailing-spaces: enable}')
 
-        with self.assertRaises(SystemExit) as ctx:
+        with RunContext(self) as ctx:
             cli.run((os.path.join(self.wd, 'a.yaml'), ))
-        self.assertEqual(ctx.exception.code, 1)
-
-        os.environ['HOME'] = temp
+        self.assertEqual(ctx.returncode, 1)
 
     def test_run_version(self):
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
+        with RunContext(self) as ctx:
             cli.run(('--version', ))
-
-        self.assertEqual(ctx.exception.code, 0)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertRegexpMatches(out + err, r'yamllint \d+\.\d+')
+        self.assertEqual(ctx.returncode, 0)
+        self.assertRegexpMatches(ctx.stdout + ctx.stderr, r'yamllint \d+\.\d+')
 
     def test_run_non_existing_file(self):
-        file = os.path.join(self.wd, 'i-do-not-exist.yaml')
+        path = os.path.join(self.wd, 'i-do-not-exist.yaml')
 
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run(('-f', 'parsable', file))
-
-        self.assertEqual(ctx.exception.code, -1)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, '')
-        self.assertRegexpMatches(err, r'No such file or directory')
+        with RunContext(self) as ctx:
+            cli.run(('-f', 'parsable', path))
+        self.assertEqual(ctx.returncode, -1)
+        self.assertEqual(ctx.stdout, '')
+        self.assertRegexpMatches(ctx.stderr, r'No such file or directory')
 
     def test_run_one_problem_file(self):
-        file = os.path.join(self.wd, 'a.yaml')
-
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run(('-f', 'parsable', file))
-
-        self.assertEqual(ctx.exception.code, 1)
+        path = os.path.join(self.wd, 'a.yaml')
 
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, (
+        with RunContext(self) as ctx:
+            cli.run(('-f', 'parsable', path))
+        self.assertEqual(ctx.returncode, 1)
+        self.assertEqual(ctx.stdout, (
             '%s:2:4: [error] trailing spaces (trailing-spaces)\n'
             '%s:3:4: [error] no new line character at the end of file '
-            '(new-line-at-end-of-file)\n') % (file, file))
-        self.assertEqual(err, '')
+            '(new-line-at-end-of-file)\n' % (path, path)))
+        self.assertEqual(ctx.stderr, '')
 
     def test_run_one_warning(self):
-        file = os.path.join(self.wd, 'warn.yaml')
-
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run(('-f', 'parsable', file))
+        path = os.path.join(self.wd, 'warn.yaml')
 
-        self.assertEqual(ctx.exception.code, 0)
+        with RunContext(self) as ctx:
+            cli.run(('-f', 'parsable', path))
+        self.assertEqual(ctx.returncode, 0)
 
     def test_run_warning_in_strict_mode(self):
-        file = os.path.join(self.wd, 'warn.yaml')
-
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run(('-f', 'parsable', '--strict', file))
+        path = os.path.join(self.wd, 'warn.yaml')
 
-        self.assertEqual(ctx.exception.code, 2)
+        with RunContext(self) as ctx:
+            cli.run(('-f', 'parsable', '--strict', path))
+        self.assertEqual(ctx.returncode, 2)
 
     def test_run_one_ok_file(self):
-        file = os.path.join(self.wd, 'sub', 'ok.yaml')
+        path = os.path.join(self.wd, 'sub', 'ok.yaml')
 
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run(('-f', 'parsable', file))
-
-        self.assertEqual(ctx.exception.code, 0)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, '')
-        self.assertEqual(err, '')
+        with RunContext(self) as ctx:
+            cli.run(('-f', 'parsable', path))
+        self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
 
     def test_run_empty_file(self):
-        file = os.path.join(self.wd, 'empty.yml')
-
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run(('-f', 'parsable', file))
-
-        self.assertEqual(ctx.exception.code, 0)
+        path = os.path.join(self.wd, 'empty.yml')
 
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, '')
-        self.assertEqual(err, '')
+        with RunContext(self) as ctx:
+            cli.run(('-f', 'parsable', path))
+        self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
 
     def test_run_non_ascii_file(self):
-        file = os.path.join(self.wd, 'non-ascii', 'utf-8')
+        path = os.path.join(self.wd, 'non-ascii', 'éçäγλνπ¥', 'utf-8')
 
         # Make sure the default localization conditions on this "system"
         # support UTF-8 encoding.
@@ -377,63 +350,46 @@
             locale.setlocale(locale.LC_ALL, 'C.UTF-8')
         except locale.Error:
             locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
+        self.addCleanup(locale.setlocale, locale.LC_ALL, loc)
 
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run(('-f', 'parsable', file))
-
-        locale.setlocale(locale.LC_ALL, loc)
-
-        self.assertEqual(ctx.exception.code, 0)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, '')
-        self.assertEqual(err, '')
+        with RunContext(self) as ctx:
+            cli.run(('-f', 'parsable', path))
+        self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
 
     def test_run_multiple_files(self):
         items = [os.path.join(self.wd, 'empty.yml'),
                  os.path.join(self.wd, 's')]
-        file = items[1] + '/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'
+        path = items[1] + '/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'
 
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
+        with RunContext(self) as ctx:
             cli.run(['-f', 'parsable'] + items)
-
-        self.assertEqual(ctx.exception.code, 1)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, (
+        self.assertEqual((ctx.returncode, ctx.stderr), (1, ''))
+        self.assertEqual(ctx.stdout, (
             '%s:3:1: [error] duplication of key "key" in mapping '
-            '(key-duplicates)\n') % file)
-        self.assertEqual(err, '')
+            '(key-duplicates)\n') % path)
 
     def test_run_piped_output_nocolor(self):
-        file = os.path.join(self.wd, 'a.yaml')
-
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run((file, ))
-
-        self.assertEqual(ctx.exception.code, 1)
+        path = os.path.join(self.wd, 'a.yaml')
 
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, (
+        with RunContext(self) as ctx:
+            cli.run((path, ))
+        self.assertEqual((ctx.returncode, ctx.stderr), (1, ''))
+        self.assertEqual(ctx.stdout, (
             '%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, '')
+            '\n' % path))
 
     def test_run_default_format_output_in_tty(self):
-        file = os.path.join(self.wd, 'a.yaml')
+        path = 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, ))
+            cli.run((path, ))
         sys.stdout.flush()
 
         self.assertEqual(ctx.exception.code, 1)
@@ -456,114 +412,108 @@
             '  \033[2m3:4\033[0m       \033[31merror\033[0m    '
             'no new line character at the end of file  '
             '\033[2m(new-line-at-end-of-file)\033[0m\n'
-            '\n' % file))
+            '\n' % path))
 
     def test_run_default_format_output_without_tty(self):
-        file = os.path.join(self.wd, 'a.yaml')
-
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run((file, ))
+        path = os.path.join(self.wd, 'a.yaml')
 
-        self.assertEqual(ctx.exception.code, 1)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, (
+        with RunContext(self) as ctx:
+            cli.run((path, ))
+        expected_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, '')
+            '\n' % path)
+        self.assertEqual(
+            (ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
 
     def test_run_auto_output_without_tty_output(self):
-        file = os.path.join(self.wd, 'a.yaml')
-
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run((file, '--format', 'auto'))
+        path = os.path.join(self.wd, 'a.yaml')
 
-        self.assertEqual(ctx.exception.code, 1)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, (
+        with RunContext(self) as ctx:
+            cli.run((path, '--format', 'auto'))
+        expected_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, '')
+            '\n' % path)
+        self.assertEqual(
+            (ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
 
     def test_run_format_colored(self):
-        file = os.path.join(self.wd, 'a.yaml')
-
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run((file, '--format', 'colored'))
+        path = os.path.join(self.wd, 'a.yaml')
 
-        self.assertEqual(ctx.exception.code, 1)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, (
+        with RunContext(self) as ctx:
+            cli.run((path, '--format', 'colored'))
+        expected_out = (
             '\033[4m%s\033[0m\n'
             '  \033[2m2:4\033[0m       \033[31merror\033[0m    '
             'trailing spaces  \033[2m(trailing-spaces)\033[0m\n'
             '  \033[2m3:4\033[0m       \033[31merror\033[0m    '
             'no new line character at the end of file  '
             '\033[2m(new-line-at-end-of-file)\033[0m\n'
-            '\n' % file))
-        self.assertEqual(err, '')
+            '\n' % path)
+        self.assertEqual(
+            (ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
 
     def test_run_read_from_stdin(self):
         # prepares stdin with an invalid yaml string so that we can check
         # for its specific error, and be assured that stdin was read
-        sys.stdout, sys.stderr = StringIO(), StringIO()
+        self.addCleanup(setattr, sys, 'stdin', sys.__stdin__)
         sys.stdin = StringIO(
             'I am a string\n'
             'therefore: I am an error\n')
 
-        with self.assertRaises(SystemExit) as ctx:
+        with RunContext(self) as ctx:
             cli.run(('-', '-f', 'parsable'))
-
-        self.assertNotEqual(ctx.exception.code, 0)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, (
+        expected_out = (
             'stdin:2:10: [error] syntax error: '
-            'mapping values are not allowed here (syntax)\n'))
-        self.assertEqual(err, '')
+            'mapping values are not allowed here (syntax)\n')
+        self.assertEqual(
+            (ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
 
     def test_run_no_warnings(self):
-        file = os.path.join(self.wd, 'a.yaml')
+        path = os.path.join(self.wd, 'a.yaml')
 
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run((file, '--no-warnings', '-f', 'auto'))
-
-        self.assertEqual(ctx.exception.code, 1)
-
-        out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
-        self.assertEqual(out, (
+        with RunContext(self) as ctx:
+            cli.run((path, '--no-warnings', '-f', 'auto'))
+        expected_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, '')
-
-        file = os.path.join(self.wd, 'warn.yaml')
+            '\n' % path)
+        self.assertEqual(
+            (ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
 
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run((file, '--no-warnings', '-f', 'auto'))
+        path = os.path.join(self.wd, 'warn.yaml')
 
-        self.assertEqual(ctx.exception.code, 0)
+        with RunContext(self) as ctx:
+            cli.run((path, '--no-warnings', '-f', 'auto'))
+        self.assertEqual(ctx.returncode, 0)
 
     def test_run_no_warnings_and_strict(self):
-        file = os.path.join(self.wd, 'warn.yaml')
-
-        sys.stdout, sys.stderr = StringIO(), StringIO()
-        with self.assertRaises(SystemExit) as ctx:
-            cli.run((file, '--no-warnings', '-s'))
+        path = os.path.join(self.wd, 'warn.yaml')
 
-        self.assertEqual(ctx.exception.code, 2)
+        with RunContext(self) as ctx:
+            cli.run((path, '--no-warnings', '-s'))
+        self.assertEqual(ctx.returncode, 2)
+
+    def test_run_non_universal_newline(self):
+        path = os.path.join(self.wd, 'dos.yml')
+
+        with RunContext(self) as ctx:
+            cli.run(('-d', 'rules:\n  new-lines:\n    type: dos', path))
+        self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
+
+        with RunContext(self) as ctx:
+            cli.run(('-d', 'rules:\n  new-lines:\n    type: unix', path))
+        expected_out = (
+            '%s\n'
+            '  1:4       error    wrong new line character: expected \\n'
+            '  (new-lines)\n'
+            '\n' % path)
+        self.assertEqual(
+            (ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yamllint-1.20.0/yamllint/__init__.py 
new/yamllint-1.22.1/yamllint/__init__.py
--- old/yamllint-1.20.0/yamllint/__init__.py    2019-12-26 16:06:16.000000000 
+0100
+++ new/yamllint-1.22.1/yamllint/__init__.py    2020-04-15 07:55:42.000000000 
+0200
@@ -22,7 +22,7 @@
 
 
 APP_NAME = 'yamllint'
-APP_VERSION = '1.20.0'
+APP_VERSION = '1.22.1'
 APP_DESCRIPTION = __doc__
 
 __author__ = u'Adrien Vergé'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yamllint-1.20.0/yamllint/cli.py 
new/yamllint-1.22.1/yamllint/cli.py
--- old/yamllint-1.20.0/yamllint/cli.py 2019-12-12 09:13:16.000000000 +0100
+++ new/yamllint-1.22.1/yamllint/cli.py 2020-04-08 11:46:13.000000000 +0200
@@ -17,6 +17,7 @@
 from __future__ import print_function
 
 import argparse
+import io
 import os
 import platform
 import sys
@@ -176,7 +177,7 @@
     for file in find_files_recursively(args.files, conf):
         filepath = file[2:] if file.startswith('./') else file
         try:
-            with open(file) as f:
+            with io.open(file, newline='') as f:
                 problems = linter.run(f, conf, filepath)
         except EnvironmentError as e:
             print(e, file=sys.stderr)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yamllint-1.20.0/yamllint/rules/quoted_strings.py 
new/yamllint-1.22.1/yamllint/rules/quoted_strings.py
--- old/yamllint-1.20.0/yamllint/rules/quoted_strings.py        2019-01-10 
10:01:44.000000000 +0100
+++ new/yamllint-1.22.1/yamllint/rules/quoted_strings.py        2020-04-15 
07:54:25.000000000 +0200
@@ -15,15 +15,23 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-Use this rule to forbid any string values that are not quoted.
-You can also enforce the type of the quote used using the ``quote-type`` option
-(``single``, ``double`` or ``any``).
+Use this rule to forbid any string values that are not quoted, or to prevent
+quoted strings without needing it. You can also enforce the type of the quote
+used.
+
+.. rubric:: Options
+
+* ``quote-type`` defines allowed quotes: ``single``, ``double`` or ``any``
+  (default).
+* ``required`` defines whether using quotes in string values is required
+  (``true``, default) or not (``false``), or only allowed when really needed
+  (``only-when-needed``).
 
 **Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
 
 .. rubric:: Examples
 
-#. With ``quoted-strings: {quote-type: any}``
+#. With ``quoted-strings: {quote-type: any, required: true}``
 
    the following code snippet would **PASS**:
    ::
@@ -37,6 +45,24 @@
    ::
 
     foo: bar
+
+#. With ``quoted-strings: {quote-type: single, required: only-when-needed}``
+
+   the following code snippet would **PASS**:
+   ::
+
+    foo: bar
+    bar: foo
+    not_number: '123'
+    not_boolean: 'true'
+    not_comment: '# comment'
+    not_list: '[1, 2, 3]'
+    not_map: '{a: 1, b: 2}'
+
+   the following code snippet would **FAIL**:
+   ::
+
+    foo: 'bar'
 """
 
 import yaml
@@ -45,34 +71,93 @@
 
 ID = 'quoted-strings'
 TYPE = 'token'
-CONF = {'quote-type': ('any', 'single', 'double')}
-DEFAULT = {'quote-type': 'any'}
+CONF = {'quote-type': ('any', 'single', 'double'),
+        'required': (True, False, 'only-when-needed')}
+DEFAULT = {'quote-type': 'any',
+           'required': True}
+
+DEFAULT_SCALAR_TAG = u'tag:yaml.org,2002:str'
+
+
+def _quote_match(quote_type, token_style):
+    return ((quote_type == 'any') or
+            (quote_type == 'single' and token_style == "'") or
+            (quote_type == 'double' and token_style == '"'))
+
+
+def _quotes_are_needed(string):
+    loader = yaml.BaseLoader('key: ' + string)
+    # Remove the 5 first tokens corresponding to 'key: ' (StreamStartToken,
+    # BlockMappingStartToken, KeyToken, ScalarToken(value=key), ValueToken)
+    for _ in range(5):
+        loader.get_token()
+    try:
+        a, b = loader.get_token(), loader.get_token()
+        if (isinstance(a, yaml.ScalarToken) and a.style is None and
+                isinstance(b, yaml.BlockEndToken)):
+            return False
+        return True
+    except yaml.scanner.ScannerError:
+        return True
 
 
 def check(conf, token, prev, next, nextnext, context):
+    if not (isinstance(token, yaml.tokens.ScalarToken) and
+            isinstance(prev, (yaml.BlockEntryToken, yaml.FlowEntryToken,
+                              yaml.FlowSequenceStartToken, yaml.TagToken,
+                              yaml.ValueToken))):
+
+        return
+
+    # Ignore explicit types, e.g. !!str testtest or !!int 42
+    if (prev and isinstance(prev, yaml.tokens.TagToken) and
+            prev.value[0] == '!!'):
+        return
+
+    # Ignore numbers, booleans, etc.
+    resolver = yaml.resolver.Resolver()
+    tag = resolver.resolve(yaml.nodes.ScalarNode, token.value, (True, False))
+    if token.plain and tag != DEFAULT_SCALAR_TAG:
+        return
+
+    # Ignore multi-line strings
+    if (not token.plain) and (token.style == "|" or token.style == ">"):
+        return
+
     quote_type = conf['quote-type']
+    required = conf['required']
 
-    if (isinstance(token, yaml.tokens.ScalarToken) and
-            isinstance(prev, (yaml.ValueToken, yaml.TagToken))):
-        # Ignore explicit types, e.g. !!str testtest or !!int 42
-        if (prev and isinstance(prev, yaml.tokens.TagToken) and
-                prev.value[0] == '!!'):
-            return
-
-        # Ignore numbers, booleans, etc.
-        resolver = yaml.resolver.Resolver()
-        if resolver.resolve(yaml.nodes.ScalarNode, token.value,
-                            (True, False)) != 'tag:yaml.org,2002:str':
-            return
-
-        # Ignore multi-line strings
-        if (not token.plain) and (token.style == "|" or token.style == ">"):
-            return
-
-        if ((quote_type == 'single' and token.style != "'") or
-                (quote_type == 'double' and token.style != '"') or
-                (quote_type == 'any' and token.style is None)):
-            yield LintProblem(
-                token.start_mark.line + 1,
-                token.start_mark.column + 1,
-                "string value is not quoted with %s quotes" % (quote_type))
+    # Completely relaxed about quotes (same as the rule being disabled)
+    if required is False and quote_type == 'any':
+        return
+
+    msg = None
+    if required is True:
+
+        # Quotes are mandatory and need to match config
+        if token.style is None or not _quote_match(quote_type, token.style):
+            msg = "string value is not quoted with %s quotes" % (quote_type)
+
+    elif required is False:
+
+        # Quotes are not mandatory but when used need to match config
+        if token.style and not _quote_match(quote_type, token.style):
+            msg = "string value is not quoted with %s quotes" % (quote_type)
+
+    elif not token.plain:
+
+        # Quotes are disallowed when not needed
+        if (tag == DEFAULT_SCALAR_TAG and token.value and
+                not _quotes_are_needed(token.value)):
+            msg = "string value is redundantly quoted with %s quotes" % (
+                quote_type)
+
+        # But when used need to match config
+        elif token.style and not _quote_match(quote_type, token.style):
+            msg = "string value is not quoted with %s quotes" % (quote_type)
+
+    if msg is not None:
+        yield LintProblem(
+            token.start_mark.line + 1,
+            token.start_mark.column + 1,
+            msg)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yamllint-1.20.0/yamllint/rules/truthy.py 
new/yamllint-1.22.1/yamllint/rules/truthy.py
--- old/yamllint-1.20.0/yamllint/rules/truthy.py        2019-08-27 
09:46:03.000000000 +0200
+++ new/yamllint-1.22.1/yamllint/rules/truthy.py        2020-04-08 
12:30:35.000000000 +0200
@@ -30,6 +30,9 @@
   ``'False'``, ``'false'``, ``'YES'``, ``'Yes'``, ``'yes'``, ``'NO'``,
   ``'No'``, ``'no'``, ``'ON'``, ``'On'``, ``'on'``, ``'OFF'``, ``'Off'``,
   ``'off'``.
+* ``check-keys`` disables verification for keys in mappings. By default,
+  ``truthy`` rule applies to both keys and values. Set this option to ``false``
+  to prevent this.
 
 .. rubric:: Examples
 
@@ -92,6 +95,22 @@
     - false
     - on
     - off
+
+#. With ``truthy: {check-keys: false}``
+
+   the following code snippet would **PASS**:
+   ::
+
+    yes:  1
+    on:   2
+    true: 3
+
+   the following code snippet would **FAIL**:
+   ::
+
+    yes:  Yes
+    on:   On
+    true: True
 """
 
 import yaml
@@ -109,14 +128,18 @@
 
 ID = 'truthy'
 TYPE = 'token'
-CONF = {'allowed-values': list(TRUTHY)}
-DEFAULT = {'allowed-values': ['true', 'false']}
+CONF = {'allowed-values': list(TRUTHY), 'check-keys': bool}
+DEFAULT = {'allowed-values': ['true', 'false'], 'check-keys': True}
 
 
 def check(conf, token, prev, next, nextnext, context):
     if prev and isinstance(prev, yaml.tokens.TagToken):
         return
 
+    if (not conf['check-keys'] and isinstance(prev, yaml.tokens.KeyToken) and
+            isinstance(token, yaml.tokens.ScalarToken)):
+        return
+
     if isinstance(token, yaml.tokens.ScalarToken):
         if (token.value in (set(TRUTHY) - set(conf['allowed-values'])) and
                 token.style is None):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yamllint-1.20.0/yamllint.egg-info/PKG-INFO 
new/yamllint-1.22.1/yamllint.egg-info/PKG-INFO
--- old/yamllint-1.20.0/yamllint.egg-info/PKG-INFO      2019-12-26 
16:07:34.000000000 +0100
+++ new/yamllint-1.22.1/yamllint.egg-info/PKG-INFO      2020-04-15 
07:57:48.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: yamllint
-Version: 1.20.0
+Version: 1.22.1
 Summary: A linter for YAML files.
 Home-page: https://github.com/adrienverge/yamllint
 Author: Adrien Vergé


Reply via email to