Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-curlylint for 
openSUSE:Factory checked in at 2022-01-16 23:18:30
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-curlylint (Old)
 and      /work/SRC/openSUSE:Factory/.python-curlylint.new.1892 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-curlylint"

Sun Jan 16 23:18:30 2022 rev:2 rq:946790 version:0.13.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-curlylint/python-curlylint.changes        
2021-04-25 21:26:04.080332701 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-curlylint.new.1892/python-curlylint.changes  
    2022-01-16 23:19:24.950381932 +0100
@@ -1,0 +2,11 @@
+Sun Jan 16 13:35:24 UTC 2022 - Dirk M??ller <dmuel...@suse.com>
+
+- update to 0.13.0:
+  * Implement --template-tags CLI flag (#25, #77).
+  * Changed
+  * Add more descriptive error message for missing whitespace between HTML 
attributes
+  * Move development dependencies from extras to separate requirements.txt
+  * Declare support for Python 3.9.
+  * Tentatively declare support for Python 3.10
+
+-------------------------------------------------------------------

Old:
----
  curlylint-0.12.2.tar.gz

New:
----
  curlylint-0.13.0.tar.gz

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

Other differences:
------------------
++++++ python-curlylint.spec ++++++
--- /var/tmp/diff_new_pack.DJapo6/_old  2022-01-16 23:19:25.310382109 +0100
+++ /var/tmp/diff_new_pack.DJapo6/_new  2022-01-16 23:19:25.318382112 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-curlylint
 #
-# Copyright (c) 2021 SUSE LLC
+# Copyright (c) 2022 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define skip_python2 1
 Name:           python-curlylint
-Version:        0.12.2
+Version:        0.13.0
 Release:        0
 Summary:        HTML templates linting for Jinja, Nunjucks, Django templates, 
Twig, Liquid
 License:        MIT

++++++ curlylint-0.12.2.tar.gz -> curlylint-0.13.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/PKG-INFO 
new/curlylint-0.13.0/PKG-INFO
--- old/curlylint-0.12.2/PKG-INFO       2021-03-07 00:42:59.000000000 +0100
+++ new/curlylint-0.13.0/PKG-INFO       2021-04-25 10:12:38.288876000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: curlylint
-Version: 0.12.2
+Version: 0.13.0
 Summary: {{ ????}} Experimental HTML templates linting for Jinja, Nunjucks, 
Django templates, Twig, Liquid
 Home-page: https://github.com/thibaudcolas/curlylint
 Author: Thibaud Colas
@@ -8,7 +8,7 @@
 License: MIT
 Description: # [curlylint](https://www.curlylint.org/) [<img 
src="https://raw.githubusercontent.com/thibaudcolas/curlylint/main/.github/curlylint-logo.svg?sanitize=true";
 width="250" height="100" align="right" alt="">](https://www.curlylint.org/)
         
-        
[![PyPI](https://img.shields.io/pypi/v/curlylint.svg)](https://pypi.org/project/curlylint/)
 [![PyPI 
downloads](https://img.shields.io/pypi/dm/curlylint.svg)](https://pypi.org/project/curlylint/)
 [![Build 
status](https://github.com/thibaudcolas/curlylint/workflows/CI/badge.svg)](https://github.com/thibaudcolas/curlylint/actions)
 [![Total 
alerts](https://img.shields.io/lgtm/alerts/g/thibaudcolas/curlylint.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/thibaudcolas/curlylint/alerts/)
+        
[![PyPI](https://img.shields.io/pypi/v/curlylint.svg)](https://pypi.org/project/curlylint/)
 [![PyPI 
downloads](https://img.shields.io/pypi/dm/curlylint.svg)](https://pypi.org/project/curlylint/)
 [![Build 
status](https://github.com/thibaudcolas/curlylint/workflows/CI/badge.svg)](https://github.com/thibaudcolas/curlylint/actions)
 [![Coverage 
Status](https://coveralls.io/repos/github/thibaudcolas/curlylint/badge.svg?branch=main)](https://coveralls.io/github/thibaudcolas/curlylint?branch=main)
 [![Total 
alerts](https://img.shields.io/lgtm/alerts/g/thibaudcolas/curlylint.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/thibaudcolas/curlylint/alerts/)
         
         > **{{ ????}}** Experimental HTML templates linting for 
[Jinja](https://jinja.palletsprojects.com/), 
[Nunjucks](https://mozilla.github.io/nunjucks/), [Django 
templates](https://docs.djangoproject.com/en/dev/topics/templates/), 
[Twig](https://twig.symfony.com/), [Liquid](https://shopify.github.io/liquid/).
         > Forked from [jinjalint](https://github.com/motet-a/jinjalint).
@@ -22,7 +22,7 @@
         On the roadmap:
         
         - More checks for common accessibility issues in HTML.
-        - Checks for common security issues ?????for exameple `rel="noopener 
noreferrer"`, or known sources of XSS vulnerabilities.
+        - Checks for common security issues ?????for example `rel="noopener 
noreferrer"`, or known sources of XSS vulnerabilities.
         - More [ideas welcome](https://www.curlylint.org/docs/reference/ideas)!
         
         ## Usage
@@ -69,6 +69,7 @@
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
 Requires-Python: >=3.6
 Description-Content-Type: text/markdown
-Provides-Extra: dev
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/README.md 
new/curlylint-0.13.0/README.md
--- old/curlylint-0.12.2/README.md      2021-03-07 00:30:40.000000000 +0100
+++ new/curlylint-0.13.0/README.md      2021-03-13 23:36:51.000000000 +0100
@@ -1,6 +1,6 @@
 # [curlylint](https://www.curlylint.org/) [<img 
src="https://raw.githubusercontent.com/thibaudcolas/curlylint/main/.github/curlylint-logo.svg?sanitize=true";
 width="250" height="100" align="right" alt="">](https://www.curlylint.org/)
 
-[![PyPI](https://img.shields.io/pypi/v/curlylint.svg)](https://pypi.org/project/curlylint/)
 [![PyPI 
downloads](https://img.shields.io/pypi/dm/curlylint.svg)](https://pypi.org/project/curlylint/)
 [![Build 
status](https://github.com/thibaudcolas/curlylint/workflows/CI/badge.svg)](https://github.com/thibaudcolas/curlylint/actions)
 [![Total 
alerts](https://img.shields.io/lgtm/alerts/g/thibaudcolas/curlylint.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/thibaudcolas/curlylint/alerts/)
+[![PyPI](https://img.shields.io/pypi/v/curlylint.svg)](https://pypi.org/project/curlylint/)
 [![PyPI 
downloads](https://img.shields.io/pypi/dm/curlylint.svg)](https://pypi.org/project/curlylint/)
 [![Build 
status](https://github.com/thibaudcolas/curlylint/workflows/CI/badge.svg)](https://github.com/thibaudcolas/curlylint/actions)
 [![Coverage 
Status](https://coveralls.io/repos/github/thibaudcolas/curlylint/badge.svg?branch=main)](https://coveralls.io/github/thibaudcolas/curlylint?branch=main)
 [![Total 
alerts](https://img.shields.io/lgtm/alerts/g/thibaudcolas/curlylint.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/thibaudcolas/curlylint/alerts/)
 
 > **{{ ????}}** Experimental HTML templates linting for 
 > [Jinja](https://jinja.palletsprojects.com/), 
 > [Nunjucks](https://mozilla.github.io/nunjucks/), [Django 
 > templates](https://docs.djangoproject.com/en/dev/topics/templates/), 
 > [Twig](https://twig.symfony.com/), 
 > [Liquid](https://shopify.github.io/liquid/).
 > Forked from [jinjalint](https://github.com/motet-a/jinjalint).
@@ -14,7 +14,7 @@
 On the roadmap:
 
 - More checks for common accessibility issues in HTML.
-- Checks for common security issues ?????for exameple `rel="noopener 
noreferrer"`, or known sources of XSS vulnerabilities.
+- Checks for common security issues ?????for example `rel="noopener 
noreferrer"`, or known sources of XSS vulnerabilities.
 - More [ideas welcome](https://www.curlylint.org/docs/reference/ideas)!
 
 ## Usage
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/curlylint/__init__.py 
new/curlylint-0.13.0/curlylint/__init__.py
--- old/curlylint-0.12.2/curlylint/__init__.py  2021-03-07 00:37:57.000000000 
+0100
+++ new/curlylint-0.13.0/curlylint/__init__.py  2021-04-25 10:11:49.000000000 
+0200
@@ -1,5 +1,5 @@
 __name__ = "curlylint"
-__version__ = "0.12.2"
+__version__ = "0.13.0"
 __description__ = "{{ ????}} Experimental HTML templates linting for Jinja, 
Nunjucks, Django templates, Twig, Liquid"
 __author__ = "Thibaud Colas"
 __author_email__ = "thibaudco...@gmail.com"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/curlylint/cli.py 
new/curlylint-0.13.0/curlylint/cli.py
--- old/curlylint-0.12.2/curlylint/cli.py       2021-03-06 16:10:55.000000000 
+0100
+++ new/curlylint-0.13.0/curlylint/cli.py       2021-04-19 07:48:11.000000000 
+0200
@@ -1,11 +1,22 @@
 import re
 from functools import partial
 from pathlib import Path
-from typing import Any, Dict, Mapping, Optional, Pattern, Set, Tuple, Union
+from typing import (
+    Any,
+    Dict,
+    List,
+    Mapping,
+    Optional,
+    Pattern,
+    Set,
+    Tuple,
+    Union,
+)
 
 import click  # lgtm [py/import-and-import-from]
 
 from curlylint.rule_param import RULE
+from curlylint.template_tags_param import TEMPLATE_TAGS
 
 from . import __version__
 from .config import (
@@ -122,6 +133,15 @@
     ),
     multiple=True,
 )
+@click.option(
+    "--template-tags",
+    type=TEMPLATE_TAGS,
+    default="[]",
+    help=(
+        'Specify additional sets of template tags, with the syntax 
--template-tags \'[["cache", "endcache"]]\'. '
+    ),
+    show_default=True,
+)
 @click.argument(
     "src",
     nargs=-1,
@@ -161,6 +181,7 @@
     include: str,
     exclude: str,
     rule: Union[Mapping[str, Any], Tuple[Mapping[str, Any], ...]],
+    template_tags: List[List[str]],
     src: Tuple[str, ...],
 ) -> None:
     """Prototype linter for Jinja and Django templates, forked from 
jinjalint"""
@@ -236,6 +257,7 @@
     configuration["rules"] = rules
     configuration["verbose"] = verbose
     configuration["parse_only"] = parse_only
+    configuration["template_tags"] = template_tags
 
     if stdin_filepath:
         configuration["stdin_filepath"] = Path(stdin_filepath)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/curlylint/cli_test.py 
new/curlylint-0.13.0/curlylint/cli_test.py
--- old/curlylint-0.12.2/curlylint/cli_test.py  1970-01-01 01:00:00.000000000 
+0100
+++ new/curlylint-0.13.0/curlylint/cli_test.py  2021-04-19 07:48:11.000000000 
+0200
@@ -0,0 +1,100 @@
+import unittest
+
+from io import BytesIO
+from typing import List
+
+from curlylint.tests.utils import BlackRunner
+
+from curlylint.cli import main
+
+
+class TestCLI(unittest.TestCase):
+    """
+    Heavily inspired by Black???s CLI tests.
+    See https://github.com/psf/black/blob/master/tests/test_black.py.
+    """
+
+    def invoke_curlylint(
+        self, exit_code: int, args: List[str], input: str = None
+    ):
+        runner = BlackRunner()
+        result = runner.invoke(
+            main, args, input=BytesIO(input.encode("utf8")) if input else None
+        )
+        self.assertEqual(
+            result.exit_code,
+            exit_code,
+            msg=(
+                f"Failed with args: {args}\n"
+                f"stdout: {runner.stdout_bytes.decode()!r}\n"
+                f"stderr: {runner.stderr_bytes.decode()!r}\n"
+                f"exception: {result.exception}"
+            ),
+        )
+        return runner
+
+    def test_no_flag(self):
+        runner = self.invoke_curlylint(0, [])
+        self.assertEqual(runner.stdout_bytes.decode(), "")
+        self.assertEqual(
+            runner.stderr_bytes.decode(), "No Path provided. Nothing to do 
????\n"
+        )
+
+    def test_stdin(self):
+        runner = self.invoke_curlylint(0, ["-"], input="<p>Hello, World!</p>")
+        self.assertEqual(runner.stdout_bytes.decode(), "")
+        self.assertEqual(runner.stderr_bytes.decode(), "All done! ??? ???? 
???\n\n")
+
+    def test_stdin_verbose(self):
+        runner = self.invoke_curlylint(
+            0, ["--verbose", "-"], input="<p>Hello, World!</p>"
+        )
+        self.assertEqual(runner.stdout_bytes.decode(), "")
+        self.assertIn(
+            "Identified project root as:", runner.stderr_bytes.decode()
+        )
+        self.assertIn(
+            """Analyzing file content from stdin
+Files being analyzed:
+-
+All done! ??? ???? ???
+""",
+            runner.stderr_bytes.decode(),
+        )
+
+    def test_flag_help(self):
+        runner = self.invoke_curlylint(0, ["--help"])
+        self.assertIn(
+            "Prototype linter for Jinja and Django templates",
+            runner.stdout_bytes.decode(),
+        )
+        self.assertEqual(runner.stderr_bytes.decode(), "")
+
+    def test_template_tags_validation_fail_no_nesting(self):
+        runner = self.invoke_curlylint(
+            2,
+            ["--template-tags", '["cache", "endcache"]', "-"],
+            input="<p>Hello, World!</p>",
+        )
+        self.assertIn(
+            "Error: Invalid value for '--template-tags': expected a list of 
lists of tags as JSON, got '[\"cache\", \"endcache\"]'",
+            runner.stderr_bytes.decode(),
+        )
+
+    def test_template_tags_cli_configured(self):
+        self.invoke_curlylint(
+            0,
+            ["--template-tags", '[["of", "elseof", "endof"]]', "-"],
+            input="<p>{% of a %}c{% elseof %}test{% endof %}</p>",
+        )
+
+    def test_template_tags_cli_unconfigured_fails(self):
+        runner = self.invoke_curlylint(
+            1,
+            ["--template-tags", "[]", "-"],
+            input="<p>{% of a %}c{% elseof %}test{% endof %}</p>",
+        )
+        self.assertIn(
+            "Parse error: expected one of 'autoescape', 'block', 'blocktrans', 
'comment', 'filter', 'for', 'if', 'ifchanged', 'ifequal', 'ifnotequal', 'not an 
intermediate Jinja tag name', 'spaceless', 'verbatim', 'with' at 
0:17\tparse_error",
+            runner.stdout_bytes.decode(),
+        )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/curlylint/parse.py 
new/curlylint-0.13.0/curlylint/parse.py
--- old/curlylint-0.12.2/curlylint/parse.py     2021-03-06 16:10:55.000000000 
+0100
+++ new/curlylint-0.13.0/curlylint/parse.py     2021-04-19 07:48:11.000000000 
+0200
@@ -150,6 +150,7 @@
 # XXX: It could be better and simpler to only allow ASCII whitespaces here.
 whitespace = P.regex(r"\s*")
 mandatory_whitespace = P.regex(r"\s+")
+spaces_between_attr = mandatory_whitespace.desc("space(s) between attributes")
 
 
 def until(parser):
@@ -415,7 +416,7 @@
 
     attrs = interpolated(
         (
-            whitespace.then(jinja_attr) | mandatory_whitespace.then(attribute)
+            whitespace.then(jinja_attr) | spaces_between_attr.then(attribute)
         ).many()
     )
 
@@ -591,6 +592,8 @@
         (names[0], names)
         for names in (
             DEFAULT_JINJA_STRUCTURED_ELEMENTS_NAMES
+            + config.get("template_tags", [])
+            # Deprecated, will be removed in a future release.
             + config.get("jinja_custom_elements_names", [])
         )
     ).values()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/curlylint/parse_test.py 
new/curlylint-0.13.0/curlylint/parse_test.py
--- old/curlylint-0.12.2/curlylint/parse_test.py        2021-03-06 
16:10:55.000000000 +0100
+++ new/curlylint-0.13.0/curlylint/parse_test.py        2021-04-19 
07:48:11.000000000 +0200
@@ -1,3 +1,6 @@
+import unittest
+import pytest
+
 import parsy as P
 
 from . import ast
@@ -72,267 +75,438 @@
 String = with_dummy_locations(ast.String)
 
 
-def test_dummy_location():
-    dummy = DummyLocation()
-    real = Location(0, 0, 0)
-    assert dummy == real
-    assert real == dummy
-
-    assert not dummy != real  # lgtm [py/redundant-comparison]
-    assert not real != dummy  # lgtm [py/redundant-comparison]
-
-
-def test_tag_name():
-    assert tag_name_char.parse("a") == "a"
-    assert tag_name.parse("bcd-ef9") == "bcd-ef9"
-    assert tag_name.parse("clipPath") == "clipPath"
-    assert tag_name.parse("HTML") == "HTML"
-
-
-def test_attribute_value():
-    assert attribute_value.parse("hello-world") == String(
-        value=Interp("hello-world"), quote=None
-    )
-
-    assert attribute_value.parse("hello{{a}}world") == String(
-        value=Interp(["hello", JinjaVariable(content="a"), "world"]), 
quote=None
-    )
-
-    assert attribute_value.parse("123") == Integer(value=123, 
has_percent=False)
-
-    assert attribute_value.parse('"hello"') == String(
-        value=Interp("hello"), quote='"'
-    )
-
-    assert attribute_value.parse("'hello'") == String(
-        value=Interp("hello"), quote="'"
-    )
+class TestUtil(unittest.TestCase):
+    def test_dummy_location(self):
+        dummy = DummyLocation()
+        real = Location(0, 0, 0)
+        self.assertEqual(dummy, real)
+        self.assertEqual(real, dummy)
+
+        self.assertEqual(dummy != real, False)  # lgtm 
[py/redundant-comparison]
+        self.assertEqual(real != dummy, False)  # lgtm 
[py/redundant-comparison]
+
+
+class TestParser(unittest.TestCase):
+    def test_tag_name(self):
+        self.assertEqual(tag_name_char.parse("a"), "a")
+        self.assertEqual(tag_name.parse("bcd-ef9"), "bcd-ef9")
+        self.assertEqual(tag_name.parse("clipPath"), "clipPath")
+        self.assertEqual(tag_name.parse("HTML"), "HTML")
+
+    def test_attribute_value(self):
+        self.assertEqual(
+            attribute_value.parse("hello-world"),
+            String(value=Interp("hello-world"), quote=None),
+        )
+
+        self.assertEqual(
+            attribute_value.parse("hello{{a}}world"),
+            String(
+                value=Interp(["hello", JinjaVariable(content="a"), "world"]),
+                quote=None,
+            ),
+        )
 
-    assert attribute_value.parse("''") == String(value=Interp([]), quote="'")
+        self.assertEqual(
+            attribute_value.parse("123"), Integer(value=123, has_percent=False)
+        )
+
+        self.assertEqual(
+            attribute_value.parse('"hello"'),
+            String(value=Interp("hello"), quote='"'),
+        )
+
+        self.assertEqual(
+            attribute_value.parse("'hello'"),
+            String(value=Interp("hello"), quote="'"),
+        )
+
+        self.assertEqual(
+            attribute_value.parse("''"), String(value=Interp([]), quote="'")
+        )
+
+        self.assertEqual(
+            attribute_value.parse("'hello{{b}}world'"),
+            String(
+                value=Interp(["hello", JinjaVariable(content="b"), "world"]),
+                quote="'",
+            ),
+        )
 
-    assert attribute_value.parse("'hello{{b}}world'") == String(
-        value=Interp(["hello", JinjaVariable(content="b"), "world"]), quote="'"
-    )
+    def test_attribute(self):
+        self.assertEqual(
+            attribute.parse("hello=world"),
+            Attribute(
+                name=Interp("hello"),
+                value=Interp(String(value=Interp("world"), quote=None)),
+            ),
+        )
 
+        self.assertEqual(
+            attribute.parse('a= "b"'),
+            Attribute(
+                name=Interp("a"),
+                value=Interp(String(value=Interp("b"), quote='"')),
+            ),
+        )
 
-def test_attribute():
-    assert attribute.parse("hello=world") == Attribute(
-        name=Interp("hello"),
-        value=Interp(String(value=Interp("world"), quote=None)),
-    )
+        self.assertEqual(
+            attribute.parse('A="b"'),
+            Attribute(
+                name=Interp("A"),
+                value=Interp(String(value=Interp("b"), quote='"')),
+            ),
+        )
 
-    assert attribute.parse('a= "b"') == Attribute(
-        name=Interp("a"), value=Interp(String(value=Interp("b"), quote='"'))
-    )
+        self.assertEqual(
+            attribute.parse('viewBox="b"'),
+            Attribute(
+                name=Interp("viewBox"),
+                value=Interp(String(value=Interp("b"), quote='"')),
+            ),
+        )
 
-    assert attribute.parse('A="b"') == Attribute(
-        name=Interp("A"), value=Interp(String(value=Interp("b"), quote='"'))
-    )
+        self.assertEqual(
+            attribute.parse("a =b_c23"),
+            Attribute(
+                name=Interp("a"),
+                value=Interp(String(value=Interp("b_c23"), quote=None)),
+            ),
+        )
 
-    assert attribute.parse('viewBox="b"') == Attribute(
-        name=Interp("viewBox"),
-        value=Interp(String(value=Interp("b"), quote='"')),
-    )
+        self.assertEqual(
+            attribute.parse("valueless-attribute"),
+            Attribute(name=Interp("valueless-attribute"), value=None),
+        )
+
+    def test_comment(self):
+        self.assertEqual(
+            comment.parse("<!--hello--world-->"), Comment(text="hello--world")
+        )
+
+    def test_jinja_comment(self):
+        self.assertEqual(
+            jinja_comment.parse("{# hello world #}"),
+            JinjaComment(text="hello world"),
+        )
+
+    def test_opening_tag(self):
+        self.assertEqual(
+            opening_tag.parse("<div>"),
+            OpeningTag(name="div", attributes=Interp([])),
+        )
+
+        self.assertEqual(
+            opening_tag.parse("<div\n >"),
+            OpeningTag(name="div", attributes=Interp([])),
+        )
+
+        self.assertEqual(
+            opening_tag.parse('<div class="red" style="" >'),
+            OpeningTag(
+                name="div",
+                attributes=Interp(
+                    [
+                        Attribute(
+                            name=Interp("class"),
+                            value=Interp(
+                                String(value=Interp("red"), quote='"')
+                            ),
+                        ),
+                        Attribute(
+                            name=Interp("style"),
+                            value=Interp(String(value=Interp([]), quote='"')),
+                        ),
+                    ]
+                ),
+            ),
+        )
 
-    assert attribute.parse("a =b_c23") == Attribute(
-        name=Interp("a"),
-        value=Interp(String(value=Interp("b_c23"), quote=None)),
-    )
+    def test_opening_tag_attributes_no_space(self):
+        # See https://html.spec.whatwg.org/multipage/syntax.html#start-tags
+        # "Attributes must be separated from each other by one or more ASCII 
whitespace."
+        # See 
https://github.com/thibaudcolas/curlylint/issues/23#issuecomment-700622837
+        with pytest.raises(
+            P.ParseError, match="space\\(s\\) between attributes",
+        ):
+            opening_tag.parse(
+                '<a class="govuk-button"href="/cookies">Set cookie 
preferences</a>'
+            )
 
-    assert attribute.parse("valueless-attribute") == Attribute(
-        name=Interp("valueless-attribute"), value=None
-    )
+    def test_opening_tag_jinja_block_attributes_whitespace_after(self):
+        opening_tag.parse(
+            '<form{% if has_file_field %} enctype="multipart/form-data"{% 
endif %} action="{{ form_url }}">'
+        )
+
+    @pytest.mark.skip(reason="no way of currently testing this")
+    def test_opening_tag_jinja_block_attributes_whitespace_before_37(self):
+        # https://github.com/thibaudcolas/curlylint/issues/37
+        opening_tag.parse(
+            '<form {% if has_file_field %}enctype="multipart/form-data" {% 
endif %}action="{{ form_url }}">'
+        )
+
+    def test_closing_tag(self):
+        closing_tag = make_closing_tag_parser(P.string("div"))
+        self.assertEqual(closing_tag.parse("</div>"), ClosingTag(name="div"))
+
+    def test_raw_text_elements(self):
+        self.assertEqual(
+            element.parse("<style a=b> <wont-be-parsed> </style>"),
+            Element(
+                content=" <wont-be-parsed> ",
+                opening_tag=OpeningTag(
+                    name="style",
+                    attributes=Interp(
+                        [
+                            Attribute(
+                                name=Interp("a"),
+                                value=Interp(
+                                    String(value=Interp("b"), quote=None)
+                                ),
+                            )
+                        ]
+                    ),
+                ),
+                closing_tag=ClosingTag(name="style"),
+            ),
+        )
 
+    def test_element(self):
+        self.assertEqual(
+            element.parse("<div> hey </div>"),
+            Element(
+                opening_tag=OpeningTag(name="div", attributes=Interp([])),
+                content=Interp([" hey "]),
+                closing_tag=ClosingTag(name="div"),
+            ),
+        )
 
-def test_comment():
-    assert comment.parse("<!--hello--world-->") == Comment(text="hello--world")
+        attributes = [
+            Attribute(
+                name=Interp("onclick"),
+                value=Interp(String(value=Interp([]), quote='"')),
+            ),
+            JinjaVariable(content="var"),
+            Attribute(
+                name=Interp("class"),
+                value=Interp(String(value=Interp("red"), quote='"')),
+            ),
+        ]
 
+        self.assertEqual(
+            element.parse('<br onclick="" {{var}} class="red">'),
+            Element(
+                opening_tag=OpeningTag(
+                    name="br", attributes=Interp(attributes)
+                ),
+                closing_tag=None,
+                content=None,
+            ),
+        )
 
-def test_jinja_comment():
-    assert jinja_comment.parse("{# hello world #}") == JinjaComment(
-        text="hello world"
-    )
+        src = "<{% if a %}bcd{% endif %}></{% if a %}bcd{% endif %}>"
+        self.assertEqual(src, str(element.parse(src)))
 
+        src = '<div{{ "ider" }}></div>'
+        self.assertEqual('<div {{ "ider" }}></div>', str(element.parse(src)))
 
-def test_opening_tag():
-    assert opening_tag.parse("<div>") == OpeningTag(
-        name="div", attributes=Interp([])
-    )
+        src = '<div{% if a %}foo="bar"{% endif %}></div>'
+        self.assertEqual(
+            '<div {% if a %}foo="bar"{% endif %}></div>',
+            str(element.parse(src)),
+        )
+
+        src = '<div{% if a %} foo="bar"  a=2 {% endif %}></div>'
+        self.assertEqual(
+            '<div {% if a %}foo="bar"a=2{% endif %}></div>',
+            str(element.parse(src)),
+        )
+
+        src = "<colgroup></colgroup>"
+        self.assertEqual(src, str(element.parse(src)))
+
+    def test_self_closing_elements(self):
+        self.assertEqual(
+            element.parse("<br>"),
+            Element(
+                opening_tag=OpeningTag(name="br", attributes=Interp([])),
+                content=None,
+                closing_tag=None,
+            ),
+        )
 
-    assert opening_tag.parse("<div\n >") == OpeningTag(
-        name="div", attributes=Interp([])
-    )
+        src = "<br />"
+        self.assertEqual(src, str(element.parse(src)))
 
-    assert opening_tag.parse('<div class="red" style="" >') == OpeningTag(
-        name="div",
-        attributes=Interp(
-            [
-                Attribute(
-                    name=Interp("class"),
-                    value=Interp(String(value=Interp("red"), quote='"')),
-                ),
-                Attribute(
-                    name=Interp("style"),
-                    value=Interp(String(value=Interp([]), quote='"')),
-                ),
-            ]
-        ),
-    )
-
-
-def test_closing_tag():
-    closing_tag = make_closing_tag_parser(P.string("div"))
-    assert closing_tag.parse("</div>") == ClosingTag(name="div")
-
-
-def test_raw_text_elements():
-    assert element.parse("<style a=b> <wont-be-parsed> </style>") == Element(
-        content=" <wont-be-parsed> ",
-        opening_tag=OpeningTag(
-            name="style",
-            attributes=Interp(
-                [
-                    Attribute(
-                        name=Interp("a"),
-                        value=Interp(String(value=Interp("b"), quote=None)),
+    def test_jinja_blocks(self):
+        self.assertEqual(
+            jinja.parse("{% name something == 123 %}"),
+            JinjaElement(
+                parts=[
+                    JinjaElementPart(
+                        tag=JinjaTag(name="name", content="something == 123"),
+                        content=None,
                     )
-                ]
+                ],
+                closing_tag=None,
             ),
-        ),
-        closing_tag=ClosingTag(name="style"),
-    )
-
-
-def test_element():
-    assert element.parse("<div> hey </div>") == Element(
-        opening_tag=OpeningTag(name="div", attributes=Interp([])),
-        content=Interp([" hey "]),
-        closing_tag=ClosingTag(name="div"),
-    )
-
-    attributes = [
-        Attribute(
-            name=Interp("onclick"),
-            value=Interp(String(value=Interp([]), quote='"')),
-        ),
-        JinjaVariable(content="var"),
-        Attribute(
-            name=Interp("class"),
-            value=Interp(String(value=Interp("red"), quote='"')),
-        ),
-    ]
-
-    assert element.parse('<br onclick="" {{var}} class="red">') == Element(
-        opening_tag=OpeningTag(name="br", attributes=Interp(attributes)),
-        closing_tag=None,
-        content=None,
-    )
-
-    src = "<{% if a %}bcd{% endif %}></{% if a %}bcd{% endif %}>"
-    assert src == str(element.parse(src))
-
-    src = '<div{{ "ider" }}></div>'
-    assert '<div {{ "ider" }}></div>' == str(element.parse(src))
-
-    src = '<div{% if a %}foo="bar"{% endif %}></div>'
-    assert '<div {% if a %}foo="bar"{% endif %}></div>' == str(
-        element.parse(src)
-    )
-
-    src = '<div{% if a %} foo="bar"  a=2 {% endif %}></div>'
-    assert '<div {% if a %}foo="bar"a=2{% endif %}></div>' == str(
-        element.parse(src)
-    )
-
-    src = "<colgroup></colgroup>"
-    assert src == str(element.parse(src))
-
-
-def test_self_closing_elements():
-    assert element.parse("<br>") == Element(
-        opening_tag=OpeningTag(name="br", attributes=Interp([])),
-        content=None,
-        closing_tag=None,
-    )
-
-    src = "<br />"
-    assert src == str(element.parse(src))
-
-
-def test_jinja_blocks():
-    assert jinja.parse("{% name something == 123 %}") == JinjaElement(
-        parts=[
-            JinjaElementPart(
-                tag=JinjaTag(name="name", content="something == 123"),
-                content=None,
-            )
-        ],
-        closing_tag=None,
-    )
-
-    assert jinja.parse("{% if a %}b{% else %}c{% endif %}") == JinjaElement(
-        parts=[
-            JinjaElementPart(
-                tag=JinjaTag(name="if", content="a"), content=Interp(["b"])
-            ),
-            JinjaElementPart(
-                tag=JinjaTag(name="else", content=""), content=Interp(["c"])
-            ),
-        ],
-        closing_tag=JinjaTag(name="endif", content=""),
-    )
-
-    src = "{% if a %}b{% elif %}c{% elif %}d{% else %}e{% endif %}"
-    assert src == str(jinja.parse(src))
-
-
-def test_jinja_whitespace_controls():
-    assert jinja.parse("{%- foo -%}") == JinjaElement(
-        parts=[
-            JinjaElementPart(
-                tag=JinjaTag(
-                    name="foo", content="", left_minus=True, right_minus=True
-                ),
-                content=None,
-            )
-        ],
-        closing_tag=None,
-    )
-
-    assert str(jinja.parse("{%- foo -%}")) == "{%- foo -%}"
-    assert str(jinja.parse("{%- foo %}")) == "{%- foo %}"
-    assert str(jinja.parse("{{- bar -}}")) == "{{- bar -}}"
-    assert str(jinja.parse("{{ bar -}}")) == "{{ bar -}}"
-    assert str(jinja.parse("{%+ foo %}")) == "{%+ foo %}"
-    assert str(jinja.parse("{{+ bar }}")) == "{{+ bar }}"
-
-
-def test_doctype():
-    assert content.parse("<!DOCTYPE html>") == Interp("<!DOCTYPE html>")
+        )
 
+        self.assertEqual(
+            jinja.parse("{% if a %}b{% else %}c{% endif %}"),
+            JinjaElement(
+                parts=[
+                    JinjaElementPart(
+                        tag=JinjaTag(name="if", content="a"),
+                        content=Interp(["b"]),
+                    ),
+                    JinjaElementPart(
+                        tag=JinjaTag(name="else", content=""),
+                        content=Interp(["c"]),
+                    ),
+                ],
+                closing_tag=JinjaTag(name="endif", content=""),
+            ),
+        )
 
-def test_attrs():
-    attrs = make_attributes_parser({}, jinja)
-    parse = attrs.parse
+        src = "{% if a %}b{% elif %}c{% elif %}d{% else %}e{% endif %}"
+        self.assertEqual(src, str(jinja.parse(src)))
 
-    assert str(parse("{% if %}{% endif %}")) == "{% if %}{% endif %}"
-    assert str(parse("{% if %}  {% endif %}")) == "{% if %}{% endif %}"
-    assert str(parse("{% if %}a=b{% endif %}")) == "{% if %}a=b{% endif %}"
-    assert str(parse("{% if %} a=b {% endif %}")) == "{% if %}a=b{% endif %}"
+    def test_jinja_custom_tag_self_closing(self):
+        self.assertEqual(
+            jinja.parse("{% potato %}"),
+            JinjaElement(
+                parts=[
+                    JinjaElementPart(
+                        tag=JinjaTag(name="potato", content=""), content=None,
+                    )
+                ],
+                closing_tag=None,
+            ),
+        )
 
+    def test_jinja_custom_tag_open_close_unconfigured(self):
+        with pytest.raises(P.ParseError):
+            jinja.parse("{% of a %}c{% endof %}")
+
+    def test_jinja_custom_tag_open_close_configured_deprecated(self):
+        # Deprecated, will be removed in a future release.
+        parser = make_parser({"jinja_custom_elements_names": [["of", 
"endof"]]})
+        jinja = parser["jinja"]
+        self.assertEqual(
+            jinja.parse("{% of a %}c{% endof %}"),
+            JinjaElement(
+                parts=[
+                    JinjaElementPart(
+                        tag=JinjaTag(name="of", content="a"),
+                        content=Interp(["c"]),
+                    ),
+                ],
+                closing_tag=JinjaTag(name="endof", content=""),
+            ),
+        )
 
-def test_optional_container():
-    src = '{% if a %}<a href="b">{% endif %}c<b>d</b>{% if a %}</a>{% endif %}'
-    assert src == str(content.parse(src))
+    def test_jinja_custom_tag_open_close_configured(self):
+        parser = make_parser({"template_tags": [["of", "endof"]]})
+        jinja = parser["jinja"]
+        self.assertEqual(
+            jinja.parse("{% of a %}c{% endof %}"),
+            JinjaElement(
+                parts=[
+                    JinjaElementPart(
+                        tag=JinjaTag(name="of", content="a"),
+                        content=Interp(["c"]),
+                    ),
+                ],
+                closing_tag=JinjaTag(name="endof", content=""),
+            ),
+        )
 
-    src = """
-    {% if a %} <a href="b"> {% endif %}
-        c <b> d </b>
-    {% if a %} </a> {% endif %}
-    """
-    content.parse(src)
+    def test_jinja_custom_tag_open_middle_close_unconfigured(self):
+        with pytest.raises(P.ParseError):
+            jinja.parse("{% of a %}b{% elseof %}c{% endof %}")
+
+    def test_jinja_custom_tag_open_middle_close(self):
+        parser = make_parser(
+            {"jinja_custom_elements_names": [["of", "elseof", "endof"]]}
+        )
+        jinja = parser["jinja"]
+        self.assertEqual(
+            jinja.parse("{% of a %}b{% elseof %}c{% endof %}"),
+            JinjaElement(
+                parts=[
+                    JinjaElementPart(
+                        tag=JinjaTag(name="of", content="a"),
+                        content=Interp(["b"]),
+                    ),
+                    JinjaElementPart(
+                        tag=JinjaTag(name="elseof", content=""),
+                        content=Interp(["c"]),
+                    ),
+                ],
+                closing_tag=JinjaTag(name="endof", content=""),
+            ),
+        )
 
+    def test_jinja_whitespace_controls(self):
+        self.assertEqual(
+            jinja.parse("{%- foo -%}"),
+            JinjaElement(
+                parts=[
+                    JinjaElementPart(
+                        tag=JinjaTag(
+                            name="foo",
+                            content="",
+                            left_minus=True,
+                            right_minus=True,
+                        ),
+                        content=None,
+                    )
+                ],
+                closing_tag=None,
+            ),
+        )
 
-def test_whole_document():
-    src = '<html lang="fr"><body>Hello<br></body></html>'
-    assert src == str(element.parse(src))
+        self.assertEqual(str(jinja.parse("{%- foo -%}")), "{%- foo -%}")
+        self.assertEqual(str(jinja.parse("{%- foo %}")), "{%- foo %}")
+        self.assertEqual(str(jinja.parse("{{- bar -}}")), "{{- bar -}}")
+        self.assertEqual(str(jinja.parse("{{ bar -}}")), "{{ bar -}}")
+        self.assertEqual(str(jinja.parse("{%+ foo %}")), "{%+ foo %}")
+        self.assertEqual(str(jinja.parse("{{+ bar }}")), "{{+ bar }}")
+
+    def test_doctype(self):
+        self.assertEqual(
+            content.parse("<!DOCTYPE html>"), Interp("<!DOCTYPE html>")
+        )
+
+    def test_attrs(self):
+        attrs = make_attributes_parser({}, jinja)
+        parse = attrs.parse
+
+        self.assertEqual(
+            str(parse("{% if %}{% endif %}")), "{% if %}{% endif %}"
+        )
+        self.assertEqual(
+            str(parse("{% if %}  {% endif %}")), "{% if %}{% endif %}"
+        )
+        self.assertEqual(
+            str(parse("{% if %}a=b{% endif %}")), "{% if %}a=b{% endif %}"
+        )
+        self.assertEqual(
+            str(parse("{% if %} a=b {% endif %}")), "{% if %}a=b{% endif %}"
+        )
+
+    def test_optional_container(self):
+        src = '{% if a %}<a href="b">{% endif %}c<b>d</b>{% if a %}</a>{% 
endif %}'
+        self.assertEqual(src, str(content.parse(src)))
+
+        src = """
+        {% if a %} <a href="b"> {% endif %}
+            c <b> d </b>
+        {% if a %} </a> {% endif %}
+        """
+        content.parse(src)
+
+    def test_whole_document(self):
+        src = '<html lang="fr"><body>Hello<br></body></html>'
+        self.assertEqual(src, str(element.parse(src)))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/curlylint/rule_param.py 
new/curlylint-0.13.0/curlylint/rule_param.py
--- old/curlylint-0.12.2/curlylint/rule_param.py        2021-03-06 
16:10:55.000000000 +0100
+++ new/curlylint-0.13.0/curlylint/rule_param.py        2021-03-12 
11:31:47.000000000 +0100
@@ -4,6 +4,8 @@
 
 
 class RuleParamType(click.ParamType):
+    """Validates and converts CLI-provided rule configuration."""
+
     name = "rule"
 
     def convert(self, value, param, ctx):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/curlylint/template_tags_param.py 
new/curlylint-0.13.0/curlylint/template_tags_param.py
--- old/curlylint-0.12.2/curlylint/template_tags_param.py       1970-01-01 
01:00:00.000000000 +0100
+++ new/curlylint-0.13.0/curlylint/template_tags_param.py       2021-04-19 
07:48:11.000000000 +0200
@@ -0,0 +1,35 @@
+import json
+
+import click
+
+
+class TemplateTagsParamType(click.ParamType):
+    """
+    Validates and converts CLI-provided template tags configuration.
+    Expects: --template-tags '[["cache", "endcache"]]'
+    """
+
+    name = "template tags"
+
+    def convert(self, value, param, ctx):
+        try:
+            if isinstance(value, str):
+                template_tags = json.loads(value)
+            else:
+                template_tags = value
+            # Validate the expected list of lists.
+            if not isinstance(template_tags, (list, tuple)):
+                raise ValueError
+            for tags in template_tags:
+                if not isinstance(tags, (list, tuple)):
+                    raise ValueError
+            return template_tags
+        except ValueError:
+            self.fail(
+                f"expected a list of lists of tags as JSON, got {value!r}",
+                param,
+                ctx,
+            )
+
+
+TEMPLATE_TAGS = TemplateTagsParamType()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/curlylint/tests/utils.py 
new/curlylint-0.13.0/curlylint/tests/utils.py
--- old/curlylint-0.12.2/curlylint/tests/utils.py       1970-01-01 
01:00:00.000000000 +0100
+++ new/curlylint-0.13.0/curlylint/tests/utils.py       2021-03-13 
10:46:48.000000000 +0100
@@ -0,0 +1,40 @@
+import sys
+from contextlib import contextmanager
+from io import BytesIO, TextIOWrapper
+from typing import (
+    Any,
+    BinaryIO,
+    Generator,
+)
+
+from click.testing import CliRunner
+
+
+class BlackRunner(CliRunner):
+    """Borrowed from black.
+    
https://github.com/psf/black/blob/5446a92f0161e398de765bf9532d8c76c5652333/tests/test_black.py#L101
+    Modify CliRunner so that stderr is not merged with stdout.
+    This is a hack that can be removed once we depend on Click 7.x"""
+
+    def __init__(self) -> None:
+        self.stderrbuf = BytesIO()
+        self.stdoutbuf = BytesIO()
+        self.stdout_bytes = b""
+        self.stderr_bytes = b""
+        super().__init__()
+
+    @contextmanager
+    def isolation(
+        self, *args: Any, **kwargs: Any
+    ) -> Generator[BinaryIO, None, None]:
+        with super().isolation(*args, **kwargs) as output:
+            try:
+                hold_stderr = sys.stderr
+                sys.stderr = TextIOWrapper(
+                    self.stderrbuf, encoding=self.charset
+                )
+                yield output
+            finally:
+                self.stdout_bytes = sys.stdout.buffer.getvalue()  # type: 
ignore
+                self.stderr_bytes = sys.stderr.buffer.getvalue()  # type: 
ignore
+                sys.stderr = hold_stderr
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/curlylint/util.py 
new/curlylint-0.13.0/curlylint/util.py
--- old/curlylint-0.12.2/curlylint/util.py      2021-03-06 16:10:55.000000000 
+0100
+++ new/curlylint-0.13.0/curlylint/util.py      2021-03-13 10:46:48.000000000 
+0100
@@ -1,4 +1,4 @@
-import collections
+from collections.abc import Iterable
 
 
 def flatten(_list):
@@ -6,9 +6,7 @@
     Deeply flattens an iterable.
     """
     for el in _list:
-        if isinstance(el, collections.Iterable) and not isinstance(
-            el, (str, bytes)
-        ):
+        if isinstance(el, Iterable) and not isinstance(el, (str, bytes)):
             yield from flatten(el)
         else:
             yield el
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/curlylint/util_test.py 
new/curlylint-0.13.0/curlylint/util_test.py
--- old/curlylint-0.12.2/curlylint/util_test.py 2021-03-06 16:10:55.000000000 
+0100
+++ new/curlylint-0.13.0/curlylint/util_test.py 2021-03-13 10:46:48.000000000 
+0100
@@ -1,8 +1,13 @@
+import unittest
+
 from .util import flatten
 
 
-def test():
-    assert list(flatten(())) == []
-    assert list(flatten([])) == []
-    assert list(flatten((1,))) == [1]
-    assert list(flatten([2, [], (), [3, [(4, 5), (6,)]]])) == [2, 3, 4, 5, 6]
+class TestUtil(unittest.TestCase):
+    def test_flatten(self):
+        self.assertEqual(list(flatten(())), [])
+        self.assertEqual(list(flatten([])), [])
+        self.assertEqual(list(flatten((1,))), [1])
+        self.assertEqual(
+            list(flatten([2, [], (), [3, [(4, 5), (6,)]]])), [2, 3, 4, 5, 6]
+        )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/curlylint.egg-info/PKG-INFO 
new/curlylint-0.13.0/curlylint.egg-info/PKG-INFO
--- old/curlylint-0.12.2/curlylint.egg-info/PKG-INFO    2021-03-07 
00:42:59.000000000 +0100
+++ new/curlylint-0.13.0/curlylint.egg-info/PKG-INFO    2021-04-25 
10:12:38.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: curlylint
-Version: 0.12.2
+Version: 0.13.0
 Summary: {{ ????}} Experimental HTML templates linting for Jinja, Nunjucks, 
Django templates, Twig, Liquid
 Home-page: https://github.com/thibaudcolas/curlylint
 Author: Thibaud Colas
@@ -8,7 +8,7 @@
 License: MIT
 Description: # [curlylint](https://www.curlylint.org/) [<img 
src="https://raw.githubusercontent.com/thibaudcolas/curlylint/main/.github/curlylint-logo.svg?sanitize=true";
 width="250" height="100" align="right" alt="">](https://www.curlylint.org/)
         
-        
[![PyPI](https://img.shields.io/pypi/v/curlylint.svg)](https://pypi.org/project/curlylint/)
 [![PyPI 
downloads](https://img.shields.io/pypi/dm/curlylint.svg)](https://pypi.org/project/curlylint/)
 [![Build 
status](https://github.com/thibaudcolas/curlylint/workflows/CI/badge.svg)](https://github.com/thibaudcolas/curlylint/actions)
 [![Total 
alerts](https://img.shields.io/lgtm/alerts/g/thibaudcolas/curlylint.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/thibaudcolas/curlylint/alerts/)
+        
[![PyPI](https://img.shields.io/pypi/v/curlylint.svg)](https://pypi.org/project/curlylint/)
 [![PyPI 
downloads](https://img.shields.io/pypi/dm/curlylint.svg)](https://pypi.org/project/curlylint/)
 [![Build 
status](https://github.com/thibaudcolas/curlylint/workflows/CI/badge.svg)](https://github.com/thibaudcolas/curlylint/actions)
 [![Coverage 
Status](https://coveralls.io/repos/github/thibaudcolas/curlylint/badge.svg?branch=main)](https://coveralls.io/github/thibaudcolas/curlylint?branch=main)
 [![Total 
alerts](https://img.shields.io/lgtm/alerts/g/thibaudcolas/curlylint.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/thibaudcolas/curlylint/alerts/)
         
         > **{{ ????}}** Experimental HTML templates linting for 
[Jinja](https://jinja.palletsprojects.com/), 
[Nunjucks](https://mozilla.github.io/nunjucks/), [Django 
templates](https://docs.djangoproject.com/en/dev/topics/templates/), 
[Twig](https://twig.symfony.com/), [Liquid](https://shopify.github.io/liquid/).
         > Forked from [jinjalint](https://github.com/motet-a/jinjalint).
@@ -22,7 +22,7 @@
         On the roadmap:
         
         - More checks for common accessibility issues in HTML.
-        - Checks for common security issues ?????for exameple `rel="noopener 
noreferrer"`, or known sources of XSS vulnerabilities.
+        - Checks for common security issues ?????for example `rel="noopener 
noreferrer"`, or known sources of XSS vulnerabilities.
         - More [ideas welcome](https://www.curlylint.org/docs/reference/ideas)!
         
         ## Usage
@@ -69,6 +69,7 @@
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
 Requires-Python: >=3.6
 Description-Content-Type: text/markdown
-Provides-Extra: dev
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/curlylint.egg-info/SOURCES.txt 
new/curlylint-0.13.0/curlylint.egg-info/SOURCES.txt
--- old/curlylint-0.12.2/curlylint.egg-info/SOURCES.txt 2021-03-07 
00:42:59.000000000 +0100
+++ new/curlylint-0.13.0/curlylint.egg-info/SOURCES.txt 2021-04-25 
10:12:38.000000000 +0200
@@ -10,6 +10,7 @@
 curlylint/check.py
 curlylint/check_node.py
 curlylint/cli.py
+curlylint/cli_test.py
 curlylint/config.py
 curlylint/file.py
 curlylint/issue.py
@@ -20,6 +21,7 @@
 curlylint/py.typed
 curlylint/report.py
 curlylint/rule_param.py
+curlylint/template_tags_param.py
 curlylint/util.py
 curlylint/util_test.py
 curlylint.egg-info/PKG-INFO
@@ -65,5 +67,7 @@
 curlylint/rules/tabindex_no_positive/tabindex_no_positive.py
 curlylint/rules/tabindex_no_positive/tabindex_no_positive_test.json
 curlylint/rules/tabindex_no_positive/tabindex_no_positive_test.py
+curlylint/tests/__init__.py
+curlylint/tests/utils.py
 stubs/parsy.pyi
 stubs/pathspec.pyi
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/curlylint.egg-info/requires.txt 
new/curlylint-0.13.0/curlylint.egg-info/requires.txt
--- old/curlylint-0.12.2/curlylint.egg-info/requires.txt        2021-03-07 
00:42:59.000000000 +0100
+++ new/curlylint-0.13.0/curlylint.egg-info/requires.txt        2021-04-25 
10:12:38.000000000 +0200
@@ -6,10 +6,3 @@
 
 [:python_version < "3.7"]
 dataclasses>=0.6
-
-[dev]
-black==19.10b0
-flake8==3.8.4
-mypy==0.812
-pytest==6.2.2
-coverage==5.4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/pyproject.toml 
new/curlylint-0.13.0/pyproject.toml
--- old/curlylint-0.12.2/pyproject.toml 2021-03-06 16:10:55.000000000 +0100
+++ new/curlylint-0.13.0/pyproject.toml 2021-04-18 11:56:42.000000000 +0200
@@ -25,3 +25,7 @@
 
 [tool.pytest.ini_options]
 testpaths = ["curlylint"]
+# https://docs.pytest.org/en/stable/warnings.html
+filterwarnings = [
+    "error",
+]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curlylint-0.12.2/setup.py 
new/curlylint-0.13.0/setup.py
--- old/curlylint-0.12.2/setup.py       2021-03-07 00:30:40.000000000 +0100
+++ new/curlylint-0.13.0/setup.py       2021-04-17 16:03:46.000000000 +0200
@@ -54,15 +54,6 @@
         "pathspec>=0.6, <1",
         "dataclasses>=0.6; python_version < '3.7'",
     ],
-    extras_require={
-        "dev": [
-            "black==19.10b0",
-            "flake8==3.8.4",
-            "mypy==0.812",
-            "pytest==6.2.2",
-            "coverage==5.4",
-        ]
-    },
     classifiers=[
         "Development Status :: 3 - Alpha",
         "Environment :: Console",
@@ -75,6 +66,8 @@
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
     ],
     entry_points={"console_scripts": ["curlylint=curlylint.cli:patched_main"]},
 )

Reply via email to