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/) - [](https://pypi.org/project/curlylint/) [](https://pypi.org/project/curlylint/) [](https://github.com/thibaudcolas/curlylint/actions) [](https://lgtm.com/projects/g/thibaudcolas/curlylint/alerts/) + [](https://pypi.org/project/curlylint/) [](https://pypi.org/project/curlylint/) [](https://github.com/thibaudcolas/curlylint/actions) [](https://coveralls.io/github/thibaudcolas/curlylint?branch=main) [](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/) -[](https://pypi.org/project/curlylint/) [](https://pypi.org/project/curlylint/) [](https://github.com/thibaudcolas/curlylint/actions) [](https://lgtm.com/projects/g/thibaudcolas/curlylint/alerts/) +[](https://pypi.org/project/curlylint/) [](https://pypi.org/project/curlylint/) [](https://github.com/thibaudcolas/curlylint/actions) [](https://coveralls.io/github/thibaudcolas/curlylint?branch=main) [](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/) - [](https://pypi.org/project/curlylint/) [](https://pypi.org/project/curlylint/) [](https://github.com/thibaudcolas/curlylint/actions) [](https://lgtm.com/projects/g/thibaudcolas/curlylint/alerts/) + [](https://pypi.org/project/curlylint/) [](https://pypi.org/project/curlylint/) [](https://github.com/thibaudcolas/curlylint/actions) [](https://coveralls.io/github/thibaudcolas/curlylint?branch=main) [](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"]}, )