├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docker └── Dockerfile.antlr ├── poetry.lock ├── pyproject.toml ├── setup.cfg ├── tests ├── __init__.py ├── data │ ├── 01_general.input.html │ ├── 01_general.output.html │ ├── 02_base_template.input.html │ ├── 02_base_template.output.html │ ├── 03_jinja_comments.input.html │ └── 03_jinja_comments.output.html ├── test_cli.py └── test_formatter.py ├── tox.ini └── vsot ├── __init__.py ├── antlr ├── HTMLLexer.g4 ├── HTMLLexer.interp ├── HTMLLexer.py ├── HTMLLexer.tokens ├── HTMLParser.g4 ├── HTMLParser.interp ├── HTMLParser.py ├── HTMLParser.tokens ├── HTMLParserVisitor.py └── __init__.py ├── assertions.py ├── cli.py ├── constants.py ├── exceptions.py ├── parser.py ├── printer.py ├── settings.py └── utils.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * HTML formatter version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # IDE settings 105 | .vscode/ 106 | *.sublime-* 107 | .idea 108 | 109 | # Antlr 110 | .antlr 111 | gen 112 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com/ for usage and config 2 | repos: 3 | - repo: local 4 | hooks: 5 | - id: isort 6 | name: isort 7 | stages: [commit] 8 | language: system 9 | entry: poetry run isort vsot 10 | types: [python] 11 | pass_filenames: false # In order to respect the ignore list 12 | 13 | 14 | - id: black 15 | name: black 16 | stages: [commit] 17 | language: system 18 | entry: poetry run black vsot 19 | types: [python] 20 | pass_filenames: false # In order to respect the ignore list 21 | 22 | 23 | - id: flake8 24 | name: flake8 25 | stages: [commit] 26 | language: system 27 | entry: poetry run flake8 vsot 28 | types: [python] 29 | pass_filenames: false # In order to respect the ignore list 30 | 31 | - id: readme 32 | name: readme checker 33 | stages: [commit] 34 | language: system 35 | entry: make release-check 36 | files: README.rst 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.com 2 | 3 | language: python 4 | python: 5 | - 3.8 6 | - 3.7 7 | - 3.6 8 | 9 | # Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 10 | install: pip install -U tox-travis poetry 11 | 12 | # Command to run tests, e.g. python setup.py test 13 | script: tox 14 | 15 | deploy: 16 | - provider: script 17 | script: poetry publish --username benhowes --password $PYPI_PASS --build 18 | on: 19 | repo: benhowes/vsot 20 | branch: master 21 | python: '3.7' 22 | tags: true 23 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Ben Howes 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/benhowes/html_formatter/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | HTML formatter could always use more documentation, whether as part of the 42 | official HTML formatter docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/benhowes/html_formatter/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `html_formatter` for local development. 61 | 62 | 1. Fork the `html_formatter` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/html_formatter.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv html_formatter 70 | $ cd html_formatter/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the 80 | tests, including testing other Python versions with tox:: 81 | 82 | $ flake8 html_formatter tests 83 | $ python setup.py test or pytest 84 | $ tox 85 | 86 | To get flake8 and tox, just pip install them into your virtualenv. 87 | 88 | 6. Commit your changes and push your branch to GitHub:: 89 | 90 | $ git add . 91 | $ git commit -m "Your detailed description of your changes." 92 | $ git push origin name-of-your-bugfix-or-feature 93 | 94 | 7. Submit a pull request through the GitHub website. 95 | 96 | Pull Request Guidelines 97 | ----------------------- 98 | 99 | Before you submit a pull request, check that it meets these guidelines: 100 | 101 | 1. The pull request should include tests. 102 | 2. If the pull request adds functionality, the docs should be updated. Put 103 | your new functionality into a function with a docstring, and add the 104 | feature to the list in README.rst. 105 | 3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8, and for PyPy. Check 106 | https://travis-ci.com/benhowes/html_formatter/pull_requests 107 | and make sure that the tests pass for all supported Python versions. 108 | 109 | Tips 110 | ---- 111 | 112 | To run a subset of tests:: 113 | 114 | $ pytest tests.test_html_formatter 115 | 116 | 117 | Deploying 118 | --------- 119 | 120 | A reminder for the maintainers on how to deploy. 121 | Make sure all your changes are committed (including an entry in HISTORY.rst). 122 | Then run:: 123 | 124 | $ bump2version patch # possible: major / minor / patch 125 | $ git push 126 | $ git push --tags 127 | 128 | Travis will then deploy to PyPI if tests pass. 129 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.1.0 (2020-04-14) 6 | ------------------ 7 | 8 | * First release on PyPI. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020, Ben Howes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | 7 | recursive-include tests * 8 | recursive-exclude * __pycache__ 9 | recursive-exclude * *.py[co] 10 | 11 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | from urllib.request import pathname2url 8 | 9 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 10 | endef 11 | export BROWSER_PYSCRIPT 12 | 13 | define PRINT_HELP_PYSCRIPT 14 | import re, sys 15 | 16 | for line in sys.stdin: 17 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 18 | if match: 19 | target, help = match.groups() 20 | print("%-20s %s" % (target, help)) 21 | endef 22 | export PRINT_HELP_PYSCRIPT 23 | 24 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 25 | 26 | help: 27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 28 | 29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 30 | 31 | clean-build: ## remove build artifacts 32 | rm -fr build/ 33 | rm -fr dist/ 34 | rm -fr .eggs/ 35 | find . -name '*.egg-info' -exec rm -fr {} + 36 | find . -name '*.egg' -exec rm -f {} + 37 | 38 | clean-pyc: ## remove Python file artifacts 39 | find . -name '*.pyc' -exec rm -f {} + 40 | find . -name '*.pyo' -exec rm -f {} + 41 | find . -name '*~' -exec rm -f {} + 42 | find . -name '__pycache__' -exec rm -fr {} + 43 | 44 | clean-test: ## remove test and coverage artifacts 45 | rm -fr .tox/ 46 | rm -f .coverage 47 | rm -fr htmlcov/ 48 | rm -fr .pytest_cache 49 | 50 | lint: ## check style with flake8 51 | poetry run flake8 vsot tests 52 | 53 | test: ## run tests quickly with the default Python 54 | poetry run pytest 55 | 56 | test-all: ## run tests on every Python version with tox 57 | tox 58 | 59 | coverage: ## check code coverage quickly with the default Python 60 | poetry run coverage run --source vsot -m pytest 61 | poetry run coverage report -m 62 | poetry run coverage html 63 | $(BROWSER) htmlcov/index.html 64 | 65 | build: 66 | poetry build 67 | 68 | release-check: 69 | poetry run python -m readme_renderer README.rst >/dev/null 70 | 71 | release: release-check build ## package and upload a release 72 | poetry publish 73 | 74 | install: clean ## install the package to the active Python's site-packages 75 | python setup.py install 76 | 77 | 78 | grammar: 79 | docker build -f docker/Dockerfile.antlr -t vsot-antlr docker ## Normally will use cache 80 | docker run -v $(shell pwd)/vsot:/vsot vsot-antlr:latest -Dlanguage=Python3 -no-listener -visitor /vsot/antlr/HTMLParser.g4 /vsot/antlr/HTMLLexer.g4 81 | rm -r gen 82 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =================================================== 2 | (unmaintained) VSOT - Like black_, but for Django/Jinja2 HTML templates 3 | =================================================== 4 | 5 | 6 | .. image:: https://img.shields.io/pypi/v/vsot.svg 7 | :target: https://pypi.python.org/pypi/vsot 8 | 9 | .. image:: https://img.shields.io/travis/benhowes/vsot.svg 10 | :target: https://travis-ci.com/benhowes/vsot 11 | 12 | .. image:: https://img.shields.io/github/license/benhowes/vsot 13 | :alt: License - MIT 14 | 15 | Note: I recommend looking at the following formatters/indenters instead: 16 | 17 | - Full formatting (as VSOT attempts): https://github.com/Riverside-Healthcare/djLint 18 | - Just indentation: https://github.com/rtts/djhtml 19 | 20 | HTML Template formatter 21 | 22 | Use VSOT to automatically format your django/Jinja2 HTML templates. No need to manually reflow text or tags when you add/remove content. 23 | 24 | It has been tested on small files, however there is a lot more to be done an a lot of edge cases which are not handled all that nicely at the moment. Use at your own risk, and only if you have the means to verify the output is correct! 25 | 26 | Free software: MIT license 27 | 28 | 29 | Features 30 | -------- 31 | 32 | * Mimics black in terms of options and configuration. 33 | * Automatically supports all built in django/jinja2 tags 34 | * Safe - VSOT ensures that it does not change the meaning of the templates 35 | 36 | 37 | Installation 38 | ------------ 39 | 40 | .. code-block:: console 41 | 42 | pip install vsot 43 | 44 | Dev Setup 45 | --------- 46 | 47 | Requirements: 48 | - Python 3.6 or later 49 | - Docker 50 | - Python poetry (see `poetry docs`_) 51 | 52 | 1. Clone repo 53 | 54 | 2. Installation 55 | 56 | .. code-block:: console 57 | 58 | poetry install 59 | 60 | 61 | Credits 62 | ------- 63 | 64 | - A lot of the code for this was repurposed from black_ 65 | - Antlr4_ is used for the parser, along with using the `HTML parser from the antlr library`_ as a starting point 66 | - This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. 67 | 68 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 69 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 70 | .. _black: https://github.com/psf/black 71 | .. _`poetry docs`: https://python-poetry.org/docs/#installation 72 | .. _Antlr4: https://github.com/antlr/antlr4 73 | .. _`HTML parser from the antlr library`: https://github.com/antlr/grammars-v4 74 | -------------------------------------------------------------------------------- /docker/Dockerfile.antlr: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jdk-alpine 2 | 3 | ADD https://www.antlr.org/download/antlr-4.8-complete.jar /usr/local/lib/antlr-4.8-complete.jar 4 | 5 | ENV CLASSPATH=".:/usr/local/lib/antlr-4.8-complete.jar:$CLASSPATH" 6 | 7 | RUN chmod +r /usr/local/lib/antlr-4.8-complete.jar && \ 8 | echo -e '#!/bin/sh\njava -Xmx500M -cp /usr/local/lib/antlr-4.8-complete.jar:$CLASSPATH org.antlr.v4.Tool "$@"' > /usr/bin/antlr4 && \ 9 | chmod +x /usr/bin/antlr4 && \ 10 | echo -e '#!/bin/sh\njava -Xmx500M -cp /usr/local/lib/antlr-4.8-complete.jar:$CLASSPATH org.antlr.v4.gui.TestRig "$@"' > /usr/bin/grun && \ 11 | chmod +x /usr/bin/grun 12 | 13 | ENTRYPOINT ["/usr/bin/antlr4"] 14 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | category = "main" 3 | description = "ANTLR 4.8 runtime for Python 3.7" 4 | name = "antlr4-python3-runtime" 5 | optional = false 6 | python-versions = "*" 7 | version = "4.8" 8 | 9 | [[package]] 10 | category = "dev" 11 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 12 | name = "appdirs" 13 | optional = false 14 | python-versions = "*" 15 | version = "1.4.3" 16 | 17 | [[package]] 18 | category = "dev" 19 | description = "Disable App Nap on OS X 10.9" 20 | marker = "python_version >= \"3.4\" and sys_platform == \"darwin\"" 21 | name = "appnope" 22 | optional = false 23 | python-versions = "*" 24 | version = "0.1.0" 25 | 26 | [[package]] 27 | category = "dev" 28 | description = "Atomic file writes." 29 | marker = "sys_platform == \"win32\"" 30 | name = "atomicwrites" 31 | optional = false 32 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 33 | version = "1.3.0" 34 | 35 | [[package]] 36 | category = "dev" 37 | description = "Classes Without Boilerplate" 38 | name = "attrs" 39 | optional = false 40 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 41 | version = "19.3.0" 42 | 43 | [package.extras] 44 | azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] 45 | dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] 46 | docs = ["sphinx", "zope.interface"] 47 | tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 48 | 49 | [[package]] 50 | category = "dev" 51 | description = "Specifications for callback functions passed in to an API" 52 | marker = "python_version >= \"3.4\"" 53 | name = "backcall" 54 | optional = false 55 | python-versions = "*" 56 | version = "0.1.0" 57 | 58 | [[package]] 59 | category = "dev" 60 | description = "The uncompromising code formatter." 61 | name = "black" 62 | optional = false 63 | python-versions = ">=3.6" 64 | version = "19.10b0" 65 | 66 | [package.dependencies] 67 | appdirs = "*" 68 | attrs = ">=18.1.0" 69 | click = ">=6.5" 70 | pathspec = ">=0.6,<1" 71 | regex = "*" 72 | toml = ">=0.9.4" 73 | typed-ast = ">=1.4.0" 74 | 75 | [package.extras] 76 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 77 | 78 | [[package]] 79 | category = "dev" 80 | description = "An easy safelist-based HTML-sanitizing tool." 81 | name = "bleach" 82 | optional = false 83 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 84 | version = "3.1.4" 85 | 86 | [package.dependencies] 87 | six = ">=1.9.0" 88 | webencodings = "*" 89 | 90 | [[package]] 91 | category = "dev" 92 | description = "Version-bump your software with a single command!" 93 | name = "bump2version" 94 | optional = false 95 | python-versions = ">=3.5" 96 | version = "1.0.0" 97 | 98 | [[package]] 99 | category = "dev" 100 | description = "Validate configuration and produce human readable error messages." 101 | name = "cfgv" 102 | optional = false 103 | python-versions = ">=3.6.1" 104 | version = "3.1.0" 105 | 106 | [[package]] 107 | category = "main" 108 | description = "Composable command line interface toolkit" 109 | name = "click" 110 | optional = false 111 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 112 | version = "7.1.2" 113 | 114 | [[package]] 115 | category = "dev" 116 | description = "Cross-platform colored terminal text." 117 | marker = "python_version >= \"3.4\" and sys_platform == \"win32\" or sys_platform == \"win32\" or platform_system == \"Windows\"" 118 | name = "colorama" 119 | optional = false 120 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 121 | version = "0.4.3" 122 | 123 | [[package]] 124 | category = "dev" 125 | description = "Code coverage measurement for Python" 126 | name = "coverage" 127 | optional = false 128 | python-versions = "*" 129 | version = "4.4.2" 130 | 131 | [[package]] 132 | category = "main" 133 | description = "A backport of the dataclasses module for Python 3.6" 134 | marker = "python_version < \"3.7\"" 135 | name = "dataclasses" 136 | optional = false 137 | python-versions = "*" 138 | version = "0.6" 139 | 140 | [[package]] 141 | category = "dev" 142 | description = "Decorators for Humans" 143 | marker = "python_version >= \"3.4\"" 144 | name = "decorator" 145 | optional = false 146 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 147 | version = "4.4.2" 148 | 149 | [[package]] 150 | category = "dev" 151 | description = "Distribution utilities" 152 | name = "distlib" 153 | optional = false 154 | python-versions = "*" 155 | version = "0.3.0" 156 | 157 | [[package]] 158 | category = "dev" 159 | description = "Docutils -- Python Documentation Utilities" 160 | name = "docutils" 161 | optional = false 162 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 163 | version = "0.16" 164 | 165 | [[package]] 166 | category = "dev" 167 | description = "Discover and load entry points from installed packages." 168 | name = "entrypoints" 169 | optional = false 170 | python-versions = ">=2.7" 171 | version = "0.3" 172 | 173 | [[package]] 174 | category = "dev" 175 | description = "A platform independent file lock." 176 | name = "filelock" 177 | optional = false 178 | python-versions = "*" 179 | version = "3.0.12" 180 | 181 | [[package]] 182 | category = "dev" 183 | description = "the modular source code checker: pep8, pyflakes and co" 184 | name = "flake8" 185 | optional = false 186 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 187 | version = "3.7.9" 188 | 189 | [package.dependencies] 190 | entrypoints = ">=0.3.0,<0.4.0" 191 | mccabe = ">=0.6.0,<0.7.0" 192 | pycodestyle = ">=2.5.0,<2.6.0" 193 | pyflakes = ">=2.1.0,<2.2.0" 194 | 195 | [[package]] 196 | category = "dev" 197 | description = "File identification library for Python" 198 | name = "identify" 199 | optional = false 200 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 201 | version = "1.4.14" 202 | 203 | [package.extras] 204 | license = ["editdistance"] 205 | 206 | [[package]] 207 | category = "dev" 208 | description = "Read metadata from Python packages" 209 | marker = "python_version < \"3.8\"" 210 | name = "importlib-metadata" 211 | optional = false 212 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 213 | version = "1.6.0" 214 | 215 | [package.dependencies] 216 | zipp = ">=0.5" 217 | 218 | [package.extras] 219 | docs = ["sphinx", "rst.linker"] 220 | testing = ["packaging", "importlib-resources"] 221 | 222 | [[package]] 223 | category = "dev" 224 | description = "Read resources from Python packages" 225 | marker = "python_version < \"3.7\"" 226 | name = "importlib-resources" 227 | optional = false 228 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 229 | version = "1.4.0" 230 | 231 | [package.dependencies] 232 | [package.dependencies.importlib-metadata] 233 | python = "<3.8" 234 | version = "*" 235 | 236 | [package.dependencies.zipp] 237 | python = "<3.8" 238 | version = ">=0.4" 239 | 240 | [package.extras] 241 | docs = ["sphinx", "rst.linker", "jaraco.packaging"] 242 | 243 | [[package]] 244 | category = "dev" 245 | description = "IPython-enabled pdb" 246 | name = "ipdb" 247 | optional = false 248 | python-versions = ">=2.7" 249 | version = "0.13.2" 250 | 251 | [package.dependencies] 252 | setuptools = "*" 253 | 254 | [package.dependencies.ipython] 255 | python = ">=3.4" 256 | version = ">=5.1.0" 257 | 258 | [[package]] 259 | category = "dev" 260 | description = "IPython: Productive Interactive Computing" 261 | marker = "python_version >= \"3.4\"" 262 | name = "ipython" 263 | optional = false 264 | python-versions = ">=3.6" 265 | version = "7.13.0" 266 | 267 | [package.dependencies] 268 | appnope = "*" 269 | backcall = "*" 270 | colorama = "*" 271 | decorator = "*" 272 | jedi = ">=0.10" 273 | pexpect = "*" 274 | pickleshare = "*" 275 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" 276 | pygments = "*" 277 | setuptools = ">=18.5" 278 | traitlets = ">=4.2" 279 | 280 | [package.extras] 281 | all = ["numpy (>=1.14)", "testpath", "notebook", "nose (>=0.10.1)", "nbconvert", "requests", "ipywidgets", "qtconsole", "ipyparallel", "Sphinx (>=1.3)", "pygments", "nbformat", "ipykernel"] 282 | doc = ["Sphinx (>=1.3)"] 283 | kernel = ["ipykernel"] 284 | nbconvert = ["nbconvert"] 285 | nbformat = ["nbformat"] 286 | notebook = ["notebook", "ipywidgets"] 287 | parallel = ["ipyparallel"] 288 | qtconsole = ["qtconsole"] 289 | test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] 290 | 291 | [[package]] 292 | category = "dev" 293 | description = "Vestigial utilities from IPython" 294 | marker = "python_version >= \"3.4\"" 295 | name = "ipython-genutils" 296 | optional = false 297 | python-versions = "*" 298 | version = "0.2.0" 299 | 300 | [[package]] 301 | category = "dev" 302 | description = "A Python utility / library to sort Python imports." 303 | name = "isort" 304 | optional = false 305 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 306 | version = "4.3.21" 307 | 308 | [package.extras] 309 | pipfile = ["pipreqs", "requirementslib"] 310 | pyproject = ["toml"] 311 | requirements = ["pipreqs", "pip-api"] 312 | xdg_home = ["appdirs (>=1.4.0)"] 313 | 314 | [[package]] 315 | category = "dev" 316 | description = "An autocompletion tool for Python that can be used for text editors." 317 | marker = "python_version >= \"3.4\"" 318 | name = "jedi" 319 | optional = false 320 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 321 | version = "0.17.0" 322 | 323 | [package.dependencies] 324 | parso = ">=0.7.0" 325 | 326 | [package.extras] 327 | qa = ["flake8 (3.7.9)"] 328 | testing = ["colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"] 329 | 330 | [[package]] 331 | category = "dev" 332 | description = "McCabe checker, plugin for flake8" 333 | name = "mccabe" 334 | optional = false 335 | python-versions = "*" 336 | version = "0.6.1" 337 | 338 | [[package]] 339 | category = "dev" 340 | description = "More routines for operating on iterables, beyond itertools" 341 | name = "more-itertools" 342 | optional = false 343 | python-versions = ">=3.5" 344 | version = "8.2.0" 345 | 346 | [[package]] 347 | category = "dev" 348 | description = "Node.js virtual environment builder" 349 | name = "nodeenv" 350 | optional = false 351 | python-versions = "*" 352 | version = "1.3.5" 353 | 354 | [[package]] 355 | category = "dev" 356 | description = "Core utilities for Python packages" 357 | name = "packaging" 358 | optional = false 359 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 360 | version = "20.3" 361 | 362 | [package.dependencies] 363 | pyparsing = ">=2.0.2" 364 | six = "*" 365 | 366 | [[package]] 367 | category = "dev" 368 | description = "A Python Parser" 369 | marker = "python_version >= \"3.4\"" 370 | name = "parso" 371 | optional = false 372 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 373 | version = "0.7.0" 374 | 375 | [package.extras] 376 | testing = ["docopt", "pytest (>=3.0.7)"] 377 | 378 | [[package]] 379 | category = "main" 380 | description = "Utility library for gitignore style pattern matching of file paths." 381 | name = "pathspec" 382 | optional = false 383 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 384 | version = "0.8.0" 385 | 386 | [[package]] 387 | category = "dev" 388 | description = "File system general utilities" 389 | name = "pathtools" 390 | optional = false 391 | python-versions = "*" 392 | version = "0.1.2" 393 | 394 | [[package]] 395 | category = "dev" 396 | description = "Pexpect allows easy control of interactive console applications." 397 | marker = "python_version >= \"3.4\" and sys_platform != \"win32\"" 398 | name = "pexpect" 399 | optional = false 400 | python-versions = "*" 401 | version = "4.8.0" 402 | 403 | [package.dependencies] 404 | ptyprocess = ">=0.5" 405 | 406 | [[package]] 407 | category = "dev" 408 | description = "Tiny 'shelve'-like database with concurrency support" 409 | marker = "python_version >= \"3.4\"" 410 | name = "pickleshare" 411 | optional = false 412 | python-versions = "*" 413 | version = "0.7.5" 414 | 415 | [[package]] 416 | category = "dev" 417 | description = "plugin and hook calling mechanisms for python" 418 | name = "pluggy" 419 | optional = false 420 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 421 | version = "0.13.1" 422 | 423 | [package.dependencies] 424 | [package.dependencies.importlib-metadata] 425 | python = "<3.8" 426 | version = ">=0.12" 427 | 428 | [package.extras] 429 | dev = ["pre-commit", "tox"] 430 | 431 | [[package]] 432 | category = "dev" 433 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 434 | name = "pre-commit" 435 | optional = false 436 | python-versions = ">=3.6.1" 437 | version = "2.3.0" 438 | 439 | [package.dependencies] 440 | cfgv = ">=2.0.0" 441 | identify = ">=1.0.0" 442 | nodeenv = ">=0.11.1" 443 | pyyaml = ">=5.1" 444 | toml = "*" 445 | virtualenv = ">=15.2" 446 | 447 | [package.dependencies.importlib-metadata] 448 | python = "<3.8" 449 | version = "*" 450 | 451 | [package.dependencies.importlib-resources] 452 | python = "<3.7" 453 | version = "*" 454 | 455 | [[package]] 456 | category = "dev" 457 | description = "Library for building powerful interactive command lines in Python" 458 | marker = "python_version >= \"3.4\"" 459 | name = "prompt-toolkit" 460 | optional = false 461 | python-versions = ">=3.6.1" 462 | version = "3.0.5" 463 | 464 | [package.dependencies] 465 | wcwidth = "*" 466 | 467 | [[package]] 468 | category = "dev" 469 | description = "Run a subprocess in a pseudo terminal" 470 | marker = "python_version >= \"3.4\" and sys_platform != \"win32\"" 471 | name = "ptyprocess" 472 | optional = false 473 | python-versions = "*" 474 | version = "0.6.0" 475 | 476 | [[package]] 477 | category = "dev" 478 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 479 | name = "py" 480 | optional = false 481 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 482 | version = "1.8.1" 483 | 484 | [[package]] 485 | category = "dev" 486 | description = "Python style guide checker" 487 | name = "pycodestyle" 488 | optional = false 489 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 490 | version = "2.5.0" 491 | 492 | [[package]] 493 | category = "dev" 494 | description = "passive checker of Python programs" 495 | name = "pyflakes" 496 | optional = false 497 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 498 | version = "2.1.1" 499 | 500 | [[package]] 501 | category = "dev" 502 | description = "Pygments is a syntax highlighting package written in Python." 503 | name = "pygments" 504 | optional = false 505 | python-versions = ">=3.5" 506 | version = "2.6.1" 507 | 508 | [[package]] 509 | category = "dev" 510 | description = "Python parsing module" 511 | name = "pyparsing" 512 | optional = false 513 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 514 | version = "2.4.7" 515 | 516 | [[package]] 517 | category = "dev" 518 | description = "pytest: simple powerful testing with Python" 519 | name = "pytest" 520 | optional = false 521 | python-versions = ">=3.5" 522 | version = "5.4.2" 523 | 524 | [package.dependencies] 525 | atomicwrites = ">=1.0" 526 | attrs = ">=17.4.0" 527 | colorama = "*" 528 | more-itertools = ">=4.0.0" 529 | packaging = "*" 530 | pluggy = ">=0.12,<1.0" 531 | py = ">=1.5.0" 532 | wcwidth = "*" 533 | 534 | [package.dependencies.importlib-metadata] 535 | python = "<3.8" 536 | version = ">=0.12" 537 | 538 | [package.extras] 539 | checkqa-mypy = ["mypy (v0.761)"] 540 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 541 | 542 | [[package]] 543 | category = "dev" 544 | description = "YAML parser and emitter for Python" 545 | name = "pyyaml" 546 | optional = false 547 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 548 | version = "5.3.1" 549 | 550 | [[package]] 551 | category = "dev" 552 | description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse" 553 | name = "readme-renderer" 554 | optional = false 555 | python-versions = "*" 556 | version = "26.0" 557 | 558 | [package.dependencies] 559 | Pygments = ">=2.5.1" 560 | bleach = ">=2.1.0" 561 | docutils = ">=0.13.1" 562 | six = "*" 563 | 564 | [package.extras] 565 | md = ["cmarkgfm (>=0.2.0)"] 566 | 567 | [[package]] 568 | category = "dev" 569 | description = "Alternative regular expression module, to replace re." 570 | name = "regex" 571 | optional = false 572 | python-versions = "*" 573 | version = "2020.4.4" 574 | 575 | [[package]] 576 | category = "dev" 577 | description = "Python 2 and 3 compatibility utilities" 578 | name = "six" 579 | optional = false 580 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 581 | version = "1.14.0" 582 | 583 | [[package]] 584 | category = "dev" 585 | description = "Python Library for Tom's Obvious, Minimal Language" 586 | name = "toml" 587 | optional = false 588 | python-versions = "*" 589 | version = "0.10.0" 590 | 591 | [[package]] 592 | category = "dev" 593 | description = "tox is a generic virtualenv management and test command line tool" 594 | name = "tox" 595 | optional = false 596 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 597 | version = "3.14.6" 598 | 599 | [package.dependencies] 600 | colorama = ">=0.4.1" 601 | filelock = ">=3.0.0,<4" 602 | packaging = ">=14" 603 | pluggy = ">=0.12.0,<1" 604 | py = ">=1.4.17,<2" 605 | six = ">=1.14.0,<2" 606 | toml = ">=0.9.4" 607 | virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" 608 | 609 | [package.dependencies.importlib-metadata] 610 | python = "<3.8" 611 | version = ">=0.12,<2" 612 | 613 | [package.extras] 614 | docs = ["sphinx (>=2.0.0,<3)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] 615 | testing = ["freezegun (>=0.3.11,<1)", "pathlib2 (>=2.3.3,<3)", "pytest (>=4.0.0,<6)", "pytest-cov (>=2.5.1,<3)", "pytest-mock (>=1.10.0,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-randomly (>=1.0.0,<4)", "flaky (>=3.4.0,<4)", "psutil (>=5.6.1,<6)"] 616 | 617 | [[package]] 618 | category = "dev" 619 | description = "Traitlets Python config system" 620 | marker = "python_version >= \"3.4\"" 621 | name = "traitlets" 622 | optional = false 623 | python-versions = "*" 624 | version = "4.3.3" 625 | 626 | [package.dependencies] 627 | decorator = "*" 628 | ipython-genutils = "*" 629 | six = "*" 630 | 631 | [package.extras] 632 | test = ["pytest", "mock"] 633 | 634 | [[package]] 635 | category = "dev" 636 | description = "a fork of Python 2 and 3 ast modules with type comment support" 637 | name = "typed-ast" 638 | optional = false 639 | python-versions = "*" 640 | version = "1.4.1" 641 | 642 | [[package]] 643 | category = "dev" 644 | description = "Virtual Python Environment builder" 645 | name = "virtualenv" 646 | optional = false 647 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 648 | version = "20.0.18" 649 | 650 | [package.dependencies] 651 | appdirs = ">=1.4.3,<2" 652 | distlib = ">=0.3.0,<1" 653 | filelock = ">=3.0.0,<4" 654 | six = ">=1.9.0,<2" 655 | 656 | [package.dependencies.importlib-metadata] 657 | python = "<3.8" 658 | version = ">=0.12,<2" 659 | 660 | [package.dependencies.importlib-resources] 661 | python = "<3.7" 662 | version = ">=1.0,<2" 663 | 664 | [package.extras] 665 | docs = ["sphinx (>=2.0.0,<3)", "sphinx-argparse (>=0.2.5,<1)", "sphinx-rtd-theme (>=0.4.3,<1)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2,<1)"] 666 | testing = ["pytest (>=4.0.0,<6)", "coverage (>=4.5.1,<6)", "pytest-mock (>=2.0.0,<3)", "pytest-env (>=0.6.2,<1)", "pytest-timeout (>=1.3.4,<2)", "packaging (>=20.0)", "xonsh (>=0.9.16,<1)"] 667 | 668 | [[package]] 669 | category = "dev" 670 | description = "Filesystem events monitoring" 671 | name = "watchdog" 672 | optional = false 673 | python-versions = "*" 674 | version = "0.10.2" 675 | 676 | [package.dependencies] 677 | pathtools = ">=0.1.1" 678 | 679 | [package.extras] 680 | watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"] 681 | 682 | [[package]] 683 | category = "dev" 684 | description = "Measures number of Terminal column cells of wide-character codes" 685 | name = "wcwidth" 686 | optional = false 687 | python-versions = "*" 688 | version = "0.1.9" 689 | 690 | [[package]] 691 | category = "dev" 692 | description = "Character encoding aliases for legacy web content" 693 | name = "webencodings" 694 | optional = false 695 | python-versions = "*" 696 | version = "0.5.1" 697 | 698 | [[package]] 699 | category = "dev" 700 | description = "Backport of pathlib-compatible object wrapper for zip files" 701 | marker = "python_version < \"3.8\"" 702 | name = "zipp" 703 | optional = false 704 | python-versions = ">=3.6" 705 | version = "3.1.0" 706 | 707 | [package.extras] 708 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 709 | testing = ["jaraco.itertools", "func-timeout"] 710 | 711 | [metadata] 712 | content-hash = "7adda273d356ea1de8d2b981fca710e4a92ac5c3788a4ceaab52408bd2d5beb5" 713 | python-versions = ">=3.6.1" 714 | 715 | [metadata.files] 716 | antlr4-python3-runtime = [ 717 | {file = "antlr4-python3-runtime-4.8.tar.gz", hash = "sha256:15793f5d0512a372b4e7d2284058ad32ce7dd27126b105fb0b2245130445db33"}, 718 | ] 719 | appdirs = [ 720 | {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, 721 | {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, 722 | ] 723 | appnope = [ 724 | {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"}, 725 | {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"}, 726 | ] 727 | atomicwrites = [ 728 | {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"}, 729 | {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"}, 730 | ] 731 | attrs = [ 732 | {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, 733 | {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, 734 | ] 735 | backcall = [ 736 | {file = "backcall-0.1.0.tar.gz", hash = "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4"}, 737 | {file = "backcall-0.1.0.zip", hash = "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"}, 738 | ] 739 | black = [ 740 | {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, 741 | {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, 742 | ] 743 | bleach = [ 744 | {file = "bleach-3.1.4-py2.py3-none-any.whl", hash = "sha256:cc8da25076a1fe56c3ac63671e2194458e0c4d9c7becfd52ca251650d517903c"}, 745 | {file = "bleach-3.1.4.tar.gz", hash = "sha256:e78e426105ac07026ba098f04de8abe9b6e3e98b5befbf89b51a5ef0a4292b03"}, 746 | ] 747 | bump2version = [ 748 | {file = "bump2version-1.0.0-py2.py3-none-any.whl", hash = "sha256:477f0e18a0d58e50bb3dbc9af7fcda464fd0ebfc7a6151d8888602d7153171a0"}, 749 | {file = "bump2version-1.0.0.tar.gz", hash = "sha256:cd4f3a231305e405ed8944d8ff35bd742d9bc740ad62f483bd0ca21ce7131984"}, 750 | ] 751 | cfgv = [ 752 | {file = "cfgv-3.1.0-py2.py3-none-any.whl", hash = "sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53"}, 753 | {file = "cfgv-3.1.0.tar.gz", hash = "sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513"}, 754 | ] 755 | click = [ 756 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 757 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 758 | ] 759 | colorama = [ 760 | {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, 761 | {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, 762 | ] 763 | coverage = [ 764 | {file = "coverage-4.4.2-cp26-cp26m-macosx_10_10_x86_64.whl", hash = "sha256:d1ee76f560c3c3e8faada866a07a32485445e16ed2206ac8378bd90dadffb9f0"}, 765 | {file = "coverage-4.4.2-cp26-cp26m-manylinux1_i686.whl", hash = "sha256:007eeef7e23f9473622f7d94a3e029a45d55a92a1f083f0f3512f5ab9a669b05"}, 766 | {file = "coverage-4.4.2-cp26-cp26m-manylinux1_x86_64.whl", hash = "sha256:17307429935f96c986a1b1674f78079528833410750321d22b5fb35d1883828e"}, 767 | {file = "coverage-4.4.2-cp26-cp26mu-manylinux1_i686.whl", hash = "sha256:845fddf89dca1e94abe168760a38271abfc2e31863fbb4ada7f9a99337d7c3dc"}, 768 | {file = "coverage-4.4.2-cp26-cp26mu-manylinux1_x86_64.whl", hash = "sha256:3f4d0b3403d3e110d2588c275540649b1841725f5a11a7162620224155d00ba2"}, 769 | {file = "coverage-4.4.2-cp27-cp27m-macosx_10_12_intel.whl", hash = "sha256:4c4f368ffe1c2e7602359c2c50233269f3abe1c48ca6b288dcd0fb1d1c679733"}, 770 | {file = "coverage-4.4.2-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:f8c55dd0f56d3d618dfacf129e010cbe5d5f94b6951c1b2f13ab1a2f79c284da"}, 771 | {file = "coverage-4.4.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cdd92dd9471e624cd1d8c1a2703d25f114b59b736b0f1f659a98414e535ffb3d"}, 772 | {file = "coverage-4.4.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2ad357d12971e77360034c1596011a03f50c0f9e1ecd12e081342b8d1aee2236"}, 773 | {file = "coverage-4.4.2-cp27-cp27m-win32.whl", hash = "sha256:700d7579995044dc724847560b78ac786f0ca292867447afda7727a6fbaa082e"}, 774 | {file = "coverage-4.4.2-cp27-cp27m-win_amd64.whl", hash = "sha256:66f393e10dd866be267deb3feca39babba08ae13763e0fc7a1063cbe1f8e49f6"}, 775 | {file = "coverage-4.4.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e9a0e1caed2a52f15c96507ab78a48f346c05681a49c5b003172f8073da6aa6b"}, 776 | {file = "coverage-4.4.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:eea9135432428d3ca7ee9be86af27cb8e56243f73764a9b6c3e0bda1394916be"}, 777 | {file = "coverage-4.4.2-cp33-cp33m-macosx_10_10_x86_64.whl", hash = "sha256:5ff16548492e8a12e65ff3d55857ccd818584ed587a6c2898a9ebbe09a880674"}, 778 | {file = "coverage-4.4.2-cp33-cp33m-manylinux1_i686.whl", hash = "sha256:d00e29b78ff610d300b2c37049a41234d48ea4f2d2581759ebcf67caaf731c31"}, 779 | {file = "coverage-4.4.2-cp33-cp33m-manylinux1_x86_64.whl", hash = "sha256:87d942863fe74b1c3be83a045996addf1639218c2cb89c5da18c06c0fe3917ea"}, 780 | {file = "coverage-4.4.2-cp34-cp34m-macosx_10_10_x86_64.whl", hash = "sha256:358d635b1fc22a425444d52f26287ae5aea9e96e254ff3c59c407426f44574f4"}, 781 | {file = "coverage-4.4.2-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:81912cfe276e0069dca99e1e4e6be7b06b5fc8342641c6b472cb2fed7de7ae18"}, 782 | {file = "coverage-4.4.2-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:079248312838c4c8f3494934ab7382a42d42d5f365f0cf7516f938dbb3f53f3f"}, 783 | {file = "coverage-4.4.2-cp34-cp34m-win32.whl", hash = "sha256:b0059630ca5c6b297690a6bf57bf2fdac1395c24b7935fd73ee64190276b743b"}, 784 | {file = "coverage-4.4.2-cp34-cp34m-win_amd64.whl", hash = "sha256:493082f104b5ca920e97a485913de254cbe351900deed72d4264571c73464cd0"}, 785 | {file = "coverage-4.4.2-cp35-cp35m-macosx_10_10_x86_64.whl", hash = "sha256:e3ba9b14607c23623cf38f90b23f5bed4a3be87cbfa96e2e9f4eabb975d1e98b"}, 786 | {file = "coverage-4.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:82cbd3317320aa63c65555aa4894bf33a13fb3a77f079059eb5935eea415938d"}, 787 | {file = "coverage-4.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9721f1b7275d3112dc7ccf63f0553c769f09b5c25a26ee45872c7f5c09edf6c1"}, 788 | {file = "coverage-4.4.2-cp35-cp35m-win32.whl", hash = "sha256:bd4800e32b4c8d99c3a2c943f1ac430cbf80658d884123d19639bcde90dad44a"}, 789 | {file = "coverage-4.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:f29841e865590af72c4b90d7b5b8e93fd560f5dea436c1d5ee8053788f9285de"}, 790 | {file = "coverage-4.4.2-cp36-cp36m-macosx_10_12_x86_64.whl", hash = "sha256:f3a5c6d054c531536a83521c00e5d4004f1e126e2e2556ce399bef4180fbe540"}, 791 | {file = "coverage-4.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:dd707a21332615108b736ef0b8513d3edaf12d2a7d5fc26cd04a169a8ae9b526"}, 792 | {file = "coverage-4.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2e1a5c6adebb93c3b175103c2f855eda957283c10cf937d791d81bef8872d6ca"}, 793 | {file = "coverage-4.4.2-cp36-cp36m-win32.whl", hash = "sha256:f87f522bde5540d8a4b11df80058281ac38c44b13ce29ced1e294963dd51a8f8"}, 794 | {file = "coverage-4.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a7cfaebd8f24c2b537fa6a271229b051cdac9c1734bb6f939ccfc7c055689baa"}, 795 | {file = "coverage-4.4.2.tar.gz", hash = "sha256:309d91bd7a35063ec7a0e4d75645488bfab3f0b66373e7722f23da7f5b0f34cc"}, 796 | {file = "coverage-4.4.2.win-amd64-py2.7.exe", hash = "sha256:b6cebae1502ce5b87d7c6f532fa90ab345cfbda62b95aeea4e431e164d498a3d"}, 797 | {file = "coverage-4.4.2.win-amd64-py3.4.exe", hash = "sha256:a4497faa4f1c0fc365ba05eaecfb6b5d24e3c8c72e95938f9524e29dadb15e76"}, 798 | {file = "coverage-4.4.2.win-amd64-py3.5.exe", hash = "sha256:2b4d7f03a8a6632598cbc5df15bbca9f778c43db7cf1a838f4fa2c8599a8691a"}, 799 | {file = "coverage-4.4.2.win-amd64-py3.6.exe", hash = "sha256:1afccd7e27cac1b9617be8c769f6d8a6d363699c9b86820f40c74cfb3328921c"}, 800 | {file = "coverage-4.4.2.win32-py2.7.exe", hash = "sha256:0388c12539372bb92d6dde68b4627f0300d948965bbb7fc104924d715fdc0965"}, 801 | {file = "coverage-4.4.2.win32-py3.4.exe", hash = "sha256:ab3508df9a92c1d3362343d235420d08e2662969b83134f8a97dc1451cbe5e84"}, 802 | {file = "coverage-4.4.2.win32-py3.5.exe", hash = "sha256:43a155eb76025c61fc20c3d03b89ca28efa6f5be572ab6110b2fb68eda96bfea"}, 803 | {file = "coverage-4.4.2.win32-py3.6.exe", hash = "sha256:f98b461cb59f117887aa634a66022c0bd394278245ed51189f63a036516e32de"}, 804 | ] 805 | dataclasses = [ 806 | {file = "dataclasses-0.6-py3-none-any.whl", hash = "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f"}, 807 | {file = "dataclasses-0.6.tar.gz", hash = "sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84"}, 808 | ] 809 | decorator = [ 810 | {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, 811 | {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, 812 | ] 813 | distlib = [ 814 | {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, 815 | ] 816 | docutils = [ 817 | {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, 818 | {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, 819 | ] 820 | entrypoints = [ 821 | {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, 822 | {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, 823 | ] 824 | filelock = [ 825 | {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, 826 | {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, 827 | ] 828 | flake8 = [ 829 | {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"}, 830 | {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"}, 831 | ] 832 | identify = [ 833 | {file = "identify-1.4.14-py2.py3-none-any.whl", hash = "sha256:2bb8760d97d8df4408f4e805883dad26a2d076f04be92a10a3e43f09c6060742"}, 834 | {file = "identify-1.4.14.tar.gz", hash = "sha256:faffea0fd8ec86bb146ac538ac350ed0c73908326426d387eded0bcc9d077522"}, 835 | ] 836 | importlib-metadata = [ 837 | {file = "importlib_metadata-1.6.0-py2.py3-none-any.whl", hash = "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f"}, 838 | {file = "importlib_metadata-1.6.0.tar.gz", hash = "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"}, 839 | ] 840 | importlib-resources = [ 841 | {file = "importlib_resources-1.4.0-py2.py3-none-any.whl", hash = "sha256:dd98ceeef3f5ad2ef4cc287b8586da4ebad15877f351e9688987ad663a0a29b8"}, 842 | {file = "importlib_resources-1.4.0.tar.gz", hash = "sha256:4019b6a9082d8ada9def02bece4a76b131518866790d58fdda0b5f8c603b36c2"}, 843 | ] 844 | ipdb = [ 845 | {file = "ipdb-0.13.2.tar.gz", hash = "sha256:77fb1c2a6fccdfee0136078c9ed6fe547ab00db00bebff181f1e8c9e13418d49"}, 846 | ] 847 | ipython = [ 848 | {file = "ipython-7.13.0-py3-none-any.whl", hash = "sha256:eb8d075de37f678424527b5ef6ea23f7b80240ca031c2dd6de5879d687a65333"}, 849 | {file = "ipython-7.13.0.tar.gz", hash = "sha256:ca478e52ae1f88da0102360e57e528b92f3ae4316aabac80a2cd7f7ab2efb48a"}, 850 | ] 851 | ipython-genutils = [ 852 | {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, 853 | {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, 854 | ] 855 | isort = [ 856 | {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, 857 | {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, 858 | ] 859 | jedi = [ 860 | {file = "jedi-0.17.0-py2.py3-none-any.whl", hash = "sha256:cd60c93b71944d628ccac47df9a60fec53150de53d42dc10a7fc4b5ba6aae798"}, 861 | {file = "jedi-0.17.0.tar.gz", hash = "sha256:df40c97641cb943661d2db4c33c2e1ff75d491189423249e989bcea4464f3030"}, 862 | ] 863 | mccabe = [ 864 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 865 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 866 | ] 867 | more-itertools = [ 868 | {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"}, 869 | {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"}, 870 | ] 871 | nodeenv = [ 872 | {file = "nodeenv-1.3.5-py2.py3-none-any.whl", hash = "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"}, 873 | ] 874 | packaging = [ 875 | {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, 876 | {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"}, 877 | ] 878 | parso = [ 879 | {file = "parso-0.7.0-py2.py3-none-any.whl", hash = "sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0"}, 880 | {file = "parso-0.7.0.tar.gz", hash = "sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c"}, 881 | ] 882 | pathspec = [ 883 | {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, 884 | {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, 885 | ] 886 | pathtools = [ 887 | {file = "pathtools-0.1.2.tar.gz", hash = "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0"}, 888 | ] 889 | pexpect = [ 890 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, 891 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, 892 | ] 893 | pickleshare = [ 894 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, 895 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, 896 | ] 897 | pluggy = [ 898 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 899 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 900 | ] 901 | pre-commit = [ 902 | {file = "pre_commit-2.3.0-py2.py3-none-any.whl", hash = "sha256:979b53dab1af35063a483bfe13b0fcbbf1a2cf8c46b60e0a9a8d08e8269647a1"}, 903 | {file = "pre_commit-2.3.0.tar.gz", hash = "sha256:f3e85e68c6d1cbe7828d3471896f1b192cfcf1c4d83bf26e26beeb5941855257"}, 904 | ] 905 | prompt-toolkit = [ 906 | {file = "prompt_toolkit-3.0.5-py3-none-any.whl", hash = "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04"}, 907 | {file = "prompt_toolkit-3.0.5.tar.gz", hash = "sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8"}, 908 | ] 909 | ptyprocess = [ 910 | {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, 911 | {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, 912 | ] 913 | py = [ 914 | {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, 915 | {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, 916 | ] 917 | pycodestyle = [ 918 | {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"}, 919 | {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"}, 920 | ] 921 | pyflakes = [ 922 | {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"}, 923 | {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"}, 924 | ] 925 | pygments = [ 926 | {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, 927 | {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, 928 | ] 929 | pyparsing = [ 930 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 931 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 932 | ] 933 | pytest = [ 934 | {file = "pytest-5.4.2-py3-none-any.whl", hash = "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3"}, 935 | {file = "pytest-5.4.2.tar.gz", hash = "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698"}, 936 | ] 937 | pyyaml = [ 938 | {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, 939 | {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, 940 | {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, 941 | {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, 942 | {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, 943 | {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, 944 | {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, 945 | {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, 946 | {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, 947 | {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, 948 | {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, 949 | ] 950 | readme-renderer = [ 951 | {file = "readme_renderer-26.0-py2.py3-none-any.whl", hash = "sha256:cc4957a803106e820d05d14f71033092537a22daa4f406dfbdd61177e0936376"}, 952 | {file = "readme_renderer-26.0.tar.gz", hash = "sha256:cbe9db71defedd2428a1589cdc545f9bd98e59297449f69d721ef8f1cfced68d"}, 953 | ] 954 | regex = [ 955 | {file = "regex-2020.4.4-cp27-cp27m-win32.whl", hash = "sha256:90742c6ff121a9c5b261b9b215cb476eea97df98ea82037ec8ac95d1be7a034f"}, 956 | {file = "regex-2020.4.4-cp27-cp27m-win_amd64.whl", hash = "sha256:24f4f4062eb16c5bbfff6a22312e8eab92c2c99c51a02e39b4eae54ce8255cd1"}, 957 | {file = "regex-2020.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:08119f707f0ebf2da60d2f24c2f39ca616277bb67ef6c92b72cbf90cbe3a556b"}, 958 | {file = "regex-2020.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c9423a150d3a4fc0f3f2aae897a59919acd293f4cb397429b120a5fcd96ea3db"}, 959 | {file = "regex-2020.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c087bff162158536387c53647411db09b6ee3f9603c334c90943e97b1052a156"}, 960 | {file = "regex-2020.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1cbe0fa0b7f673400eb29e9ef41d4f53638f65f9a2143854de6b1ce2899185c3"}, 961 | {file = "regex-2020.4.4-cp36-cp36m-win32.whl", hash = "sha256:0ce9537396d8f556bcfc317c65b6a0705320701e5ce511f05fc04421ba05b8a8"}, 962 | {file = "regex-2020.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:7e1037073b1b7053ee74c3c6c0ada80f3501ec29d5f46e42669378eae6d4405a"}, 963 | {file = "regex-2020.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4385f12aa289d79419fede43f979e372f527892ac44a541b5446617e4406c468"}, 964 | {file = "regex-2020.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a58dd45cb865be0ce1d5ecc4cfc85cd8c6867bea66733623e54bd95131f473b6"}, 965 | {file = "regex-2020.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ccccdd84912875e34c5ad2d06e1989d890d43af6c2242c6fcfa51556997af6cd"}, 966 | {file = "regex-2020.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ea4adf02d23b437684cd388d557bf76e3afa72f7fed5bbc013482cc00c816948"}, 967 | {file = "regex-2020.4.4-cp37-cp37m-win32.whl", hash = "sha256:2294f8b70e058a2553cd009df003a20802ef75b3c629506be20687df0908177e"}, 968 | {file = "regex-2020.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:e91ba11da11cf770f389e47c3f5c30473e6d85e06d7fd9dcba0017d2867aab4a"}, 969 | {file = "regex-2020.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5635cd1ed0a12b4c42cce18a8d2fb53ff13ff537f09de5fd791e97de27b6400e"}, 970 | {file = "regex-2020.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:23069d9c07e115537f37270d1d5faea3e0bdded8279081c4d4d607a2ad393683"}, 971 | {file = "regex-2020.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c162a21e0da33eb3d31a3ac17a51db5e634fc347f650d271f0305d96601dc15b"}, 972 | {file = "regex-2020.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:fb95debbd1a824b2c4376932f2216cc186912e389bdb0e27147778cf6acb3f89"}, 973 | {file = "regex-2020.4.4-cp38-cp38-win32.whl", hash = "sha256:2a3bf8b48f8e37c3a40bb3f854bf0121c194e69a650b209628d951190b862de3"}, 974 | {file = "regex-2020.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bfed051dbff32fd8945eccca70f5e22b55e4148d2a8a45141a3b053d6455ae3"}, 975 | {file = "regex-2020.4.4.tar.gz", hash = "sha256:295badf61a51add2d428a46b8580309c520d8b26e769868b922750cf3ce67142"}, 976 | ] 977 | six = [ 978 | {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, 979 | {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, 980 | ] 981 | toml = [ 982 | {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, 983 | {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, 984 | {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, 985 | ] 986 | tox = [ 987 | {file = "tox-3.14.6-py2.py3-none-any.whl", hash = "sha256:b2c4b91c975ea5c11463d9ca00bebf82654439c5df0f614807b9bdec62cc9471"}, 988 | {file = "tox-3.14.6.tar.gz", hash = "sha256:a4a6689045d93c208d77230853b28058b7513f5123647b67bf012f82fa168303"}, 989 | ] 990 | traitlets = [ 991 | {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"}, 992 | {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"}, 993 | ] 994 | typed-ast = [ 995 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, 996 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, 997 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, 998 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, 999 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, 1000 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, 1001 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, 1002 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, 1003 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, 1004 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, 1005 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, 1006 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, 1007 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, 1008 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, 1009 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, 1010 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, 1011 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, 1012 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, 1013 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, 1014 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, 1015 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, 1016 | ] 1017 | virtualenv = [ 1018 | {file = "virtualenv-20.0.18-py2.py3-none-any.whl", hash = "sha256:5021396e8f03d0d002a770da90e31e61159684db2859d0ba4850fbea752aa675"}, 1019 | {file = "virtualenv-20.0.18.tar.gz", hash = "sha256:ac53ade75ca189bc97b6c1d9ec0f1a50efe33cbf178ae09452dcd9fd309013c1"}, 1020 | ] 1021 | watchdog = [ 1022 | {file = "watchdog-0.10.2.tar.gz", hash = "sha256:c560efb643faed5ef28784b2245cf8874f939569717a4a12826a173ac644456b"}, 1023 | ] 1024 | wcwidth = [ 1025 | {file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"}, 1026 | {file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"}, 1027 | ] 1028 | webencodings = [ 1029 | {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, 1030 | {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, 1031 | ] 1032 | zipp = [ 1033 | {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, 1034 | {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, 1035 | ] 1036 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool] 2 | [tool.poetry] 3 | name = "vsot" 4 | version = "0.1.4" 5 | description = "VSOT - Django/jinja template formatter" 6 | license = "MIT license" 7 | keywords = ["vsot"] 8 | classifiers = ["Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8"] 9 | homepage = "https://github.com/benhowes/vsot" 10 | authors = ["Ben Howes "] 11 | readme = "README.rst" 12 | 13 | [tool.poetry.scripts] 14 | vsot = "vsot.cli:main" 15 | 16 | [tool.poetry.dependencies] 17 | python = ">=3.6.1" 18 | click = ">=7.0" 19 | antlr4-python3-runtime = "==4.8" 20 | pathspec = "^0.8.0" 21 | dataclasses = { version = ">=0.6", python = "<3.7" } 22 | 23 | [tool.poetry.dev-dependencies] 24 | pre-commit = "^2.3.0" 25 | black = "^19.10b0" 26 | isort = "^4.3.21" 27 | bump2version = "==1.0.0" 28 | watchdog = "==0.10.2" 29 | flake8 = "==3.7.9" 30 | tox = "==3.14.6" 31 | coverage = "*" 32 | pytest = "5.4.2" 33 | ipdb = "^0.13.2" 34 | readme_renderer = "^26.0" 35 | 36 | 37 | [tool.black] 38 | include = '\.pyi?$' 39 | exclude = ''' 40 | /( 41 | \.git 42 | | \.hg 43 | | \.mypy_cache 44 | | \.tox 45 | | \.venv 46 | | _build 47 | | buck-out 48 | | build 49 | | dist 50 | | setup 51 | | vsot/antlr 52 | )/ 53 | ''' 54 | 55 | [tool.vsot] 56 | indent_size = 2 57 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.4 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:pyproject.toml] 7 | search = version = "{current_version}" 8 | replace = version = "{new_version}" 9 | 10 | [bumpversion:file:vsot/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = 19 | .git, 20 | docs, 21 | vsot/antlr 22 | ignore = E203, E266, E501, W503, F403, F401 23 | max-line-length = 100 24 | max-complexity = 18 25 | select = B,C,E,F,W,T4 26 | 27 | [aliases] 28 | test = pytest 29 | 30 | [tool:pytest] 31 | 32 | [isort] 33 | multi_line_output = 3 34 | include_trailing_comma = True 35 | force_grid_wrap = 0 36 | use_parentheses = True 37 | line_length = 88 38 | exclude = vsot/antlr 39 | 40 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit test package for vsot.""" 2 | -------------------------------------------------------------------------------- /tests/data/01_general.input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 12 | 13 | 14 |

My First Heading

15 | 16 |
{#
#} 17 | {% if boulton.is_listening() %} 18 |

My first paragraph.

19 | {% elif boulton.is_sleeping() %} 20 |

Hello {{var }}

21 | Lorizzle ipsum dolor fizzle amizzle, shizzlin dizzle adipiscing elit. Brizzle velizzle, izzle volutpizzle, suscipit quizzle, {{var}} gangster vel, things. Pellentesque hizzle tortizzle. Sed erizzle. Boom shackalack at dolizzle yo turpis tempizzle bizzle. Maurizzle bizzle crackalackin et turpizzle. Phat in tortizzle. Away {{snoop|dawg}} eleifend rhoncizzle rizzle. In bling bling crackalackin platea dictumst. Pimpin' dope. crackalackin gangsta, pretizzle pot, yo go to hizzle, eleifend owned, nunc. Break yo neck, yall suscipit. Integer semper velit tellivizzle sizzle. 22 | {% endif %} 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/data/01_general.output.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 15 | 16 | 17 | 18 |

24 | My First Heading 25 |

26 |
27 | {#
#} 28 | {% if boulton.is_listening() %} 29 |

30 | My first paragraph. 31 |

32 | {% elif boulton.is_sleeping() %} 33 |

34 | Hello {{ var }} 35 |

36 | Lorizzle ipsum dolor fizzle amizzle, shizzlin dizzle adipiscing elit. Brizzle velizzle, 37 | izzle volutpizzle, suscipit quizzle, {{ var }} gangster vel, things. Pellentesque hizzle 38 | tortizzle. Sed erizzle. Boom shackalack at dolizzle yo turpis tempizzle bizzle. Maurizzle 39 | bizzle crackalackin et turpizzle. Phat in tortizzle. Away {{ snoop|dawg }} eleifend 40 | rhoncizzle rizzle. In bling bling crackalackin platea dictumst. Pimpin' dope. 41 | crackalackin gangsta, pretizzle pot, yo go to hizzle, eleifend owned, nunc. Break yo neck, 42 | yall suscipit. Integer semper velit tellivizzle sizzle. 43 | {% endif %} 44 | 45 | 46 | -------------------------------------------------------------------------------- /tests/data/02_base_template.input.html: -------------------------------------------------------------------------------- 1 | {% extends 'bootstrap.html' %} 2 | {% load static from staticfiles %} 3 | {% load bootstrap4 %} 4 | {% load render_bundle from webpack_loader %} 5 | 6 | {% block bootstrap4_extra_head %} 7 | 8 | 9 | {% render_bundle 'site' 'css' %} 10 | {% block settings_scripts %}{% endblock %} 11 | {% endblock %} 12 | 13 | {% block bootstrap4_extra_script %} 14 | 15 | {% block extra_script %}{% endblock %} 16 | {% endblock %} 17 | 18 | {% block bootstrap4_content %} 19 | {% include "partials/impersonation_bar.html" %} 20 | {#{% include "partials/navbar.html" %}#} 21 |
22 |

Hedira

23 |
24 | Hedira logo 25 |

{% block title %}(no title){% endblock %}

26 | 27 | {% autoescape off %}{% bootstrap_messages %}{% endautoescape %} 28 |
29 | {% block content %}(no content){% endblock %} 30 |
31 |
32 |
33 | 34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /tests/data/02_base_template.output.html: -------------------------------------------------------------------------------- 1 | {% extends 'bootstrap.html' %} 2 | {% load static from staticfiles %} 3 | {% load bootstrap4 %} 4 | {% load render_bundle from webpack_loader %} 5 | {% block bootstrap4_extra_head %} 6 | 9 | 10 | {% render_bundle 'site' 'css' %} 11 | {% block settings_scripts %} 12 | {% endblock %} 13 | {% endblock %} 14 | {% block bootstrap4_extra_script %} 15 | 18 | {% block extra_script %} 19 | {% endblock %} 20 | {% endblock %} 21 | {% block bootstrap4_content %} 22 | {% include "partials/impersonation_bar.html" %} 23 | {#{%include"partials/navbar.html"%}#} 24 |
25 |

26 | Hedira 27 |

28 |
29 | 30 | Hedira logo 37 |

38 | {% block title %} 39 | (no title) 40 | {% endblock %} 41 |

42 | {% autoescape off %} 43 | {% bootstrap_messages %} 44 | {% endautoescape %} 45 |
46 | {% block content %} 47 | (no content) 48 | {% endblock %} 49 |
50 |
51 |
52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /tests/data/03_jinja_comments.input.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block content %} 3 |

This is the start of my child template

4 |
5 |

My string: {{my_string}}

6 | {# 7 |

Value from the list: {{my_list[3]}}

8 | #} 9 |

Loop through the list:

10 | 15 |

This is the end of my child template

16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /tests/data/03_jinja_comments.output.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block content %} 3 |

4 | This is the start of my child template 5 |

6 |
7 |

8 | My string: {{ my_string }} 9 |

10 | {# 11 |

12 | Value from the list: {{ my_list[3] }} 13 |

14 | #} 15 |

16 | Loop through the list: 17 |

18 | 25 |

26 | This is the end of my child template 27 |

28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Tests for `vsot` package.""" 4 | 5 | import pytest 6 | from click.testing import CliRunner 7 | 8 | from vsot import cli 9 | 10 | 11 | def test_command_line_interface(): 12 | """Test the CLI.""" 13 | runner = CliRunner() 14 | help_result = runner.invoke(cli.main, ["--help"]) 15 | assert help_result.exit_code == 0 16 | assert "Show this message and exit." in help_result.output 17 | -------------------------------------------------------------------------------- /tests/test_formatter.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import List, Tuple 3 | 4 | import pytest 5 | 6 | from vsot import format_string 7 | from vsot.assertions import assert_equivalent, assert_stable 8 | from vsot.settings import Settings 9 | 10 | THIS_FILE = Path(__file__) 11 | THIS_DIR = THIS_FILE.parent 12 | 13 | 14 | def read_data(name: str) -> Tuple[str, str]: 15 | """ 16 | Attempt to load `{name}.input.html` and `{name}.output.html`. 17 | 18 | If output does not exist, that indicates that the formatted output 19 | should be identical to the input. 20 | """ 21 | input_name = f"{name}.input.html" 22 | output_name = f"{name}.output.html" 23 | 24 | _input: List[str] = [] 25 | _output: List[str] = [] 26 | base_dir = THIS_DIR / "data" 27 | 28 | with open(base_dir / input_name, "r", encoding="utf8") as test: 29 | _input = test.readlines() 30 | 31 | try: 32 | with open(base_dir / output_name, "r", encoding="utf8") as test: 33 | _output = test.readlines() 34 | except IOError: 35 | _output = _input[:] 36 | 37 | return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n" 38 | 39 | 40 | @pytest.fixture 41 | def settings(): 42 | return Settings() 43 | 44 | 45 | @pytest.mark.parametrize( 46 | "input_name", ["01_general", "02_base_template", "03_jinja_comments"] 47 | ) 48 | def test_formatting(input_name, settings): 49 | 50 | src, expected = read_data(input_name) 51 | actual = format_string(src, settings) 52 | 53 | assert actual == expected 54 | assert_equivalent(src, actual) 55 | assert_stable(src, actual, settings) 56 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | isolated_build = true 3 | envlist = py36, py37, py38 4 | 5 | [travis] 6 | python = 7 | 3.8: py38 8 | 3.7: py37 9 | 3.6: py36 10 | 11 | [testenv] 12 | whitelist_externals = poetry 13 | skip_install = true 14 | setenv = 15 | PYTHONPATH = {toxinidir} 16 | commands = 17 | pip install -U poetry 18 | poetry install -v 19 | poetry run pytest tests/ 20 | 21 | -------------------------------------------------------------------------------- /vsot/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for HTML formatter.""" 2 | 3 | __author__ = """Ben Howes""" 4 | __email__ = "ben@ben-howes.co.uk" 5 | __version__ = "__version__ = '0.1.4'" 6 | 7 | 8 | from datetime import datetime 9 | import io 10 | from pathlib import Path 11 | import sys 12 | 13 | from .parser import parse_string 14 | from .printer import print_to_string 15 | from .assertions import assert_equivalent, assert_stable 16 | from .settings import Settings, WriteBack 17 | from .utils import decode_bytes, diff 18 | from .exceptions import NothingChanged 19 | 20 | 21 | def format_string(src: str, settings: Settings) -> str: 22 | ast = parse_string(src) 23 | dst = print_to_string(ast, settings) 24 | 25 | if src == dst: 26 | raise NothingChanged 27 | 28 | assert_equivalent(src, dst) 29 | assert_stable(src, dst, settings=settings) 30 | 31 | return dst 32 | 33 | 34 | def format_file_in_place(src: Path, settings: Settings): 35 | 36 | then = datetime.utcfromtimestamp(src.stat().st_mtime) 37 | with open(src, "rb") as buf: 38 | src_contents, encoding, newline = decode_bytes(buf.read()) 39 | 40 | try: 41 | dst_contents = format_string(src_contents, settings) 42 | except NothingChanged: 43 | return False 44 | 45 | if settings.write_back == WriteBack.YES: 46 | with open(src, "w", encoding=encoding, newline=newline) as f: 47 | f.write(dst_contents) 48 | elif settings.write_back == WriteBack.DIFF: 49 | now = datetime.utcnow() 50 | src_name = f"{src}\t{then} +0000" 51 | dst_name = f"{src}\t{now} +0000" 52 | diff_contents = diff(src_contents, dst_contents, src_name, dst_name) 53 | 54 | # TODO: if this uses multiprocessing in future, a lock is needed 55 | # with lock or nullcontext(): 56 | f = io.TextIOWrapper( 57 | sys.stdout.buffer, encoding=encoding, newline=newline, write_through=True, 58 | ) 59 | f.write(diff_contents) 60 | f.detach() 61 | 62 | return True 63 | -------------------------------------------------------------------------------- /vsot/antlr/HTMLLexer.g4: -------------------------------------------------------------------------------- 1 | /* 2 | [The "BSD licence"] 3 | Copyright (c) 2013 Tom Everett 4 | All rights reserved. 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 3. The name of the author may not be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | lexer grammar HTMLLexer; 28 | 29 | TEMPLATE_TAG_OPEN 30 | : '{%' -> pushMode(TEMPLATE_TAG) 31 | ; 32 | 33 | TEMPLATE_VARIABLE_OPEN 34 | : '{{' -> pushMode(TEMPLATE_TAG) 35 | ; 36 | 37 | TEMPLATE_COMMENT_OPEN 38 | : '{#' 39 | ; 40 | 41 | TEMPLATE_COMMENT_CLOSE 42 | : '#}' 43 | ; 44 | 45 | HTML_COMMENT 46 | : '' 47 | ; 48 | 49 | HTML_CONDITIONAL_COMMENT 50 | : '' 51 | ; 52 | 53 | XML_DECLARATION 54 | : '' 55 | ; 56 | 57 | CDATA 58 | : '' 59 | ; 60 | 61 | DTD 62 | : '' 63 | ; 64 | 65 | SCRIPTLET 66 | : '' 67 | | '<%' .*? '%>' 68 | ; 69 | 70 | SEA_WS 71 | : (' '|'\t'|'\r'? '\n')+ 72 | ; 73 | 74 | SCRIPT_OPEN 75 | : ' pushMode(SCRIPT), pushMode(TAG) 76 | ; 77 | 78 | STYLE_OPEN 79 | : ' pushMode(STYLE), pushMode(TAG) 80 | ; 81 | 82 | TAG_OPEN 83 | : '<' -> pushMode(TAG) 84 | ; 85 | 86 | HTML_TEXT 87 | : ~[<{#]+ 88 | | ~[<{#]* '#' ~[}<{#]+ 89 | | ~[<{#]* '{' ~[%<{#]+ 90 | ; 91 | 92 | 93 | mode TEMPLATE_TAG; 94 | 95 | TEMPLATE_TAG_CLOSE 96 | : ('%}' | '}}') -> popMode 97 | ; 98 | 99 | // Anything that's not whitespace or `%}` 100 | // Lookahead would've been nice 101 | TEMPLATE_CONTENT 102 | : ~[ \t\r\n%}]+ 103 | | ~[ \t\r\n%}]+ '%'+ ~'}' ~[ \t\r\n%}] 104 | ; 105 | 106 | TEMPLATE_WS 107 | : [ \t\r\n] -> skip 108 | ; 109 | 110 | // 111 | // tag declarations 112 | // 113 | mode TAG; 114 | 115 | TAG_CLOSE 116 | : '>' -> popMode 117 | ; 118 | 119 | TAG_SLASH_CLOSE 120 | : '/>' -> popMode 121 | ; 122 | 123 | TAG_SLASH 124 | : '/' 125 | ; 126 | 127 | // 128 | // lexing mode for attribute values 129 | // 130 | TAG_EQUALS 131 | : '=' -> pushMode(ATTVALUE) 132 | ; 133 | 134 | TAG_NAME 135 | : TAG_NameStartChar TAG_NameChar* 136 | ; 137 | 138 | TAG_WHITESPACE 139 | : [ \t\r\n] -> skip 140 | ; 141 | 142 | fragment 143 | HEXDIGIT 144 | : [a-fA-F0-9] 145 | ; 146 | 147 | fragment 148 | DIGIT 149 | : [0-9] 150 | ; 151 | 152 | fragment 153 | TAG_NameChar 154 | : TAG_NameStartChar 155 | | '-' 156 | | '_' 157 | | '.' 158 | | DIGIT 159 | | '\u00B7' 160 | | '\u0300'..'\u036F' 161 | | '\u203F'..'\u2040' 162 | ; 163 | 164 | fragment 165 | TAG_NameStartChar 166 | : [:a-zA-Z] 167 | | '\u2070'..'\u218F' 168 | | '\u2C00'..'\u2FEF' 169 | | '\u3001'..'\uD7FF' 170 | | '\uF900'..'\uFDCF' 171 | | '\uFDF0'..'\uFFFD' 172 | ; 173 | 174 | // 175 | // 176 | // 177 | mode SCRIPT; 178 | 179 | SCRIPT_BODY 180 | : .*? '' -> popMode 181 | ; 182 | 183 | SCRIPT_SHORT_BODY 184 | : .*? '' -> popMode 185 | ; 186 | 187 | // 188 | // 189 | // 190 | mode STYLE; 191 | 192 | STYLE_BODY 193 | : .*? '' -> popMode 194 | ; 195 | 196 | STYLE_SHORT_BODY 197 | : .*? '' -> popMode 198 | ; 199 | 200 | // 201 | // attribute values 202 | // 203 | mode ATTVALUE; 204 | 205 | // an attribute value may have spaces b/t the '=' and the value 206 | ATTVALUE_VALUE 207 | : [ ]* ATTRIBUTE -> popMode 208 | ; 209 | 210 | ATTRIBUTE 211 | : DOUBLE_QUOTE_STRING 212 | | SINGLE_QUOTE_STRING 213 | | ATTCHARS 214 | | HEXCHARS 215 | | DECCHARS 216 | ; 217 | 218 | fragment ATTCHAR 219 | : '-' 220 | | '_' 221 | | '.' 222 | | '/' 223 | | '+' 224 | | ',' 225 | | '?' 226 | | '=' 227 | | ':' 228 | | ';' 229 | | '#' 230 | | [0-9a-zA-Z] 231 | ; 232 | 233 | fragment ATTCHARS 234 | : ATTCHAR+ ' '? 235 | ; 236 | 237 | fragment HEXCHARS 238 | : '#' [0-9a-fA-F]+ 239 | ; 240 | 241 | fragment DECCHARS 242 | : [0-9]+ '%'? 243 | ; 244 | 245 | fragment DOUBLE_QUOTE_STRING 246 | : '"' ~[<"]* '"' 247 | ; 248 | fragment SINGLE_QUOTE_STRING 249 | : '\'' ~[<']* '\'' 250 | ; 251 | -------------------------------------------------------------------------------- /vsot/antlr/HTMLLexer.interp: -------------------------------------------------------------------------------- 1 | token literal names: 2 | null 3 | '{%' 4 | '{{' 5 | '{#' 6 | '#}' 7 | null 8 | null 9 | null 10 | null 11 | null 12 | null 13 | null 14 | null 15 | null 16 | '<' 17 | null 18 | null 19 | null 20 | null 21 | '>' 22 | '/>' 23 | '/' 24 | '=' 25 | null 26 | null 27 | null 28 | null 29 | null 30 | null 31 | null 32 | null 33 | 34 | token symbolic names: 35 | null 36 | TEMPLATE_TAG_OPEN 37 | TEMPLATE_VARIABLE_OPEN 38 | TEMPLATE_COMMENT_OPEN 39 | TEMPLATE_COMMENT_CLOSE 40 | HTML_COMMENT 41 | HTML_CONDITIONAL_COMMENT 42 | XML_DECLARATION 43 | CDATA 44 | DTD 45 | SCRIPTLET 46 | SEA_WS 47 | SCRIPT_OPEN 48 | STYLE_OPEN 49 | TAG_OPEN 50 | HTML_TEXT 51 | TEMPLATE_TAG_CLOSE 52 | TEMPLATE_CONTENT 53 | TEMPLATE_WS 54 | TAG_CLOSE 55 | TAG_SLASH_CLOSE 56 | TAG_SLASH 57 | TAG_EQUALS 58 | TAG_NAME 59 | TAG_WHITESPACE 60 | SCRIPT_BODY 61 | SCRIPT_SHORT_BODY 62 | STYLE_BODY 63 | STYLE_SHORT_BODY 64 | ATTVALUE_VALUE 65 | ATTRIBUTE 66 | 67 | rule names: 68 | TEMPLATE_TAG_OPEN 69 | TEMPLATE_VARIABLE_OPEN 70 | TEMPLATE_COMMENT_OPEN 71 | TEMPLATE_COMMENT_CLOSE 72 | HTML_COMMENT 73 | HTML_CONDITIONAL_COMMENT 74 | XML_DECLARATION 75 | CDATA 76 | DTD 77 | SCRIPTLET 78 | SEA_WS 79 | SCRIPT_OPEN 80 | STYLE_OPEN 81 | TAG_OPEN 82 | HTML_TEXT 83 | TEMPLATE_TAG_CLOSE 84 | TEMPLATE_CONTENT 85 | TEMPLATE_WS 86 | TAG_CLOSE 87 | TAG_SLASH_CLOSE 88 | TAG_SLASH 89 | TAG_EQUALS 90 | TAG_NAME 91 | TAG_WHITESPACE 92 | HEXDIGIT 93 | DIGIT 94 | TAG_NameChar 95 | TAG_NameStartChar 96 | SCRIPT_BODY 97 | SCRIPT_SHORT_BODY 98 | STYLE_BODY 99 | STYLE_SHORT_BODY 100 | ATTVALUE_VALUE 101 | ATTRIBUTE 102 | ATTCHAR 103 | ATTCHARS 104 | HEXCHARS 105 | DECCHARS 106 | DOUBLE_QUOTE_STRING 107 | SINGLE_QUOTE_STRING 108 | 109 | channel names: 110 | DEFAULT_TOKEN_CHANNEL 111 | HIDDEN 112 | 113 | mode names: 114 | DEFAULT_MODE 115 | TEMPLATE_TAG 116 | TAG 117 | SCRIPT 118 | STYLE 119 | ATTVALUE 120 | 121 | atn: 122 | [3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 32, 455, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 7, 6, 111, 10, 6, 12, 6, 14, 6, 114, 11, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 7, 7, 125, 10, 7, 12, 7, 14, 7, 128, 11, 7, 3, 7, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 7, 8, 140, 10, 8, 12, 8, 14, 8, 143, 11, 8, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 7, 9, 158, 10, 9, 12, 9, 14, 9, 161, 11, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 10, 3, 10, 7, 10, 171, 10, 10, 12, 10, 14, 10, 174, 11, 10, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 11, 7, 11, 182, 10, 11, 12, 11, 14, 11, 185, 11, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 3, 11, 7, 11, 193, 10, 11, 12, 11, 14, 11, 196, 11, 11, 3, 11, 3, 11, 5, 11, 200, 10, 11, 3, 12, 3, 12, 5, 12, 204, 10, 12, 3, 12, 6, 12, 207, 10, 12, 13, 12, 14, 12, 208, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 14, 3, 14, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 15, 3, 16, 6, 16, 237, 10, 16, 13, 16, 14, 16, 238, 3, 16, 7, 16, 242, 10, 16, 12, 16, 14, 16, 245, 11, 16, 3, 16, 3, 16, 6, 16, 249, 10, 16, 13, 16, 14, 16, 250, 3, 16, 7, 16, 254, 10, 16, 12, 16, 14, 16, 257, 11, 16, 3, 16, 3, 16, 6, 16, 261, 10, 16, 13, 16, 14, 16, 262, 5, 16, 265, 10, 16, 3, 17, 3, 17, 3, 17, 3, 17, 5, 17, 271, 10, 17, 3, 17, 3, 17, 3, 18, 6, 18, 276, 10, 18, 13, 18, 14, 18, 277, 3, 18, 6, 18, 281, 10, 18, 13, 18, 14, 18, 282, 3, 18, 6, 18, 286, 10, 18, 13, 18, 14, 18, 287, 3, 18, 3, 18, 5, 18, 292, 10, 18, 3, 19, 3, 19, 3, 19, 3, 19, 3, 20, 3, 20, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 23, 3, 23, 3, 24, 3, 24, 7, 24, 315, 10, 24, 12, 24, 14, 24, 318, 11, 24, 3, 25, 3, 25, 3, 25, 3, 25, 3, 26, 3, 26, 3, 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 28, 5, 28, 332, 10, 28, 3, 29, 5, 29, 335, 10, 29, 3, 30, 7, 30, 338, 10, 30, 12, 30, 14, 30, 341, 11, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 31, 7, 31, 356, 10, 31, 12, 31, 14, 31, 359, 11, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 7, 32, 368, 10, 32, 12, 32, 14, 32, 371, 11, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 33, 7, 33, 385, 10, 33, 12, 33, 14, 33, 388, 11, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 3, 34, 7, 34, 397, 10, 34, 12, 34, 14, 34, 400, 11, 34, 3, 34, 3, 34, 3, 34, 3, 34, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 5, 35, 411, 10, 35, 3, 36, 5, 36, 414, 10, 36, 3, 37, 6, 37, 417, 10, 37, 13, 37, 14, 37, 418, 3, 37, 5, 37, 422, 10, 37, 3, 38, 3, 38, 6, 38, 426, 10, 38, 13, 38, 14, 38, 427, 3, 39, 6, 39, 431, 10, 39, 13, 39, 14, 39, 432, 3, 39, 5, 39, 436, 10, 39, 3, 40, 3, 40, 7, 40, 440, 10, 40, 12, 40, 14, 40, 443, 11, 40, 3, 40, 3, 40, 3, 41, 3, 41, 7, 41, 449, 10, 41, 12, 41, 14, 41, 452, 11, 41, 3, 41, 3, 41, 13, 112, 126, 141, 159, 172, 183, 194, 339, 357, 369, 386, 2, 42, 8, 3, 10, 4, 12, 5, 14, 6, 16, 7, 18, 8, 20, 9, 22, 10, 24, 11, 26, 12, 28, 13, 30, 14, 32, 15, 34, 16, 36, 17, 38, 18, 40, 19, 42, 20, 44, 21, 46, 22, 48, 23, 50, 24, 52, 25, 54, 26, 56, 2, 58, 2, 60, 2, 62, 2, 64, 27, 66, 28, 68, 29, 70, 30, 72, 31, 74, 32, 76, 2, 78, 2, 80, 2, 82, 2, 84, 2, 86, 2, 8, 2, 3, 4, 5, 6, 7, 18, 4, 2, 11, 11, 34, 34, 5, 2, 37, 37, 62, 62, 125, 125, 6, 2, 37, 37, 62, 62, 125, 125, 127, 127, 6, 2, 37, 37, 39, 39, 62, 62, 125, 125, 7, 2, 11, 12, 15, 15, 34, 34, 39, 39, 127, 127, 3, 2, 127, 127, 5, 2, 11, 12, 15, 15, 34, 34, 5, 2, 50, 59, 67, 72, 99, 104, 3, 2, 50, 59, 4, 2, 47, 48, 97, 97, 5, 2, 185, 185, 770, 881, 8257, 8258, 10, 2, 60, 60, 67, 92, 99, 124, 8306, 8593, 11266, 12273, 12291, 55297, 63746, 64977, 65010, 65535, 3, 2, 34, 34, 9, 2, 37, 37, 45, 61, 63, 63, 65, 65, 67, 92, 97, 97, 99, 124, 4, 2, 36, 36, 62, 62, 4, 2, 41, 41, 62, 62, 2, 482, 2, 8, 3, 2, 2, 2, 2, 10, 3, 2, 2, 2, 2, 12, 3, 2, 2, 2, 2, 14, 3, 2, 2, 2, 2, 16, 3, 2, 2, 2, 2, 18, 3, 2, 2, 2, 2, 20, 3, 2, 2, 2, 2, 22, 3, 2, 2, 2, 2, 24, 3, 2, 2, 2, 2, 26, 3, 2, 2, 2, 2, 28, 3, 2, 2, 2, 2, 30, 3, 2, 2, 2, 2, 32, 3, 2, 2, 2, 2, 34, 3, 2, 2, 2, 2, 36, 3, 2, 2, 2, 3, 38, 3, 2, 2, 2, 3, 40, 3, 2, 2, 2, 3, 42, 3, 2, 2, 2, 4, 44, 3, 2, 2, 2, 4, 46, 3, 2, 2, 2, 4, 48, 3, 2, 2, 2, 4, 50, 3, 2, 2, 2, 4, 52, 3, 2, 2, 2, 4, 54, 3, 2, 2, 2, 5, 64, 3, 2, 2, 2, 5, 66, 3, 2, 2, 2, 6, 68, 3, 2, 2, 2, 6, 70, 3, 2, 2, 2, 7, 72, 3, 2, 2, 2, 7, 74, 3, 2, 2, 2, 8, 88, 3, 2, 2, 2, 10, 93, 3, 2, 2, 2, 12, 98, 3, 2, 2, 2, 14, 101, 3, 2, 2, 2, 16, 104, 3, 2, 2, 2, 18, 119, 3, 2, 2, 2, 20, 132, 3, 2, 2, 2, 22, 146, 3, 2, 2, 2, 24, 166, 3, 2, 2, 2, 26, 199, 3, 2, 2, 2, 28, 206, 3, 2, 2, 2, 30, 210, 3, 2, 2, 2, 32, 221, 3, 2, 2, 2, 34, 231, 3, 2, 2, 2, 36, 264, 3, 2, 2, 2, 38, 270, 3, 2, 2, 2, 40, 291, 3, 2, 2, 2, 42, 293, 3, 2, 2, 2, 44, 297, 3, 2, 2, 2, 46, 301, 3, 2, 2, 2, 48, 306, 3, 2, 2, 2, 50, 308, 3, 2, 2, 2, 52, 312, 3, 2, 2, 2, 54, 319, 3, 2, 2, 2, 56, 323, 3, 2, 2, 2, 58, 325, 3, 2, 2, 2, 60, 331, 3, 2, 2, 2, 62, 334, 3, 2, 2, 2, 64, 339, 3, 2, 2, 2, 66, 357, 3, 2, 2, 2, 68, 369, 3, 2, 2, 2, 70, 386, 3, 2, 2, 2, 72, 398, 3, 2, 2, 2, 74, 410, 3, 2, 2, 2, 76, 413, 3, 2, 2, 2, 78, 416, 3, 2, 2, 2, 80, 423, 3, 2, 2, 2, 82, 430, 3, 2, 2, 2, 84, 437, 3, 2, 2, 2, 86, 446, 3, 2, 2, 2, 88, 89, 7, 125, 2, 2, 89, 90, 7, 39, 2, 2, 90, 91, 3, 2, 2, 2, 91, 92, 8, 2, 2, 2, 92, 9, 3, 2, 2, 2, 93, 94, 7, 125, 2, 2, 94, 95, 7, 125, 2, 2, 95, 96, 3, 2, 2, 2, 96, 97, 8, 3, 2, 2, 97, 11, 3, 2, 2, 2, 98, 99, 7, 125, 2, 2, 99, 100, 7, 37, 2, 2, 100, 13, 3, 2, 2, 2, 101, 102, 7, 37, 2, 2, 102, 103, 7, 127, 2, 2, 103, 15, 3, 2, 2, 2, 104, 105, 7, 62, 2, 2, 105, 106, 7, 35, 2, 2, 106, 107, 7, 47, 2, 2, 107, 108, 7, 47, 2, 2, 108, 112, 3, 2, 2, 2, 109, 111, 11, 2, 2, 2, 110, 109, 3, 2, 2, 2, 111, 114, 3, 2, 2, 2, 112, 113, 3, 2, 2, 2, 112, 110, 3, 2, 2, 2, 113, 115, 3, 2, 2, 2, 114, 112, 3, 2, 2, 2, 115, 116, 7, 47, 2, 2, 116, 117, 7, 47, 2, 2, 117, 118, 7, 64, 2, 2, 118, 17, 3, 2, 2, 2, 119, 120, 7, 62, 2, 2, 120, 121, 7, 35, 2, 2, 121, 122, 7, 93, 2, 2, 122, 126, 3, 2, 2, 2, 123, 125, 11, 2, 2, 2, 124, 123, 3, 2, 2, 2, 125, 128, 3, 2, 2, 2, 126, 127, 3, 2, 2, 2, 126, 124, 3, 2, 2, 2, 127, 129, 3, 2, 2, 2, 128, 126, 3, 2, 2, 2, 129, 130, 7, 95, 2, 2, 130, 131, 7, 64, 2, 2, 131, 19, 3, 2, 2, 2, 132, 133, 7, 62, 2, 2, 133, 134, 7, 65, 2, 2, 134, 135, 7, 122, 2, 2, 135, 136, 7, 111, 2, 2, 136, 137, 7, 110, 2, 2, 137, 141, 3, 2, 2, 2, 138, 140, 11, 2, 2, 2, 139, 138, 3, 2, 2, 2, 140, 143, 3, 2, 2, 2, 141, 142, 3, 2, 2, 2, 141, 139, 3, 2, 2, 2, 142, 144, 3, 2, 2, 2, 143, 141, 3, 2, 2, 2, 144, 145, 7, 64, 2, 2, 145, 21, 3, 2, 2, 2, 146, 147, 7, 62, 2, 2, 147, 148, 7, 35, 2, 2, 148, 149, 7, 93, 2, 2, 149, 150, 7, 69, 2, 2, 150, 151, 7, 70, 2, 2, 151, 152, 7, 67, 2, 2, 152, 153, 7, 86, 2, 2, 153, 154, 7, 67, 2, 2, 154, 155, 7, 93, 2, 2, 155, 159, 3, 2, 2, 2, 156, 158, 11, 2, 2, 2, 157, 156, 3, 2, 2, 2, 158, 161, 3, 2, 2, 2, 159, 160, 3, 2, 2, 2, 159, 157, 3, 2, 2, 2, 160, 162, 3, 2, 2, 2, 161, 159, 3, 2, 2, 2, 162, 163, 7, 95, 2, 2, 163, 164, 7, 95, 2, 2, 164, 165, 7, 64, 2, 2, 165, 23, 3, 2, 2, 2, 166, 167, 7, 62, 2, 2, 167, 168, 7, 35, 2, 2, 168, 172, 3, 2, 2, 2, 169, 171, 11, 2, 2, 2, 170, 169, 3, 2, 2, 2, 171, 174, 3, 2, 2, 2, 172, 173, 3, 2, 2, 2, 172, 170, 3, 2, 2, 2, 173, 175, 3, 2, 2, 2, 174, 172, 3, 2, 2, 2, 175, 176, 7, 64, 2, 2, 176, 25, 3, 2, 2, 2, 177, 178, 7, 62, 2, 2, 178, 179, 7, 65, 2, 2, 179, 183, 3, 2, 2, 2, 180, 182, 11, 2, 2, 2, 181, 180, 3, 2, 2, 2, 182, 185, 3, 2, 2, 2, 183, 184, 3, 2, 2, 2, 183, 181, 3, 2, 2, 2, 184, 186, 3, 2, 2, 2, 185, 183, 3, 2, 2, 2, 186, 187, 7, 65, 2, 2, 187, 200, 7, 64, 2, 2, 188, 189, 7, 62, 2, 2, 189, 190, 7, 39, 2, 2, 190, 194, 3, 2, 2, 2, 191, 193, 11, 2, 2, 2, 192, 191, 3, 2, 2, 2, 193, 196, 3, 2, 2, 2, 194, 195, 3, 2, 2, 2, 194, 192, 3, 2, 2, 2, 195, 197, 3, 2, 2, 2, 196, 194, 3, 2, 2, 2, 197, 198, 7, 39, 2, 2, 198, 200, 7, 64, 2, 2, 199, 177, 3, 2, 2, 2, 199, 188, 3, 2, 2, 2, 200, 27, 3, 2, 2, 2, 201, 207, 9, 2, 2, 2, 202, 204, 7, 15, 2, 2, 203, 202, 3, 2, 2, 2, 203, 204, 3, 2, 2, 2, 204, 205, 3, 2, 2, 2, 205, 207, 7, 12, 2, 2, 206, 201, 3, 2, 2, 2, 206, 203, 3, 2, 2, 2, 207, 208, 3, 2, 2, 2, 208, 206, 3, 2, 2, 2, 208, 209, 3, 2, 2, 2, 209, 29, 3, 2, 2, 2, 210, 211, 7, 62, 2, 2, 211, 212, 7, 117, 2, 2, 212, 213, 7, 101, 2, 2, 213, 214, 7, 116, 2, 2, 214, 215, 7, 107, 2, 2, 215, 216, 7, 114, 2, 2, 216, 217, 7, 118, 2, 2, 217, 218, 3, 2, 2, 2, 218, 219, 8, 13, 3, 2, 219, 220, 8, 13, 4, 2, 220, 31, 3, 2, 2, 2, 221, 222, 7, 62, 2, 2, 222, 223, 7, 117, 2, 2, 223, 224, 7, 118, 2, 2, 224, 225, 7, 123, 2, 2, 225, 226, 7, 110, 2, 2, 226, 227, 7, 103, 2, 2, 227, 228, 3, 2, 2, 2, 228, 229, 8, 14, 5, 2, 229, 230, 8, 14, 4, 2, 230, 33, 3, 2, 2, 2, 231, 232, 7, 62, 2, 2, 232, 233, 3, 2, 2, 2, 233, 234, 8, 15, 4, 2, 234, 35, 3, 2, 2, 2, 235, 237, 10, 3, 2, 2, 236, 235, 3, 2, 2, 2, 237, 238, 3, 2, 2, 2, 238, 236, 3, 2, 2, 2, 238, 239, 3, 2, 2, 2, 239, 265, 3, 2, 2, 2, 240, 242, 10, 3, 2, 2, 241, 240, 3, 2, 2, 2, 242, 245, 3, 2, 2, 2, 243, 241, 3, 2, 2, 2, 243, 244, 3, 2, 2, 2, 244, 246, 3, 2, 2, 2, 245, 243, 3, 2, 2, 2, 246, 248, 7, 37, 2, 2, 247, 249, 10, 4, 2, 2, 248, 247, 3, 2, 2, 2, 249, 250, 3, 2, 2, 2, 250, 248, 3, 2, 2, 2, 250, 251, 3, 2, 2, 2, 251, 265, 3, 2, 2, 2, 252, 254, 10, 3, 2, 2, 253, 252, 3, 2, 2, 2, 254, 257, 3, 2, 2, 2, 255, 253, 3, 2, 2, 2, 255, 256, 3, 2, 2, 2, 256, 258, 3, 2, 2, 2, 257, 255, 3, 2, 2, 2, 258, 260, 7, 125, 2, 2, 259, 261, 10, 5, 2, 2, 260, 259, 3, 2, 2, 2, 261, 262, 3, 2, 2, 2, 262, 260, 3, 2, 2, 2, 262, 263, 3, 2, 2, 2, 263, 265, 3, 2, 2, 2, 264, 236, 3, 2, 2, 2, 264, 243, 3, 2, 2, 2, 264, 255, 3, 2, 2, 2, 265, 37, 3, 2, 2, 2, 266, 267, 7, 39, 2, 2, 267, 271, 7, 127, 2, 2, 268, 269, 7, 127, 2, 2, 269, 271, 7, 127, 2, 2, 270, 266, 3, 2, 2, 2, 270, 268, 3, 2, 2, 2, 271, 272, 3, 2, 2, 2, 272, 273, 8, 17, 6, 2, 273, 39, 3, 2, 2, 2, 274, 276, 10, 6, 2, 2, 275, 274, 3, 2, 2, 2, 276, 277, 3, 2, 2, 2, 277, 275, 3, 2, 2, 2, 277, 278, 3, 2, 2, 2, 278, 292, 3, 2, 2, 2, 279, 281, 10, 6, 2, 2, 280, 279, 3, 2, 2, 2, 281, 282, 3, 2, 2, 2, 282, 280, 3, 2, 2, 2, 282, 283, 3, 2, 2, 2, 283, 285, 3, 2, 2, 2, 284, 286, 7, 39, 2, 2, 285, 284, 3, 2, 2, 2, 286, 287, 3, 2, 2, 2, 287, 285, 3, 2, 2, 2, 287, 288, 3, 2, 2, 2, 288, 289, 3, 2, 2, 2, 289, 290, 10, 7, 2, 2, 290, 292, 10, 6, 2, 2, 291, 275, 3, 2, 2, 2, 291, 280, 3, 2, 2, 2, 292, 41, 3, 2, 2, 2, 293, 294, 9, 8, 2, 2, 294, 295, 3, 2, 2, 2, 295, 296, 8, 19, 7, 2, 296, 43, 3, 2, 2, 2, 297, 298, 7, 64, 2, 2, 298, 299, 3, 2, 2, 2, 299, 300, 8, 20, 6, 2, 300, 45, 3, 2, 2, 2, 301, 302, 7, 49, 2, 2, 302, 303, 7, 64, 2, 2, 303, 304, 3, 2, 2, 2, 304, 305, 8, 21, 6, 2, 305, 47, 3, 2, 2, 2, 306, 307, 7, 49, 2, 2, 307, 49, 3, 2, 2, 2, 308, 309, 7, 63, 2, 2, 309, 310, 3, 2, 2, 2, 310, 311, 8, 23, 8, 2, 311, 51, 3, 2, 2, 2, 312, 316, 5, 62, 29, 2, 313, 315, 5, 60, 28, 2, 314, 313, 3, 2, 2, 2, 315, 318, 3, 2, 2, 2, 316, 314, 3, 2, 2, 2, 316, 317, 3, 2, 2, 2, 317, 53, 3, 2, 2, 2, 318, 316, 3, 2, 2, 2, 319, 320, 9, 8, 2, 2, 320, 321, 3, 2, 2, 2, 321, 322, 8, 25, 7, 2, 322, 55, 3, 2, 2, 2, 323, 324, 9, 9, 2, 2, 324, 57, 3, 2, 2, 2, 325, 326, 9, 10, 2, 2, 326, 59, 3, 2, 2, 2, 327, 332, 5, 62, 29, 2, 328, 332, 9, 11, 2, 2, 329, 332, 5, 58, 27, 2, 330, 332, 9, 12, 2, 2, 331, 327, 3, 2, 2, 2, 331, 328, 3, 2, 2, 2, 331, 329, 3, 2, 2, 2, 331, 330, 3, 2, 2, 2, 332, 61, 3, 2, 2, 2, 333, 335, 9, 13, 2, 2, 334, 333, 3, 2, 2, 2, 335, 63, 3, 2, 2, 2, 336, 338, 11, 2, 2, 2, 337, 336, 3, 2, 2, 2, 338, 341, 3, 2, 2, 2, 339, 340, 3, 2, 2, 2, 339, 337, 3, 2, 2, 2, 340, 342, 3, 2, 2, 2, 341, 339, 3, 2, 2, 2, 342, 343, 7, 62, 2, 2, 343, 344, 7, 49, 2, 2, 344, 345, 7, 117, 2, 2, 345, 346, 7, 101, 2, 2, 346, 347, 7, 116, 2, 2, 347, 348, 7, 107, 2, 2, 348, 349, 7, 114, 2, 2, 349, 350, 7, 118, 2, 2, 350, 351, 7, 64, 2, 2, 351, 352, 3, 2, 2, 2, 352, 353, 8, 30, 6, 2, 353, 65, 3, 2, 2, 2, 354, 356, 11, 2, 2, 2, 355, 354, 3, 2, 2, 2, 356, 359, 3, 2, 2, 2, 357, 358, 3, 2, 2, 2, 357, 355, 3, 2, 2, 2, 358, 360, 3, 2, 2, 2, 359, 357, 3, 2, 2, 2, 360, 361, 7, 62, 2, 2, 361, 362, 7, 49, 2, 2, 362, 363, 7, 64, 2, 2, 363, 364, 3, 2, 2, 2, 364, 365, 8, 31, 6, 2, 365, 67, 3, 2, 2, 2, 366, 368, 11, 2, 2, 2, 367, 366, 3, 2, 2, 2, 368, 371, 3, 2, 2, 2, 369, 370, 3, 2, 2, 2, 369, 367, 3, 2, 2, 2, 370, 372, 3, 2, 2, 2, 371, 369, 3, 2, 2, 2, 372, 373, 7, 62, 2, 2, 373, 374, 7, 49, 2, 2, 374, 375, 7, 117, 2, 2, 375, 376, 7, 118, 2, 2, 376, 377, 7, 123, 2, 2, 377, 378, 7, 110, 2, 2, 378, 379, 7, 103, 2, 2, 379, 380, 7, 64, 2, 2, 380, 381, 3, 2, 2, 2, 381, 382, 8, 32, 6, 2, 382, 69, 3, 2, 2, 2, 383, 385, 11, 2, 2, 2, 384, 383, 3, 2, 2, 2, 385, 388, 3, 2, 2, 2, 386, 387, 3, 2, 2, 2, 386, 384, 3, 2, 2, 2, 387, 389, 3, 2, 2, 2, 388, 386, 3, 2, 2, 2, 389, 390, 7, 62, 2, 2, 390, 391, 7, 49, 2, 2, 391, 392, 7, 64, 2, 2, 392, 393, 3, 2, 2, 2, 393, 394, 8, 33, 6, 2, 394, 71, 3, 2, 2, 2, 395, 397, 9, 14, 2, 2, 396, 395, 3, 2, 2, 2, 397, 400, 3, 2, 2, 2, 398, 396, 3, 2, 2, 2, 398, 399, 3, 2, 2, 2, 399, 401, 3, 2, 2, 2, 400, 398, 3, 2, 2, 2, 401, 402, 5, 74, 35, 2, 402, 403, 3, 2, 2, 2, 403, 404, 8, 34, 6, 2, 404, 73, 3, 2, 2, 2, 405, 411, 5, 84, 40, 2, 406, 411, 5, 86, 41, 2, 407, 411, 5, 78, 37, 2, 408, 411, 5, 80, 38, 2, 409, 411, 5, 82, 39, 2, 410, 405, 3, 2, 2, 2, 410, 406, 3, 2, 2, 2, 410, 407, 3, 2, 2, 2, 410, 408, 3, 2, 2, 2, 410, 409, 3, 2, 2, 2, 411, 75, 3, 2, 2, 2, 412, 414, 9, 15, 2, 2, 413, 412, 3, 2, 2, 2, 414, 77, 3, 2, 2, 2, 415, 417, 5, 76, 36, 2, 416, 415, 3, 2, 2, 2, 417, 418, 3, 2, 2, 2, 418, 416, 3, 2, 2, 2, 418, 419, 3, 2, 2, 2, 419, 421, 3, 2, 2, 2, 420, 422, 7, 34, 2, 2, 421, 420, 3, 2, 2, 2, 421, 422, 3, 2, 2, 2, 422, 79, 3, 2, 2, 2, 423, 425, 7, 37, 2, 2, 424, 426, 9, 9, 2, 2, 425, 424, 3, 2, 2, 2, 426, 427, 3, 2, 2, 2, 427, 425, 3, 2, 2, 2, 427, 428, 3, 2, 2, 2, 428, 81, 3, 2, 2, 2, 429, 431, 9, 10, 2, 2, 430, 429, 3, 2, 2, 2, 431, 432, 3, 2, 2, 2, 432, 430, 3, 2, 2, 2, 432, 433, 3, 2, 2, 2, 433, 435, 3, 2, 2, 2, 434, 436, 7, 39, 2, 2, 435, 434, 3, 2, 2, 2, 435, 436, 3, 2, 2, 2, 436, 83, 3, 2, 2, 2, 437, 441, 7, 36, 2, 2, 438, 440, 10, 16, 2, 2, 439, 438, 3, 2, 2, 2, 440, 443, 3, 2, 2, 2, 441, 439, 3, 2, 2, 2, 441, 442, 3, 2, 2, 2, 442, 444, 3, 2, 2, 2, 443, 441, 3, 2, 2, 2, 444, 445, 7, 36, 2, 2, 445, 85, 3, 2, 2, 2, 446, 450, 7, 41, 2, 2, 447, 449, 10, 17, 2, 2, 448, 447, 3, 2, 2, 2, 449, 452, 3, 2, 2, 2, 450, 448, 3, 2, 2, 2, 450, 451, 3, 2, 2, 2, 451, 453, 3, 2, 2, 2, 452, 450, 3, 2, 2, 2, 453, 454, 7, 41, 2, 2, 454, 87, 3, 2, 2, 2, 47, 2, 3, 4, 5, 6, 7, 112, 126, 141, 159, 172, 183, 194, 199, 203, 206, 208, 238, 243, 250, 255, 262, 264, 270, 277, 282, 287, 291, 316, 331, 334, 339, 357, 369, 386, 398, 410, 413, 418, 421, 427, 432, 435, 441, 450, 9, 7, 3, 2, 7, 5, 2, 7, 4, 2, 7, 6, 2, 6, 2, 2, 8, 2, 2, 7, 7, 2] -------------------------------------------------------------------------------- /vsot/antlr/HTMLLexer.py: -------------------------------------------------------------------------------- 1 | # Generated from /vsot/antlr/HTMLLexer.g4 by ANTLR 4.8 2 | from antlr4 import * 3 | from io import StringIO 4 | from typing.io import TextIO 5 | import sys 6 | 7 | 8 | 9 | def serializedATN(): 10 | with StringIO() as buf: 11 | buf.write("\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2 ") 12 | buf.write("\u01c7\b\1\b\1\b\1\b\1\b\1\b\1\4\2\t\2\4\3\t\3\4\4\t\4") 13 | buf.write("\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13") 14 | buf.write("\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4") 15 | buf.write("\21\t\21\4\22\t\22\4\23\t\23\4\24\t\24\4\25\t\25\4\26") 16 | buf.write("\t\26\4\27\t\27\4\30\t\30\4\31\t\31\4\32\t\32\4\33\t\33") 17 | buf.write("\4\34\t\34\4\35\t\35\4\36\t\36\4\37\t\37\4 \t \4!\t!\4") 18 | buf.write("\"\t\"\4#\t#\4$\t$\4%\t%\4&\t&\4\'\t\'\4(\t(\4)\t)\3\2") 19 | buf.write("\3\2\3\2\3\2\3\2\3\3\3\3\3\3\3\3\3\3\3\4\3\4\3\4\3\5\3") 20 | buf.write("\5\3\5\3\6\3\6\3\6\3\6\3\6\3\6\7\6o\n\6\f\6\16\6r\13\6") 21 | buf.write("\3\6\3\6\3\6\3\6\3\7\3\7\3\7\3\7\3\7\7\7}\n\7\f\7\16\7") 22 | buf.write("\u0080\13\7\3\7\3\7\3\7\3\b\3\b\3\b\3\b\3\b\3\b\3\b\7") 23 | buf.write("\b\u008c\n\b\f\b\16\b\u008f\13\b\3\b\3\b\3\t\3\t\3\t\3") 24 | buf.write("\t\3\t\3\t\3\t\3\t\3\t\3\t\3\t\7\t\u009e\n\t\f\t\16\t") 25 | buf.write("\u00a1\13\t\3\t\3\t\3\t\3\t\3\n\3\n\3\n\3\n\7\n\u00ab") 26 | buf.write("\n\n\f\n\16\n\u00ae\13\n\3\n\3\n\3\13\3\13\3\13\3\13\7") 27 | buf.write("\13\u00b6\n\13\f\13\16\13\u00b9\13\13\3\13\3\13\3\13\3") 28 | buf.write("\13\3\13\3\13\7\13\u00c1\n\13\f\13\16\13\u00c4\13\13\3") 29 | buf.write("\13\3\13\5\13\u00c8\n\13\3\f\3\f\5\f\u00cc\n\f\3\f\6\f") 30 | buf.write("\u00cf\n\f\r\f\16\f\u00d0\3\r\3\r\3\r\3\r\3\r\3\r\3\r") 31 | buf.write("\3\r\3\r\3\r\3\r\3\16\3\16\3\16\3\16\3\16\3\16\3\16\3") 32 | buf.write("\16\3\16\3\16\3\17\3\17\3\17\3\17\3\20\6\20\u00ed\n\20") 33 | buf.write("\r\20\16\20\u00ee\3\20\7\20\u00f2\n\20\f\20\16\20\u00f5") 34 | buf.write("\13\20\3\20\3\20\6\20\u00f9\n\20\r\20\16\20\u00fa\3\20") 35 | buf.write("\7\20\u00fe\n\20\f\20\16\20\u0101\13\20\3\20\3\20\6\20") 36 | buf.write("\u0105\n\20\r\20\16\20\u0106\5\20\u0109\n\20\3\21\3\21") 37 | buf.write("\3\21\3\21\5\21\u010f\n\21\3\21\3\21\3\22\6\22\u0114\n") 38 | buf.write("\22\r\22\16\22\u0115\3\22\6\22\u0119\n\22\r\22\16\22\u011a") 39 | buf.write("\3\22\6\22\u011e\n\22\r\22\16\22\u011f\3\22\3\22\5\22") 40 | buf.write("\u0124\n\22\3\23\3\23\3\23\3\23\3\24\3\24\3\24\3\24\3") 41 | buf.write("\25\3\25\3\25\3\25\3\25\3\26\3\26\3\27\3\27\3\27\3\27") 42 | buf.write("\3\30\3\30\7\30\u013b\n\30\f\30\16\30\u013e\13\30\3\31") 43 | buf.write("\3\31\3\31\3\31\3\32\3\32\3\33\3\33\3\34\3\34\3\34\3\34") 44 | buf.write("\5\34\u014c\n\34\3\35\5\35\u014f\n\35\3\36\7\36\u0152") 45 | buf.write("\n\36\f\36\16\36\u0155\13\36\3\36\3\36\3\36\3\36\3\36") 46 | buf.write("\3\36\3\36\3\36\3\36\3\36\3\36\3\36\3\37\7\37\u0164\n") 47 | buf.write("\37\f\37\16\37\u0167\13\37\3\37\3\37\3\37\3\37\3\37\3") 48 | buf.write("\37\3 \7 \u0170\n \f \16 \u0173\13 \3 \3 \3 \3 \3 \3 ") 49 | buf.write("\3 \3 \3 \3 \3 \3!\7!\u0181\n!\f!\16!\u0184\13!\3!\3!") 50 | buf.write("\3!\3!\3!\3!\3\"\7\"\u018d\n\"\f\"\16\"\u0190\13\"\3\"") 51 | buf.write("\3\"\3\"\3\"\3#\3#\3#\3#\3#\5#\u019b\n#\3$\5$\u019e\n") 52 | buf.write("$\3%\6%\u01a1\n%\r%\16%\u01a2\3%\5%\u01a6\n%\3&\3&\6&") 53 | buf.write("\u01aa\n&\r&\16&\u01ab\3\'\6\'\u01af\n\'\r\'\16\'\u01b0") 54 | buf.write("\3\'\5\'\u01b4\n\'\3(\3(\7(\u01b8\n(\f(\16(\u01bb\13(") 55 | buf.write("\3(\3(\3)\3)\7)\u01c1\n)\f)\16)\u01c4\13)\3)\3)\rp~\u008d") 56 | buf.write("\u009f\u00ac\u00b7\u00c2\u0153\u0165\u0171\u0182\2*\b") 57 | buf.write("\3\n\4\f\5\16\6\20\7\22\b\24\t\26\n\30\13\32\f\34\r\36") 58 | buf.write("\16 \17\"\20$\21&\22(\23*\24,\25.\26\60\27\62\30\64\31") 59 | buf.write("\66\328\2:\2<\2>\2@\33B\34D\35F\36H\37J L\2N\2P\2R\2T") 60 | buf.write("\2V\2\b\2\3\4\5\6\7\22\4\2\13\13\"\"\5\2%%>>}}\6\2%%>") 61 | buf.write(">}}\177\177\6\2%%\'\'>>}}\7\2\13\f\17\17\"\"\'\'\177\177") 62 | buf.write("\3\2\177\177\5\2\13\f\17\17\"\"\5\2\62;CHch\3\2\62;\4") 63 | buf.write("\2/\60aa\5\2\u00b9\u00b9\u0302\u0371\u2041\u2042\n\2<") 64 | buf.write(">\4\2))") 66 | buf.write(">>\2\u01e2\2\b\3\2\2\2\2\n\3\2\2\2\2\f\3\2\2\2\2\16\3") 67 | buf.write("\2\2\2\2\20\3\2\2\2\2\22\3\2\2\2\2\24\3\2\2\2\2\26\3\2") 68 | buf.write("\2\2\2\30\3\2\2\2\2\32\3\2\2\2\2\34\3\2\2\2\2\36\3\2\2") 69 | buf.write("\2\2 \3\2\2\2\2\"\3\2\2\2\2$\3\2\2\2\3&\3\2\2\2\3(\3\2") 70 | buf.write("\2\2\3*\3\2\2\2\4,\3\2\2\2\4.\3\2\2\2\4\60\3\2\2\2\4\62") 71 | buf.write("\3\2\2\2\4\64\3\2\2\2\4\66\3\2\2\2\5@\3\2\2\2\5B\3\2\2") 72 | buf.write("\2\6D\3\2\2\2\6F\3\2\2\2\7H\3\2\2\2\7J\3\2\2\2\bX\3\2") 73 | buf.write("\2\2\n]\3\2\2\2\fb\3\2\2\2\16e\3\2\2\2\20h\3\2\2\2\22") 74 | buf.write("w\3\2\2\2\24\u0084\3\2\2\2\26\u0092\3\2\2\2\30\u00a6\3") 75 | buf.write("\2\2\2\32\u00c7\3\2\2\2\34\u00ce\3\2\2\2\36\u00d2\3\2") 76 | buf.write("\2\2 \u00dd\3\2\2\2\"\u00e7\3\2\2\2$\u0108\3\2\2\2&\u010e") 77 | buf.write("\3\2\2\2(\u0123\3\2\2\2*\u0125\3\2\2\2,\u0129\3\2\2\2") 78 | buf.write(".\u012d\3\2\2\2\60\u0132\3\2\2\2\62\u0134\3\2\2\2\64\u0138") 79 | buf.write("\3\2\2\2\66\u013f\3\2\2\28\u0143\3\2\2\2:\u0145\3\2\2") 80 | buf.write("\2<\u014b\3\2\2\2>\u014e\3\2\2\2@\u0153\3\2\2\2B\u0165") 81 | buf.write("\3\2\2\2D\u0171\3\2\2\2F\u0182\3\2\2\2H\u018e\3\2\2\2") 82 | buf.write("J\u019a\3\2\2\2L\u019d\3\2\2\2N\u01a0\3\2\2\2P\u01a7\3") 83 | buf.write("\2\2\2R\u01ae\3\2\2\2T\u01b5\3\2\2\2V\u01be\3\2\2\2XY") 84 | buf.write("\7}\2\2YZ\7\'\2\2Z[\3\2\2\2[\\\b\2\2\2\\\t\3\2\2\2]^\7") 85 | buf.write("}\2\2^_\7}\2\2_`\3\2\2\2`a\b\3\2\2a\13\3\2\2\2bc\7}\2") 86 | buf.write("\2cd\7%\2\2d\r\3\2\2\2ef\7%\2\2fg\7\177\2\2g\17\3\2\2") 87 | buf.write("\2hi\7>\2\2ij\7#\2\2jk\7/\2\2kl\7/\2\2lp\3\2\2\2mo\13") 88 | buf.write("\2\2\2nm\3\2\2\2or\3\2\2\2pq\3\2\2\2pn\3\2\2\2qs\3\2\2") 89 | buf.write("\2rp\3\2\2\2st\7/\2\2tu\7/\2\2uv\7@\2\2v\21\3\2\2\2wx") 90 | buf.write("\7>\2\2xy\7#\2\2yz\7]\2\2z~\3\2\2\2{}\13\2\2\2|{\3\2\2") 91 | buf.write("\2}\u0080\3\2\2\2~\177\3\2\2\2~|\3\2\2\2\177\u0081\3\2") 92 | buf.write("\2\2\u0080~\3\2\2\2\u0081\u0082\7_\2\2\u0082\u0083\7@") 93 | buf.write("\2\2\u0083\23\3\2\2\2\u0084\u0085\7>\2\2\u0085\u0086\7") 94 | buf.write("A\2\2\u0086\u0087\7z\2\2\u0087\u0088\7o\2\2\u0088\u0089") 95 | buf.write("\7n\2\2\u0089\u008d\3\2\2\2\u008a\u008c\13\2\2\2\u008b") 96 | buf.write("\u008a\3\2\2\2\u008c\u008f\3\2\2\2\u008d\u008e\3\2\2\2") 97 | buf.write("\u008d\u008b\3\2\2\2\u008e\u0090\3\2\2\2\u008f\u008d\3") 98 | buf.write("\2\2\2\u0090\u0091\7@\2\2\u0091\25\3\2\2\2\u0092\u0093") 99 | buf.write("\7>\2\2\u0093\u0094\7#\2\2\u0094\u0095\7]\2\2\u0095\u0096") 100 | buf.write("\7E\2\2\u0096\u0097\7F\2\2\u0097\u0098\7C\2\2\u0098\u0099") 101 | buf.write("\7V\2\2\u0099\u009a\7C\2\2\u009a\u009b\7]\2\2\u009b\u009f") 102 | buf.write("\3\2\2\2\u009c\u009e\13\2\2\2\u009d\u009c\3\2\2\2\u009e") 103 | buf.write("\u00a1\3\2\2\2\u009f\u00a0\3\2\2\2\u009f\u009d\3\2\2\2") 104 | buf.write("\u00a0\u00a2\3\2\2\2\u00a1\u009f\3\2\2\2\u00a2\u00a3\7") 105 | buf.write("_\2\2\u00a3\u00a4\7_\2\2\u00a4\u00a5\7@\2\2\u00a5\27\3") 106 | buf.write("\2\2\2\u00a6\u00a7\7>\2\2\u00a7\u00a8\7#\2\2\u00a8\u00ac") 107 | buf.write("\3\2\2\2\u00a9\u00ab\13\2\2\2\u00aa\u00a9\3\2\2\2\u00ab") 108 | buf.write("\u00ae\3\2\2\2\u00ac\u00ad\3\2\2\2\u00ac\u00aa\3\2\2\2") 109 | buf.write("\u00ad\u00af\3\2\2\2\u00ae\u00ac\3\2\2\2\u00af\u00b0\7") 110 | buf.write("@\2\2\u00b0\31\3\2\2\2\u00b1\u00b2\7>\2\2\u00b2\u00b3") 111 | buf.write("\7A\2\2\u00b3\u00b7\3\2\2\2\u00b4\u00b6\13\2\2\2\u00b5") 112 | buf.write("\u00b4\3\2\2\2\u00b6\u00b9\3\2\2\2\u00b7\u00b8\3\2\2\2") 113 | buf.write("\u00b7\u00b5\3\2\2\2\u00b8\u00ba\3\2\2\2\u00b9\u00b7\3") 114 | buf.write("\2\2\2\u00ba\u00bb\7A\2\2\u00bb\u00c8\7@\2\2\u00bc\u00bd") 115 | buf.write("\7>\2\2\u00bd\u00be\7\'\2\2\u00be\u00c2\3\2\2\2\u00bf") 116 | buf.write("\u00c1\13\2\2\2\u00c0\u00bf\3\2\2\2\u00c1\u00c4\3\2\2") 117 | buf.write("\2\u00c2\u00c3\3\2\2\2\u00c2\u00c0\3\2\2\2\u00c3\u00c5") 118 | buf.write("\3\2\2\2\u00c4\u00c2\3\2\2\2\u00c5\u00c6\7\'\2\2\u00c6") 119 | buf.write("\u00c8\7@\2\2\u00c7\u00b1\3\2\2\2\u00c7\u00bc\3\2\2\2") 120 | buf.write("\u00c8\33\3\2\2\2\u00c9\u00cf\t\2\2\2\u00ca\u00cc\7\17") 121 | buf.write("\2\2\u00cb\u00ca\3\2\2\2\u00cb\u00cc\3\2\2\2\u00cc\u00cd") 122 | buf.write("\3\2\2\2\u00cd\u00cf\7\f\2\2\u00ce\u00c9\3\2\2\2\u00ce") 123 | buf.write("\u00cb\3\2\2\2\u00cf\u00d0\3\2\2\2\u00d0\u00ce\3\2\2\2") 124 | buf.write("\u00d0\u00d1\3\2\2\2\u00d1\35\3\2\2\2\u00d2\u00d3\7>\2") 125 | buf.write("\2\u00d3\u00d4\7u\2\2\u00d4\u00d5\7e\2\2\u00d5\u00d6\7") 126 | buf.write("t\2\2\u00d6\u00d7\7k\2\2\u00d7\u00d8\7r\2\2\u00d8\u00d9") 127 | buf.write("\7v\2\2\u00d9\u00da\3\2\2\2\u00da\u00db\b\r\3\2\u00db") 128 | buf.write("\u00dc\b\r\4\2\u00dc\37\3\2\2\2\u00dd\u00de\7>\2\2\u00de") 129 | buf.write("\u00df\7u\2\2\u00df\u00e0\7v\2\2\u00e0\u00e1\7{\2\2\u00e1") 130 | buf.write("\u00e2\7n\2\2\u00e2\u00e3\7g\2\2\u00e3\u00e4\3\2\2\2\u00e4") 131 | buf.write("\u00e5\b\16\5\2\u00e5\u00e6\b\16\4\2\u00e6!\3\2\2\2\u00e7") 132 | buf.write("\u00e8\7>\2\2\u00e8\u00e9\3\2\2\2\u00e9\u00ea\b\17\4\2") 133 | buf.write("\u00ea#\3\2\2\2\u00eb\u00ed\n\3\2\2\u00ec\u00eb\3\2\2") 134 | buf.write("\2\u00ed\u00ee\3\2\2\2\u00ee\u00ec\3\2\2\2\u00ee\u00ef") 135 | buf.write("\3\2\2\2\u00ef\u0109\3\2\2\2\u00f0\u00f2\n\3\2\2\u00f1") 136 | buf.write("\u00f0\3\2\2\2\u00f2\u00f5\3\2\2\2\u00f3\u00f1\3\2\2\2") 137 | buf.write("\u00f3\u00f4\3\2\2\2\u00f4\u00f6\3\2\2\2\u00f5\u00f3\3") 138 | buf.write("\2\2\2\u00f6\u00f8\7%\2\2\u00f7\u00f9\n\4\2\2\u00f8\u00f7") 139 | buf.write("\3\2\2\2\u00f9\u00fa\3\2\2\2\u00fa\u00f8\3\2\2\2\u00fa") 140 | buf.write("\u00fb\3\2\2\2\u00fb\u0109\3\2\2\2\u00fc\u00fe\n\3\2\2") 141 | buf.write("\u00fd\u00fc\3\2\2\2\u00fe\u0101\3\2\2\2\u00ff\u00fd\3") 142 | buf.write("\2\2\2\u00ff\u0100\3\2\2\2\u0100\u0102\3\2\2\2\u0101\u00ff") 143 | buf.write("\3\2\2\2\u0102\u0104\7}\2\2\u0103\u0105\n\5\2\2\u0104") 144 | buf.write("\u0103\3\2\2\2\u0105\u0106\3\2\2\2\u0106\u0104\3\2\2\2") 145 | buf.write("\u0106\u0107\3\2\2\2\u0107\u0109\3\2\2\2\u0108\u00ec\3") 146 | buf.write("\2\2\2\u0108\u00f3\3\2\2\2\u0108\u00ff\3\2\2\2\u0109%") 147 | buf.write("\3\2\2\2\u010a\u010b\7\'\2\2\u010b\u010f\7\177\2\2\u010c") 148 | buf.write("\u010d\7\177\2\2\u010d\u010f\7\177\2\2\u010e\u010a\3\2") 149 | buf.write("\2\2\u010e\u010c\3\2\2\2\u010f\u0110\3\2\2\2\u0110\u0111") 150 | buf.write("\b\21\6\2\u0111\'\3\2\2\2\u0112\u0114\n\6\2\2\u0113\u0112") 151 | buf.write("\3\2\2\2\u0114\u0115\3\2\2\2\u0115\u0113\3\2\2\2\u0115") 152 | buf.write("\u0116\3\2\2\2\u0116\u0124\3\2\2\2\u0117\u0119\n\6\2\2") 153 | buf.write("\u0118\u0117\3\2\2\2\u0119\u011a\3\2\2\2\u011a\u0118\3") 154 | buf.write("\2\2\2\u011a\u011b\3\2\2\2\u011b\u011d\3\2\2\2\u011c\u011e") 155 | buf.write("\7\'\2\2\u011d\u011c\3\2\2\2\u011e\u011f\3\2\2\2\u011f") 156 | buf.write("\u011d\3\2\2\2\u011f\u0120\3\2\2\2\u0120\u0121\3\2\2\2") 157 | buf.write("\u0121\u0122\n\7\2\2\u0122\u0124\n\6\2\2\u0123\u0113\3") 158 | buf.write("\2\2\2\u0123\u0118\3\2\2\2\u0124)\3\2\2\2\u0125\u0126") 159 | buf.write("\t\b\2\2\u0126\u0127\3\2\2\2\u0127\u0128\b\23\7\2\u0128") 160 | buf.write("+\3\2\2\2\u0129\u012a\7@\2\2\u012a\u012b\3\2\2\2\u012b") 161 | buf.write("\u012c\b\24\6\2\u012c-\3\2\2\2\u012d\u012e\7\61\2\2\u012e") 162 | buf.write("\u012f\7@\2\2\u012f\u0130\3\2\2\2\u0130\u0131\b\25\6\2") 163 | buf.write("\u0131/\3\2\2\2\u0132\u0133\7\61\2\2\u0133\61\3\2\2\2") 164 | buf.write("\u0134\u0135\7?\2\2\u0135\u0136\3\2\2\2\u0136\u0137\b") 165 | buf.write("\27\b\2\u0137\63\3\2\2\2\u0138\u013c\5>\35\2\u0139\u013b") 166 | buf.write("\5<\34\2\u013a\u0139\3\2\2\2\u013b\u013e\3\2\2\2\u013c") 167 | buf.write("\u013a\3\2\2\2\u013c\u013d\3\2\2\2\u013d\65\3\2\2\2\u013e") 168 | buf.write("\u013c\3\2\2\2\u013f\u0140\t\b\2\2\u0140\u0141\3\2\2\2") 169 | buf.write("\u0141\u0142\b\31\7\2\u0142\67\3\2\2\2\u0143\u0144\t\t") 170 | buf.write("\2\2\u01449\3\2\2\2\u0145\u0146\t\n\2\2\u0146;\3\2\2\2") 171 | buf.write("\u0147\u014c\5>\35\2\u0148\u014c\t\13\2\2\u0149\u014c") 172 | buf.write("\5:\33\2\u014a\u014c\t\f\2\2\u014b\u0147\3\2\2\2\u014b") 173 | buf.write("\u0148\3\2\2\2\u014b\u0149\3\2\2\2\u014b\u014a\3\2\2\2") 174 | buf.write("\u014c=\3\2\2\2\u014d\u014f\t\r\2\2\u014e\u014d\3\2\2") 175 | buf.write("\2\u014f?\3\2\2\2\u0150\u0152\13\2\2\2\u0151\u0150\3\2") 176 | buf.write("\2\2\u0152\u0155\3\2\2\2\u0153\u0154\3\2\2\2\u0153\u0151") 177 | buf.write("\3\2\2\2\u0154\u0156\3\2\2\2\u0155\u0153\3\2\2\2\u0156") 178 | buf.write("\u0157\7>\2\2\u0157\u0158\7\61\2\2\u0158\u0159\7u\2\2") 179 | buf.write("\u0159\u015a\7e\2\2\u015a\u015b\7t\2\2\u015b\u015c\7k") 180 | buf.write("\2\2\u015c\u015d\7r\2\2\u015d\u015e\7v\2\2\u015e\u015f") 181 | buf.write("\7@\2\2\u015f\u0160\3\2\2\2\u0160\u0161\b\36\6\2\u0161") 182 | buf.write("A\3\2\2\2\u0162\u0164\13\2\2\2\u0163\u0162\3\2\2\2\u0164") 183 | buf.write("\u0167\3\2\2\2\u0165\u0166\3\2\2\2\u0165\u0163\3\2\2\2") 184 | buf.write("\u0166\u0168\3\2\2\2\u0167\u0165\3\2\2\2\u0168\u0169\7") 185 | buf.write(">\2\2\u0169\u016a\7\61\2\2\u016a\u016b\7@\2\2\u016b\u016c") 186 | buf.write("\3\2\2\2\u016c\u016d\b\37\6\2\u016dC\3\2\2\2\u016e\u0170") 187 | buf.write("\13\2\2\2\u016f\u016e\3\2\2\2\u0170\u0173\3\2\2\2\u0171") 188 | buf.write("\u0172\3\2\2\2\u0171\u016f\3\2\2\2\u0172\u0174\3\2\2\2") 189 | buf.write("\u0173\u0171\3\2\2\2\u0174\u0175\7>\2\2\u0175\u0176\7") 190 | buf.write("\61\2\2\u0176\u0177\7u\2\2\u0177\u0178\7v\2\2\u0178\u0179") 191 | buf.write("\7{\2\2\u0179\u017a\7n\2\2\u017a\u017b\7g\2\2\u017b\u017c") 192 | buf.write("\7@\2\2\u017c\u017d\3\2\2\2\u017d\u017e\b \6\2\u017eE") 193 | buf.write("\3\2\2\2\u017f\u0181\13\2\2\2\u0180\u017f\3\2\2\2\u0181") 194 | buf.write("\u0184\3\2\2\2\u0182\u0183\3\2\2\2\u0182\u0180\3\2\2\2") 195 | buf.write("\u0183\u0185\3\2\2\2\u0184\u0182\3\2\2\2\u0185\u0186\7") 196 | buf.write(">\2\2\u0186\u0187\7\61\2\2\u0187\u0188\7@\2\2\u0188\u0189") 197 | buf.write("\3\2\2\2\u0189\u018a\b!\6\2\u018aG\3\2\2\2\u018b\u018d") 198 | buf.write("\t\16\2\2\u018c\u018b\3\2\2\2\u018d\u0190\3\2\2\2\u018e") 199 | buf.write("\u018c\3\2\2\2\u018e\u018f\3\2\2\2\u018f\u0191\3\2\2\2") 200 | buf.write("\u0190\u018e\3\2\2\2\u0191\u0192\5J#\2\u0192\u0193\3\2") 201 | buf.write("\2\2\u0193\u0194\b\"\6\2\u0194I\3\2\2\2\u0195\u019b\5") 202 | buf.write("T(\2\u0196\u019b\5V)\2\u0197\u019b\5N%\2\u0198\u019b\5") 203 | buf.write("P&\2\u0199\u019b\5R\'\2\u019a\u0195\3\2\2\2\u019a\u0196") 204 | buf.write("\3\2\2\2\u019a\u0197\3\2\2\2\u019a\u0198\3\2\2\2\u019a") 205 | buf.write("\u0199\3\2\2\2\u019bK\3\2\2\2\u019c\u019e\t\17\2\2\u019d") 206 | buf.write("\u019c\3\2\2\2\u019eM\3\2\2\2\u019f\u01a1\5L$\2\u01a0") 207 | buf.write("\u019f\3\2\2\2\u01a1\u01a2\3\2\2\2\u01a2\u01a0\3\2\2\2") 208 | buf.write("\u01a2\u01a3\3\2\2\2\u01a3\u01a5\3\2\2\2\u01a4\u01a6\7") 209 | buf.write("\"\2\2\u01a5\u01a4\3\2\2\2\u01a5\u01a6\3\2\2\2\u01a6O") 210 | buf.write("\3\2\2\2\u01a7\u01a9\7%\2\2\u01a8\u01aa\t\t\2\2\u01a9") 211 | buf.write("\u01a8\3\2\2\2\u01aa\u01ab\3\2\2\2\u01ab\u01a9\3\2\2\2") 212 | buf.write("\u01ab\u01ac\3\2\2\2\u01acQ\3\2\2\2\u01ad\u01af\t\n\2") 213 | buf.write("\2\u01ae\u01ad\3\2\2\2\u01af\u01b0\3\2\2\2\u01b0\u01ae") 214 | buf.write("\3\2\2\2\u01b0\u01b1\3\2\2\2\u01b1\u01b3\3\2\2\2\u01b2") 215 | buf.write("\u01b4\7\'\2\2\u01b3\u01b2\3\2\2\2\u01b3\u01b4\3\2\2\2") 216 | buf.write("\u01b4S\3\2\2\2\u01b5\u01b9\7$\2\2\u01b6\u01b8\n\20\2") 217 | buf.write("\2\u01b7\u01b6\3\2\2\2\u01b8\u01bb\3\2\2\2\u01b9\u01b7") 218 | buf.write("\3\2\2\2\u01b9\u01ba\3\2\2\2\u01ba\u01bc\3\2\2\2\u01bb") 219 | buf.write("\u01b9\3\2\2\2\u01bc\u01bd\7$\2\2\u01bdU\3\2\2\2\u01be") 220 | buf.write("\u01c2\7)\2\2\u01bf\u01c1\n\21\2\2\u01c0\u01bf\3\2\2\2") 221 | buf.write("\u01c1\u01c4\3\2\2\2\u01c2\u01c0\3\2\2\2\u01c2\u01c3\3") 222 | buf.write("\2\2\2\u01c3\u01c5\3\2\2\2\u01c4\u01c2\3\2\2\2\u01c5\u01c6") 223 | buf.write("\7)\2\2\u01c6W\3\2\2\2/\2\3\4\5\6\7p~\u008d\u009f\u00ac") 224 | buf.write("\u00b7\u00c2\u00c7\u00cb\u00ce\u00d0\u00ee\u00f3\u00fa") 225 | buf.write("\u00ff\u0106\u0108\u010e\u0115\u011a\u011f\u0123\u013c") 226 | buf.write("\u014b\u014e\u0153\u0165\u0171\u0182\u018e\u019a\u019d") 227 | buf.write("\u01a2\u01a5\u01ab\u01b0\u01b3\u01b9\u01c2\t\7\3\2\7\5") 228 | buf.write("\2\7\4\2\7\6\2\6\2\2\b\2\2\7\7\2") 229 | return buf.getvalue() 230 | 231 | 232 | class HTMLLexer(Lexer): 233 | 234 | atn = ATNDeserializer().deserialize(serializedATN()) 235 | 236 | decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ] 237 | 238 | TEMPLATE_TAG = 1 239 | TAG = 2 240 | SCRIPT = 3 241 | STYLE = 4 242 | ATTVALUE = 5 243 | 244 | TEMPLATE_TAG_OPEN = 1 245 | TEMPLATE_VARIABLE_OPEN = 2 246 | TEMPLATE_COMMENT_OPEN = 3 247 | TEMPLATE_COMMENT_CLOSE = 4 248 | HTML_COMMENT = 5 249 | HTML_CONDITIONAL_COMMENT = 6 250 | XML_DECLARATION = 7 251 | CDATA = 8 252 | DTD = 9 253 | SCRIPTLET = 10 254 | SEA_WS = 11 255 | SCRIPT_OPEN = 12 256 | STYLE_OPEN = 13 257 | TAG_OPEN = 14 258 | HTML_TEXT = 15 259 | TEMPLATE_TAG_CLOSE = 16 260 | TEMPLATE_CONTENT = 17 261 | TEMPLATE_WS = 18 262 | TAG_CLOSE = 19 263 | TAG_SLASH_CLOSE = 20 264 | TAG_SLASH = 21 265 | TAG_EQUALS = 22 266 | TAG_NAME = 23 267 | TAG_WHITESPACE = 24 268 | SCRIPT_BODY = 25 269 | SCRIPT_SHORT_BODY = 26 270 | STYLE_BODY = 27 271 | STYLE_SHORT_BODY = 28 272 | ATTVALUE_VALUE = 29 273 | ATTRIBUTE = 30 274 | 275 | channelNames = [ u"DEFAULT_TOKEN_CHANNEL", u"HIDDEN" ] 276 | 277 | modeNames = [ "DEFAULT_MODE", "TEMPLATE_TAG", "TAG", "SCRIPT", "STYLE", 278 | "ATTVALUE" ] 279 | 280 | literalNames = [ "", 281 | "'{%'", "'{{'", "'{#'", "'#}'", "'<'", "'>'", "'/>'", "'/'", 282 | "'='" ] 283 | 284 | symbolicNames = [ "", 285 | "TEMPLATE_TAG_OPEN", "TEMPLATE_VARIABLE_OPEN", "TEMPLATE_COMMENT_OPEN", 286 | "TEMPLATE_COMMENT_CLOSE", "HTML_COMMENT", "HTML_CONDITIONAL_COMMENT", 287 | "XML_DECLARATION", "CDATA", "DTD", "SCRIPTLET", "SEA_WS", "SCRIPT_OPEN", 288 | "STYLE_OPEN", "TAG_OPEN", "HTML_TEXT", "TEMPLATE_TAG_CLOSE", 289 | "TEMPLATE_CONTENT", "TEMPLATE_WS", "TAG_CLOSE", "TAG_SLASH_CLOSE", 290 | "TAG_SLASH", "TAG_EQUALS", "TAG_NAME", "TAG_WHITESPACE", "SCRIPT_BODY", 291 | "SCRIPT_SHORT_BODY", "STYLE_BODY", "STYLE_SHORT_BODY", "ATTVALUE_VALUE", 292 | "ATTRIBUTE" ] 293 | 294 | ruleNames = [ "TEMPLATE_TAG_OPEN", "TEMPLATE_VARIABLE_OPEN", "TEMPLATE_COMMENT_OPEN", 295 | "TEMPLATE_COMMENT_CLOSE", "HTML_COMMENT", "HTML_CONDITIONAL_COMMENT", 296 | "XML_DECLARATION", "CDATA", "DTD", "SCRIPTLET", "SEA_WS", 297 | "SCRIPT_OPEN", "STYLE_OPEN", "TAG_OPEN", "HTML_TEXT", 298 | "TEMPLATE_TAG_CLOSE", "TEMPLATE_CONTENT", "TEMPLATE_WS", 299 | "TAG_CLOSE", "TAG_SLASH_CLOSE", "TAG_SLASH", "TAG_EQUALS", 300 | "TAG_NAME", "TAG_WHITESPACE", "HEXDIGIT", "DIGIT", "TAG_NameChar", 301 | "TAG_NameStartChar", "SCRIPT_BODY", "SCRIPT_SHORT_BODY", 302 | "STYLE_BODY", "STYLE_SHORT_BODY", "ATTVALUE_VALUE", "ATTRIBUTE", 303 | "ATTCHAR", "ATTCHARS", "HEXCHARS", "DECCHARS", "DOUBLE_QUOTE_STRING", 304 | "SINGLE_QUOTE_STRING" ] 305 | 306 | grammarFileName = "HTMLLexer.g4" 307 | 308 | def __init__(self, input=None, output:TextIO = sys.stdout): 309 | super().__init__(input, output) 310 | self.checkVersion("4.8") 311 | self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache()) 312 | self._actions = None 313 | self._predicates = None 314 | 315 | 316 | -------------------------------------------------------------------------------- /vsot/antlr/HTMLLexer.tokens: -------------------------------------------------------------------------------- 1 | TEMPLATE_TAG_OPEN=1 2 | TEMPLATE_VARIABLE_OPEN=2 3 | TEMPLATE_COMMENT_OPEN=3 4 | TEMPLATE_COMMENT_CLOSE=4 5 | HTML_COMMENT=5 6 | HTML_CONDITIONAL_COMMENT=6 7 | XML_DECLARATION=7 8 | CDATA=8 9 | DTD=9 10 | SCRIPTLET=10 11 | SEA_WS=11 12 | SCRIPT_OPEN=12 13 | STYLE_OPEN=13 14 | TAG_OPEN=14 15 | HTML_TEXT=15 16 | TEMPLATE_TAG_CLOSE=16 17 | TEMPLATE_CONTENT=17 18 | TEMPLATE_WS=18 19 | TAG_CLOSE=19 20 | TAG_SLASH_CLOSE=20 21 | TAG_SLASH=21 22 | TAG_EQUALS=22 23 | TAG_NAME=23 24 | TAG_WHITESPACE=24 25 | SCRIPT_BODY=25 26 | SCRIPT_SHORT_BODY=26 27 | STYLE_BODY=27 28 | STYLE_SHORT_BODY=28 29 | ATTVALUE_VALUE=29 30 | ATTRIBUTE=30 31 | '{%'=1 32 | '{{'=2 33 | '{#'=3 34 | '#}'=4 35 | '<'=14 36 | '>'=19 37 | '/>'=20 38 | '/'=21 39 | '='=22 40 | -------------------------------------------------------------------------------- /vsot/antlr/HTMLParser.g4: -------------------------------------------------------------------------------- 1 | /* 2 | [The "BSD licence"] 3 | Copyright (c) 2013 Tom Everett 4 | All rights reserved. 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 3. The name of the author may not be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | parser grammar HTMLParser; 28 | 29 | options { tokenVocab=HTMLLexer; } 30 | 31 | // Template bits 32 | 33 | templateTag 34 | : openTag=TEMPLATE_TAG_OPEN templateContent+ closeTag=TEMPLATE_TAG_CLOSE 35 | ; 36 | 37 | templateComment 38 | : TEMPLATE_COMMENT_OPEN htmlContent TEMPLATE_COMMENT_CLOSE 39 | ; 40 | 41 | templateVariable 42 | : TEMPLATE_VARIABLE_OPEN templateContent+ TEMPLATE_TAG_CLOSE 43 | ; 44 | 45 | templateContent 46 | : TEMPLATE_CONTENT 47 | ; 48 | 49 | 50 | // HTML 51 | 52 | htmlDocument 53 | : (scriptlet | templateTag | templateComment | templateVariable | SEA_WS)* xml? (scriptlet | templateTag | templateComment | templateVariable | SEA_WS)* dtd? (scriptlet | templateTag | templateComment | templateVariable | SEA_WS)* htmlElements* 54 | ; 55 | 56 | htmlElements 57 | : htmlMisc* htmlElement htmlMisc* 58 | ; 59 | 60 | htmlElement 61 | : TAG_OPEN htmlTagName htmlAttribute* TAG_CLOSE htmlContent TAG_OPEN TAG_SLASH htmlTagName TAG_CLOSE # Tag 62 | | TAG_OPEN htmlTagName htmlAttribute* TAG_SLASH_CLOSE # SelfClosingTag 63 | | TAG_OPEN htmlTagName htmlAttribute* TAG_CLOSE # SelfClosingTag 64 | | scriptlet # RawTag 65 | | script # dummy 66 | | style # RawTag 67 | | templateTag # dummy 68 | | templateComment # dummy 69 | | templateVariable # dummy 70 | ; 71 | 72 | htmlContent 73 | : htmlChardata? ((htmlElement | xhtmlCDATA | htmlComment) htmlChardata?)* 74 | ; 75 | 76 | htmlAttribute 77 | : htmlAttributeName TAG_EQUALS htmlAttributeValue 78 | | htmlAttributeName 79 | ; 80 | 81 | htmlAttributeName 82 | : TAG_NAME 83 | ; 84 | 85 | htmlAttributeValue 86 | : ATTVALUE_VALUE 87 | ; 88 | 89 | htmlTagName 90 | : TAG_NAME 91 | ; 92 | 93 | htmlChardata 94 | : HTML_TEXT 95 | | SEA_WS 96 | ; 97 | 98 | htmlMisc 99 | : htmlComment 100 | | SEA_WS 101 | ; 102 | 103 | htmlComment 104 | : HTML_COMMENT 105 | | HTML_CONDITIONAL_COMMENT 106 | ; 107 | 108 | xhtmlCDATA 109 | : CDATA 110 | ; 111 | 112 | dtd 113 | : DTD 114 | ; 115 | 116 | xml 117 | : XML_DECLARATION 118 | ; 119 | 120 | scriptlet 121 | : SCRIPTLET 122 | ; 123 | 124 | script 125 | : SCRIPT_OPEN htmlAttribute* TAG_CLOSE scriptBody 126 | ; 127 | 128 | scriptBody 129 | : SCRIPT_BODY 130 | | SCRIPT_SHORT_BODY 131 | ; 132 | 133 | style 134 | : STYLE_OPEN htmlAttribute* TAG_CLOSE styleBody 135 | ; 136 | 137 | styleBody 138 | : STYLE_BODY 139 | | STYLE_SHORT_BODY 140 | ; 141 | -------------------------------------------------------------------------------- /vsot/antlr/HTMLParser.interp: -------------------------------------------------------------------------------- 1 | token literal names: 2 | null 3 | '{%' 4 | '{{' 5 | '{#' 6 | '#}' 7 | null 8 | null 9 | null 10 | null 11 | null 12 | null 13 | null 14 | null 15 | null 16 | '<' 17 | null 18 | null 19 | null 20 | null 21 | '>' 22 | '/>' 23 | '/' 24 | '=' 25 | null 26 | null 27 | null 28 | null 29 | null 30 | null 31 | null 32 | null 33 | 34 | token symbolic names: 35 | null 36 | TEMPLATE_TAG_OPEN 37 | TEMPLATE_VARIABLE_OPEN 38 | TEMPLATE_COMMENT_OPEN 39 | TEMPLATE_COMMENT_CLOSE 40 | HTML_COMMENT 41 | HTML_CONDITIONAL_COMMENT 42 | XML_DECLARATION 43 | CDATA 44 | DTD 45 | SCRIPTLET 46 | SEA_WS 47 | SCRIPT_OPEN 48 | STYLE_OPEN 49 | TAG_OPEN 50 | HTML_TEXT 51 | TEMPLATE_TAG_CLOSE 52 | TEMPLATE_CONTENT 53 | TEMPLATE_WS 54 | TAG_CLOSE 55 | TAG_SLASH_CLOSE 56 | TAG_SLASH 57 | TAG_EQUALS 58 | TAG_NAME 59 | TAG_WHITESPACE 60 | SCRIPT_BODY 61 | SCRIPT_SHORT_BODY 62 | STYLE_BODY 63 | STYLE_SHORT_BODY 64 | ATTVALUE_VALUE 65 | ATTRIBUTE 66 | 67 | rule names: 68 | templateTag 69 | templateComment 70 | templateVariable 71 | templateContent 72 | htmlDocument 73 | htmlElements 74 | htmlElement 75 | htmlContent 76 | htmlAttribute 77 | htmlAttributeName 78 | htmlAttributeValue 79 | htmlTagName 80 | htmlChardata 81 | htmlMisc 82 | htmlComment 83 | xhtmlCDATA 84 | dtd 85 | xml 86 | scriptlet 87 | script 88 | scriptBody 89 | style 90 | styleBody 91 | 92 | 93 | atn: 94 | [3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 32, 238, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 3, 2, 3, 2, 6, 2, 51, 10, 2, 13, 2, 14, 2, 52, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 6, 4, 63, 10, 4, 13, 4, 14, 4, 64, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 7, 6, 76, 10, 6, 12, 6, 14, 6, 79, 11, 6, 3, 6, 5, 6, 82, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 7, 6, 89, 10, 6, 12, 6, 14, 6, 92, 11, 6, 3, 6, 5, 6, 95, 10, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 7, 6, 102, 10, 6, 12, 6, 14, 6, 105, 11, 6, 3, 6, 7, 6, 108, 10, 6, 12, 6, 14, 6, 111, 11, 6, 3, 7, 7, 7, 114, 10, 7, 12, 7, 14, 7, 117, 11, 7, 3, 7, 3, 7, 7, 7, 121, 10, 7, 12, 7, 14, 7, 124, 11, 7, 3, 8, 3, 8, 3, 8, 7, 8, 129, 10, 8, 12, 8, 14, 8, 132, 11, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 7, 8, 144, 10, 8, 12, 8, 14, 8, 147, 11, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 7, 8, 154, 10, 8, 12, 8, 14, 8, 157, 11, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 5, 8, 167, 10, 8, 3, 9, 5, 9, 170, 10, 9, 3, 9, 3, 9, 3, 9, 5, 9, 175, 10, 9, 3, 9, 5, 9, 178, 10, 9, 7, 9, 180, 10, 9, 12, 9, 14, 9, 183, 11, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 5, 10, 190, 10, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 14, 3, 14, 3, 15, 3, 15, 5, 15, 202, 10, 15, 3, 16, 3, 16, 3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 3, 20, 3, 20, 3, 21, 3, 21, 7, 21, 216, 10, 21, 12, 21, 14, 21, 219, 11, 21, 3, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 7, 23, 228, 10, 23, 12, 23, 14, 23, 231, 11, 23, 3, 23, 3, 23, 3, 23, 3, 24, 3, 24, 3, 24, 2, 2, 25, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 2, 6, 4, 2, 13, 13, 17, 17, 3, 2, 7, 8, 3, 2, 27, 28, 3, 2, 29, 30, 2, 256, 2, 48, 3, 2, 2, 2, 4, 56, 3, 2, 2, 2, 6, 60, 3, 2, 2, 2, 8, 68, 3, 2, 2, 2, 10, 77, 3, 2, 2, 2, 12, 115, 3, 2, 2, 2, 14, 166, 3, 2, 2, 2, 16, 169, 3, 2, 2, 2, 18, 189, 3, 2, 2, 2, 20, 191, 3, 2, 2, 2, 22, 193, 3, 2, 2, 2, 24, 195, 3, 2, 2, 2, 26, 197, 3, 2, 2, 2, 28, 201, 3, 2, 2, 2, 30, 203, 3, 2, 2, 2, 32, 205, 3, 2, 2, 2, 34, 207, 3, 2, 2, 2, 36, 209, 3, 2, 2, 2, 38, 211, 3, 2, 2, 2, 40, 213, 3, 2, 2, 2, 42, 223, 3, 2, 2, 2, 44, 225, 3, 2, 2, 2, 46, 235, 3, 2, 2, 2, 48, 50, 7, 3, 2, 2, 49, 51, 5, 8, 5, 2, 50, 49, 3, 2, 2, 2, 51, 52, 3, 2, 2, 2, 52, 50, 3, 2, 2, 2, 52, 53, 3, 2, 2, 2, 53, 54, 3, 2, 2, 2, 54, 55, 7, 18, 2, 2, 55, 3, 3, 2, 2, 2, 56, 57, 7, 5, 2, 2, 57, 58, 5, 16, 9, 2, 58, 59, 7, 6, 2, 2, 59, 5, 3, 2, 2, 2, 60, 62, 7, 4, 2, 2, 61, 63, 5, 8, 5, 2, 62, 61, 3, 2, 2, 2, 63, 64, 3, 2, 2, 2, 64, 62, 3, 2, 2, 2, 64, 65, 3, 2, 2, 2, 65, 66, 3, 2, 2, 2, 66, 67, 7, 18, 2, 2, 67, 7, 3, 2, 2, 2, 68, 69, 7, 19, 2, 2, 69, 9, 3, 2, 2, 2, 70, 76, 5, 38, 20, 2, 71, 76, 5, 2, 2, 2, 72, 76, 5, 4, 3, 2, 73, 76, 5, 6, 4, 2, 74, 76, 7, 13, 2, 2, 75, 70, 3, 2, 2, 2, 75, 71, 3, 2, 2, 2, 75, 72, 3, 2, 2, 2, 75, 73, 3, 2, 2, 2, 75, 74, 3, 2, 2, 2, 76, 79, 3, 2, 2, 2, 77, 75, 3, 2, 2, 2, 77, 78, 3, 2, 2, 2, 78, 81, 3, 2, 2, 2, 79, 77, 3, 2, 2, 2, 80, 82, 5, 36, 19, 2, 81, 80, 3, 2, 2, 2, 81, 82, 3, 2, 2, 2, 82, 90, 3, 2, 2, 2, 83, 89, 5, 38, 20, 2, 84, 89, 5, 2, 2, 2, 85, 89, 5, 4, 3, 2, 86, 89, 5, 6, 4, 2, 87, 89, 7, 13, 2, 2, 88, 83, 3, 2, 2, 2, 88, 84, 3, 2, 2, 2, 88, 85, 3, 2, 2, 2, 88, 86, 3, 2, 2, 2, 88, 87, 3, 2, 2, 2, 89, 92, 3, 2, 2, 2, 90, 88, 3, 2, 2, 2, 90, 91, 3, 2, 2, 2, 91, 94, 3, 2, 2, 2, 92, 90, 3, 2, 2, 2, 93, 95, 5, 34, 18, 2, 94, 93, 3, 2, 2, 2, 94, 95, 3, 2, 2, 2, 95, 103, 3, 2, 2, 2, 96, 102, 5, 38, 20, 2, 97, 102, 5, 2, 2, 2, 98, 102, 5, 4, 3, 2, 99, 102, 5, 6, 4, 2, 100, 102, 7, 13, 2, 2, 101, 96, 3, 2, 2, 2, 101, 97, 3, 2, 2, 2, 101, 98, 3, 2, 2, 2, 101, 99, 3, 2, 2, 2, 101, 100, 3, 2, 2, 2, 102, 105, 3, 2, 2, 2, 103, 101, 3, 2, 2, 2, 103, 104, 3, 2, 2, 2, 104, 109, 3, 2, 2, 2, 105, 103, 3, 2, 2, 2, 106, 108, 5, 12, 7, 2, 107, 106, 3, 2, 2, 2, 108, 111, 3, 2, 2, 2, 109, 107, 3, 2, 2, 2, 109, 110, 3, 2, 2, 2, 110, 11, 3, 2, 2, 2, 111, 109, 3, 2, 2, 2, 112, 114, 5, 28, 15, 2, 113, 112, 3, 2, 2, 2, 114, 117, 3, 2, 2, 2, 115, 113, 3, 2, 2, 2, 115, 116, 3, 2, 2, 2, 116, 118, 3, 2, 2, 2, 117, 115, 3, 2, 2, 2, 118, 122, 5, 14, 8, 2, 119, 121, 5, 28, 15, 2, 120, 119, 3, 2, 2, 2, 121, 124, 3, 2, 2, 2, 122, 120, 3, 2, 2, 2, 122, 123, 3, 2, 2, 2, 123, 13, 3, 2, 2, 2, 124, 122, 3, 2, 2, 2, 125, 126, 7, 16, 2, 2, 126, 130, 5, 24, 13, 2, 127, 129, 5, 18, 10, 2, 128, 127, 3, 2, 2, 2, 129, 132, 3, 2, 2, 2, 130, 128, 3, 2, 2, 2, 130, 131, 3, 2, 2, 2, 131, 133, 3, 2, 2, 2, 132, 130, 3, 2, 2, 2, 133, 134, 7, 21, 2, 2, 134, 135, 5, 16, 9, 2, 135, 136, 7, 16, 2, 2, 136, 137, 7, 23, 2, 2, 137, 138, 5, 24, 13, 2, 138, 139, 7, 21, 2, 2, 139, 167, 3, 2, 2, 2, 140, 141, 7, 16, 2, 2, 141, 145, 5, 24, 13, 2, 142, 144, 5, 18, 10, 2, 143, 142, 3, 2, 2, 2, 144, 147, 3, 2, 2, 2, 145, 143, 3, 2, 2, 2, 145, 146, 3, 2, 2, 2, 146, 148, 3, 2, 2, 2, 147, 145, 3, 2, 2, 2, 148, 149, 7, 22, 2, 2, 149, 167, 3, 2, 2, 2, 150, 151, 7, 16, 2, 2, 151, 155, 5, 24, 13, 2, 152, 154, 5, 18, 10, 2, 153, 152, 3, 2, 2, 2, 154, 157, 3, 2, 2, 2, 155, 153, 3, 2, 2, 2, 155, 156, 3, 2, 2, 2, 156, 158, 3, 2, 2, 2, 157, 155, 3, 2, 2, 2, 158, 159, 7, 21, 2, 2, 159, 167, 3, 2, 2, 2, 160, 167, 5, 38, 20, 2, 161, 167, 5, 40, 21, 2, 162, 167, 5, 44, 23, 2, 163, 167, 5, 2, 2, 2, 164, 167, 5, 4, 3, 2, 165, 167, 5, 6, 4, 2, 166, 125, 3, 2, 2, 2, 166, 140, 3, 2, 2, 2, 166, 150, 3, 2, 2, 2, 166, 160, 3, 2, 2, 2, 166, 161, 3, 2, 2, 2, 166, 162, 3, 2, 2, 2, 166, 163, 3, 2, 2, 2, 166, 164, 3, 2, 2, 2, 166, 165, 3, 2, 2, 2, 167, 15, 3, 2, 2, 2, 168, 170, 5, 26, 14, 2, 169, 168, 3, 2, 2, 2, 169, 170, 3, 2, 2, 2, 170, 181, 3, 2, 2, 2, 171, 175, 5, 14, 8, 2, 172, 175, 5, 32, 17, 2, 173, 175, 5, 30, 16, 2, 174, 171, 3, 2, 2, 2, 174, 172, 3, 2, 2, 2, 174, 173, 3, 2, 2, 2, 175, 177, 3, 2, 2, 2, 176, 178, 5, 26, 14, 2, 177, 176, 3, 2, 2, 2, 177, 178, 3, 2, 2, 2, 178, 180, 3, 2, 2, 2, 179, 174, 3, 2, 2, 2, 180, 183, 3, 2, 2, 2, 181, 179, 3, 2, 2, 2, 181, 182, 3, 2, 2, 2, 182, 17, 3, 2, 2, 2, 183, 181, 3, 2, 2, 2, 184, 185, 5, 20, 11, 2, 185, 186, 7, 24, 2, 2, 186, 187, 5, 22, 12, 2, 187, 190, 3, 2, 2, 2, 188, 190, 5, 20, 11, 2, 189, 184, 3, 2, 2, 2, 189, 188, 3, 2, 2, 2, 190, 19, 3, 2, 2, 2, 191, 192, 7, 25, 2, 2, 192, 21, 3, 2, 2, 2, 193, 194, 7, 31, 2, 2, 194, 23, 3, 2, 2, 2, 195, 196, 7, 25, 2, 2, 196, 25, 3, 2, 2, 2, 197, 198, 9, 2, 2, 2, 198, 27, 3, 2, 2, 2, 199, 202, 5, 30, 16, 2, 200, 202, 7, 13, 2, 2, 201, 199, 3, 2, 2, 2, 201, 200, 3, 2, 2, 2, 202, 29, 3, 2, 2, 2, 203, 204, 9, 3, 2, 2, 204, 31, 3, 2, 2, 2, 205, 206, 7, 10, 2, 2, 206, 33, 3, 2, 2, 2, 207, 208, 7, 11, 2, 2, 208, 35, 3, 2, 2, 2, 209, 210, 7, 9, 2, 2, 210, 37, 3, 2, 2, 2, 211, 212, 7, 12, 2, 2, 212, 39, 3, 2, 2, 2, 213, 217, 7, 14, 2, 2, 214, 216, 5, 18, 10, 2, 215, 214, 3, 2, 2, 2, 216, 219, 3, 2, 2, 2, 217, 215, 3, 2, 2, 2, 217, 218, 3, 2, 2, 2, 218, 220, 3, 2, 2, 2, 219, 217, 3, 2, 2, 2, 220, 221, 7, 21, 2, 2, 221, 222, 5, 42, 22, 2, 222, 41, 3, 2, 2, 2, 223, 224, 9, 4, 2, 2, 224, 43, 3, 2, 2, 2, 225, 229, 7, 15, 2, 2, 226, 228, 5, 18, 10, 2, 227, 226, 3, 2, 2, 2, 228, 231, 3, 2, 2, 2, 229, 227, 3, 2, 2, 2, 229, 230, 3, 2, 2, 2, 230, 232, 3, 2, 2, 2, 231, 229, 3, 2, 2, 2, 232, 233, 7, 21, 2, 2, 233, 234, 5, 46, 24, 2, 234, 45, 3, 2, 2, 2, 235, 236, 9, 5, 2, 2, 236, 47, 3, 2, 2, 2, 27, 52, 64, 75, 77, 81, 88, 90, 94, 101, 103, 109, 115, 122, 130, 145, 155, 166, 169, 174, 177, 181, 189, 201, 217, 229] -------------------------------------------------------------------------------- /vsot/antlr/HTMLParser.tokens: -------------------------------------------------------------------------------- 1 | TEMPLATE_TAG_OPEN=1 2 | TEMPLATE_VARIABLE_OPEN=2 3 | TEMPLATE_COMMENT_OPEN=3 4 | TEMPLATE_COMMENT_CLOSE=4 5 | HTML_COMMENT=5 6 | HTML_CONDITIONAL_COMMENT=6 7 | XML_DECLARATION=7 8 | CDATA=8 9 | DTD=9 10 | SCRIPTLET=10 11 | SEA_WS=11 12 | SCRIPT_OPEN=12 13 | STYLE_OPEN=13 14 | TAG_OPEN=14 15 | HTML_TEXT=15 16 | TEMPLATE_TAG_CLOSE=16 17 | TEMPLATE_CONTENT=17 18 | TEMPLATE_WS=18 19 | TAG_CLOSE=19 20 | TAG_SLASH_CLOSE=20 21 | TAG_SLASH=21 22 | TAG_EQUALS=22 23 | TAG_NAME=23 24 | TAG_WHITESPACE=24 25 | SCRIPT_BODY=25 26 | SCRIPT_SHORT_BODY=26 27 | STYLE_BODY=27 28 | STYLE_SHORT_BODY=28 29 | ATTVALUE_VALUE=29 30 | ATTRIBUTE=30 31 | '{%'=1 32 | '{{'=2 33 | '{#'=3 34 | '#}'=4 35 | '<'=14 36 | '>'=19 37 | '/>'=20 38 | '/'=21 39 | '='=22 40 | -------------------------------------------------------------------------------- /vsot/antlr/HTMLParserVisitor.py: -------------------------------------------------------------------------------- 1 | # Generated from /vsot/antlr/HTMLParser.g4 by ANTLR 4.8 2 | from antlr4 import * 3 | if __name__ is not None and "." in __name__: 4 | from .HTMLParser import HTMLParser 5 | else: 6 | from HTMLParser import HTMLParser 7 | 8 | # This class defines a complete generic visitor for a parse tree produced by HTMLParser. 9 | 10 | class HTMLParserVisitor(ParseTreeVisitor): 11 | 12 | # Visit a parse tree produced by HTMLParser#templateTag. 13 | def visitTemplateTag(self, ctx:HTMLParser.TemplateTagContext): 14 | return self.visitChildren(ctx) 15 | 16 | 17 | # Visit a parse tree produced by HTMLParser#templateComment. 18 | def visitTemplateComment(self, ctx:HTMLParser.TemplateCommentContext): 19 | return self.visitChildren(ctx) 20 | 21 | 22 | # Visit a parse tree produced by HTMLParser#templateVariable. 23 | def visitTemplateVariable(self, ctx:HTMLParser.TemplateVariableContext): 24 | return self.visitChildren(ctx) 25 | 26 | 27 | # Visit a parse tree produced by HTMLParser#templateContent. 28 | def visitTemplateContent(self, ctx:HTMLParser.TemplateContentContext): 29 | return self.visitChildren(ctx) 30 | 31 | 32 | # Visit a parse tree produced by HTMLParser#htmlDocument. 33 | def visitHtmlDocument(self, ctx:HTMLParser.HtmlDocumentContext): 34 | return self.visitChildren(ctx) 35 | 36 | 37 | # Visit a parse tree produced by HTMLParser#htmlElements. 38 | def visitHtmlElements(self, ctx:HTMLParser.HtmlElementsContext): 39 | return self.visitChildren(ctx) 40 | 41 | 42 | # Visit a parse tree produced by HTMLParser#Tag. 43 | def visitTag(self, ctx:HTMLParser.TagContext): 44 | return self.visitChildren(ctx) 45 | 46 | 47 | # Visit a parse tree produced by HTMLParser#SelfClosingTag. 48 | def visitSelfClosingTag(self, ctx:HTMLParser.SelfClosingTagContext): 49 | return self.visitChildren(ctx) 50 | 51 | 52 | # Visit a parse tree produced by HTMLParser#RawTag. 53 | def visitRawTag(self, ctx:HTMLParser.RawTagContext): 54 | return self.visitChildren(ctx) 55 | 56 | 57 | # Visit a parse tree produced by HTMLParser#dummy. 58 | def visitDummy(self, ctx:HTMLParser.DummyContext): 59 | return self.visitChildren(ctx) 60 | 61 | 62 | # Visit a parse tree produced by HTMLParser#htmlContent. 63 | def visitHtmlContent(self, ctx:HTMLParser.HtmlContentContext): 64 | return self.visitChildren(ctx) 65 | 66 | 67 | # Visit a parse tree produced by HTMLParser#htmlAttribute. 68 | def visitHtmlAttribute(self, ctx:HTMLParser.HtmlAttributeContext): 69 | return self.visitChildren(ctx) 70 | 71 | 72 | # Visit a parse tree produced by HTMLParser#htmlAttributeName. 73 | def visitHtmlAttributeName(self, ctx:HTMLParser.HtmlAttributeNameContext): 74 | return self.visitChildren(ctx) 75 | 76 | 77 | # Visit a parse tree produced by HTMLParser#htmlAttributeValue. 78 | def visitHtmlAttributeValue(self, ctx:HTMLParser.HtmlAttributeValueContext): 79 | return self.visitChildren(ctx) 80 | 81 | 82 | # Visit a parse tree produced by HTMLParser#htmlTagName. 83 | def visitHtmlTagName(self, ctx:HTMLParser.HtmlTagNameContext): 84 | return self.visitChildren(ctx) 85 | 86 | 87 | # Visit a parse tree produced by HTMLParser#htmlChardata. 88 | def visitHtmlChardata(self, ctx:HTMLParser.HtmlChardataContext): 89 | return self.visitChildren(ctx) 90 | 91 | 92 | # Visit a parse tree produced by HTMLParser#htmlMisc. 93 | def visitHtmlMisc(self, ctx:HTMLParser.HtmlMiscContext): 94 | return self.visitChildren(ctx) 95 | 96 | 97 | # Visit a parse tree produced by HTMLParser#htmlComment. 98 | def visitHtmlComment(self, ctx:HTMLParser.HtmlCommentContext): 99 | return self.visitChildren(ctx) 100 | 101 | 102 | # Visit a parse tree produced by HTMLParser#xhtmlCDATA. 103 | def visitXhtmlCDATA(self, ctx:HTMLParser.XhtmlCDATAContext): 104 | return self.visitChildren(ctx) 105 | 106 | 107 | # Visit a parse tree produced by HTMLParser#dtd. 108 | def visitDtd(self, ctx:HTMLParser.DtdContext): 109 | return self.visitChildren(ctx) 110 | 111 | 112 | # Visit a parse tree produced by HTMLParser#xml. 113 | def visitXml(self, ctx:HTMLParser.XmlContext): 114 | return self.visitChildren(ctx) 115 | 116 | 117 | # Visit a parse tree produced by HTMLParser#scriptlet. 118 | def visitScriptlet(self, ctx:HTMLParser.ScriptletContext): 119 | return self.visitChildren(ctx) 120 | 121 | 122 | # Visit a parse tree produced by HTMLParser#script. 123 | def visitScript(self, ctx:HTMLParser.ScriptContext): 124 | return self.visitChildren(ctx) 125 | 126 | 127 | # Visit a parse tree produced by HTMLParser#scriptBody. 128 | def visitScriptBody(self, ctx:HTMLParser.ScriptBodyContext): 129 | return self.visitChildren(ctx) 130 | 131 | 132 | # Visit a parse tree produced by HTMLParser#style. 133 | def visitStyle(self, ctx:HTMLParser.StyleContext): 134 | return self.visitChildren(ctx) 135 | 136 | 137 | # Visit a parse tree produced by HTMLParser#styleBody. 138 | def visitStyleBody(self, ctx:HTMLParser.StyleBodyContext): 139 | return self.visitChildren(ctx) 140 | 141 | 142 | 143 | del HTMLParser -------------------------------------------------------------------------------- /vsot/antlr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhowes/vsot/37d160c4f66178e637952a05dce9f7e34e479a0c/vsot/antlr/__init__.py -------------------------------------------------------------------------------- /vsot/assertions.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | 3 | from antlr4 import ParserRuleContext 4 | 5 | from .parser import parse_string 6 | from .printer import print_to_string 7 | from .utils import diff, dump_to_file 8 | from .settings import Settings 9 | 10 | 11 | def ast_to_str(tree: ParserRuleContext) -> str: 12 | """ Antlr4 "lisp" style tree print for comparison. """ 13 | tree.toStringTree(None, tree.parser) 14 | 15 | 16 | def assert_equivalent(src: str, dst: str) -> None: 17 | """ 18 | The parser should find that the original and the reformatted file 19 | parse to the same tree. 20 | """ 21 | input_ast = parse_string(src) 22 | try: 23 | output_ast = parse_string(dst) 24 | except Exception: 25 | raise AssertionError("VSOT produced invalid markup") 26 | 27 | if ast_to_str(input_ast) != ast_to_str(output_ast): 28 | raise AssertionError( 29 | "VSOT produced output which is not syntactically equivalent to the original" 30 | ) 31 | 32 | 33 | def assert_stable(src: str, dst: str, settings: Settings) -> None: 34 | """ 35 | Reformatting formatted code, should produce identical output 36 | 37 | Largely borrowed from black 38 | """ 39 | new_ast = parse_string(dst) 40 | new_dst = print_to_string(new_ast, settings) 41 | 42 | if dst != new_dst: 43 | log = dump_to_file( 44 | diff(src, dst, "source", "first pass"), 45 | diff(dst, new_dst, "first pass", "second pass"), 46 | ) 47 | raise AssertionError( 48 | f"INTERNAL ERROR: VSOT produced different markup on the second " 49 | f"pass of the formatter. See {log} " 50 | ) 51 | -------------------------------------------------------------------------------- /vsot/cli.py: -------------------------------------------------------------------------------- 1 | """Console script for vsot.""" 2 | from functools import partial 3 | from pathlib import Path 4 | import sys 5 | from typing import Pattern, Optional, Tuple, Callable, Set 6 | import re 7 | 8 | import click 9 | from antlr4 import FileStream, CommonTokenStream 10 | 11 | from . import __version__ 12 | from .antlr.HTMLLexer import HTMLLexer 13 | from .antlr.HTMLParser import HTMLParser 14 | from .settings import ( 15 | Settings, 16 | WriteBack, 17 | read_pyproject_toml, 18 | Changed, 19 | find_project_root, 20 | ) 21 | from . import format_file_in_place, format_string 22 | from .constants import ( 23 | DEFAULT_LINE_LENGTH, 24 | DEFAULT_INDENT_SIZE, 25 | DEFAULT_EXCLUDES, 26 | DEFAULT_INCLUDES, 27 | ) 28 | from .utils import Report, gen_html_files_in_dir, get_gitignore 29 | 30 | 31 | out = partial(click.secho, bold=True, err=True) 32 | err = partial(click.secho, fg="red", err=True) 33 | 34 | 35 | def re_compile_maybe_verbose(regex: str) -> Pattern[str]: 36 | """Compile a regular expression string in `regex`. 37 | If it contains newlines, use verbose mode. 38 | """ 39 | if "\n" in regex: 40 | regex = "(?x)" + regex 41 | compiled: Pattern[str] = re.compile(regex) 42 | return compiled 43 | 44 | 45 | @click.command(context_settings=dict(help_option_names=["-h", "--help"])) 46 | @click.option("-c", "--code", type=str, help="Format the code passed in as a string.") 47 | @click.option( 48 | "-l", 49 | "--line-length", 50 | type=int, 51 | default=DEFAULT_LINE_LENGTH, 52 | help="How many characters per line to allow.", 53 | show_default=True, 54 | ) 55 | @click.option( 56 | "-s", 57 | "--indent-size", 58 | type=int, 59 | default=DEFAULT_INDENT_SIZE, 60 | help="How many spaces to use for each level of indentation.", 61 | show_default=True, 62 | ) 63 | @click.option( 64 | "--check", 65 | is_flag=True, 66 | help=( 67 | "Don't write the files back, just return the status. Return code 0 " 68 | "means nothing would change. Return code 1 means some files would be " 69 | "reformatted. Return code 123 means there was an internal error." 70 | ), 71 | ) 72 | @click.option( 73 | "--diff", 74 | is_flag=True, 75 | help="Don't write the files back, just output a diff for each file on stdout.", 76 | ) 77 | @click.option( 78 | "--include", 79 | type=str, 80 | default=DEFAULT_INCLUDES, 81 | help=( 82 | "A regular expression that matches files and directories that should be " 83 | "included on recursive searches. An empty value means all files are " 84 | "included regardless of the name. Use forward slashes for directories on " 85 | "all platforms (Windows, too). Exclusions are calculated first, inclusions " 86 | "later." 87 | ), 88 | show_default=True, 89 | ) 90 | @click.option( 91 | "--exclude", 92 | type=str, 93 | default=DEFAULT_EXCLUDES, 94 | help=( 95 | "A regular expression that matches files and directories that should be " 96 | "excluded on recursive searches. An empty value means no paths are excluded. " 97 | "Use forward slashes for directories on all platforms (Windows, too). " 98 | "Exclusions are calculated first, inclusions later." 99 | ), 100 | show_default=True, 101 | ) 102 | @click.version_option(version=__version__) 103 | @click.argument( 104 | "src", 105 | nargs=-1, 106 | type=click.Path( 107 | exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True 108 | ), 109 | is_eager=True, 110 | ) 111 | @click.option( 112 | "--config", 113 | type=click.Path( 114 | exists=False, file_okay=True, dir_okay=False, readable=True, allow_dash=False 115 | ), 116 | is_eager=True, 117 | callback=read_pyproject_toml, 118 | help="Read configuration from PATH.", 119 | ) 120 | @click.pass_context 121 | def main( 122 | ctx: click.Context, 123 | code: Optional[str], 124 | line_length: int, 125 | indent_size: int, 126 | check: bool, 127 | diff: bool, 128 | # quiet: bool, 129 | # verbose: bool, 130 | include: str, 131 | exclude: str, 132 | src: Tuple[str, ...], 133 | config: Optional[str], 134 | ) -> None: 135 | quiet = False 136 | verbose = False 137 | 138 | write_back = WriteBack.from_configuration(check=check, diff=diff) 139 | 140 | settings = Settings( 141 | line_length=line_length, indent_size=indent_size, write_back=write_back 142 | ) 143 | 144 | if config and verbose: 145 | out(f"Using configuration from {config}.", bold=False, fg="blue") 146 | if code is not None: 147 | print(format_string(code, settings=settings)) 148 | ctx.exit(0) 149 | 150 | try: 151 | include_regex = re_compile_maybe_verbose(include) 152 | except re.error: 153 | err(f"Invalid regular expression for include given: {include!r}") 154 | ctx.exit(2) 155 | try: 156 | exclude_regex = re_compile_maybe_verbose(exclude) 157 | except re.error: 158 | err(f"Invalid regular expression for exclude given: {exclude!r}") 159 | ctx.exit(2) 160 | 161 | report = Report( 162 | check=check, diff=diff, quiet=quiet, verbose=verbose, out=out, err=err 163 | ) 164 | root = find_project_root(src) 165 | sources: Set[Path] = set() 166 | 167 | if not src: 168 | if verbose or not quiet: 169 | out("No Path provided. Nothing to do 😴") 170 | ctx.exit(0) 171 | 172 | for s in src: 173 | p = Path(s) 174 | if p.is_dir(): 175 | sources.update( 176 | gen_html_files_in_dir( 177 | p, root, include_regex, exclude_regex, report, get_gitignore(root) 178 | ) 179 | ) 180 | elif p.is_file() or s == "-": 181 | # if a file was explicitly given, we don't care about its extension 182 | sources.add(p) 183 | else: 184 | err(f"invalid path: {s}") 185 | if len(sources) == 0: 186 | if verbose or not quiet: 187 | out("No template/html files are present to be formatted. Nothing to do 😴") 188 | ctx.exit(0) 189 | 190 | for source in sources: 191 | reformat_one( 192 | src=source, settings=settings, report=report, 193 | ) 194 | 195 | if verbose or not quiet: 196 | out("Oh no! 💥 💔 💥" if report.return_code else "All done! ✨ 🍰 ✨") 197 | click.secho(str(report), err=True) 198 | ctx.exit(report.return_code) 199 | 200 | 201 | def reformat_one(src, settings, report): 202 | try: 203 | changed = Changed.NO 204 | if format_file_in_place(src, settings): 205 | changed = Changed.YES 206 | report.done(src, changed) 207 | except Exception as exc: 208 | report.failed(src, exc) 209 | raise exc 210 | 211 | 212 | if __name__ == "__main__": 213 | sys.exit(main()) # pragma: no cover 214 | -------------------------------------------------------------------------------- /vsot/constants.py: -------------------------------------------------------------------------------- 1 | TEMPLATE_SCOPE_OPEN_TAGS = ( 2 | "autoescape", 3 | "block", 4 | "comment", 5 | "filter", 6 | "for", 7 | "if", 8 | "ifchanged", 9 | "while", 10 | "with", 11 | # Jinja only 12 | "macro", 13 | "call", 14 | # "set" - this is context dependent, so would need a special case :( 15 | ) 16 | 17 | TEMPLATE_SCOPE_CLOSE_OPEN_TAGS = ("elif", "empty") 18 | 19 | DEFAULT_LINE_LENGTH = 88 20 | DEFAULT_INDENT_SIZE = 2 21 | DEFAULT_EXCLUDES = r"/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_build|buck-out|build|dist)/" # noqa: B950 22 | DEFAULT_INCLUDES = r"/templates/.*?\.html$" 23 | -------------------------------------------------------------------------------- /vsot/exceptions.py: -------------------------------------------------------------------------------- 1 | class NothingChanged(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /vsot/parser.py: -------------------------------------------------------------------------------- 1 | """ This is mostly just a wrapper around the antlr parser/runtime """ 2 | import sys 3 | 4 | import click 5 | from antlr4 import InputStream, FileStream, CommonTokenStream 6 | 7 | from .antlr.HTMLLexer import HTMLLexer 8 | from .antlr.HTMLParser import HTMLParser 9 | 10 | 11 | def parse_string(src: str): 12 | return parse_stream(InputStream(src)) 13 | 14 | 15 | def parse_stream(src: InputStream): 16 | lexer = HTMLLexer(src) 17 | stream = CommonTokenStream(lexer) 18 | parser = HTMLParser(stream) 19 | tree = parser.htmlDocument() 20 | 21 | return tree 22 | -------------------------------------------------------------------------------- /vsot/printer.py: -------------------------------------------------------------------------------- 1 | from functools import partialmethod 2 | from pprint import pformat 3 | from typing import Optional 4 | 5 | from .antlr.HTMLParserVisitor import HTMLParserVisitor 6 | 7 | from .constants import TEMPLATE_SCOPE_OPEN_TAGS, TEMPLATE_SCOPE_CLOSE_OPEN_TAGS 8 | from .settings import Settings 9 | 10 | 11 | class HTMLPrinter(HTMLParserVisitor): 12 | def __init__(self, settings: Settings): 13 | # Settings 14 | self.indent_str = " " * settings.indent_size 15 | self.max_line_len = settings.line_length 16 | # Future settings 17 | self.sort_html_attributes = True 18 | # State 19 | self.indent = 0 20 | self.current_line_len = 0 21 | self.buffer = "" 22 | return super().__init__() 23 | 24 | def visit(self, node): 25 | self.buffer = "" 26 | super().visit(node) 27 | return self.buffer 28 | 29 | def enter_scope(self): 30 | self.indent += 1 31 | 32 | def exit_scope(self): 33 | self.indent -= 1 34 | 35 | def resolve_indent(self): 36 | return self.indent_str * self.indent 37 | 38 | def output(self, content, is_block=True, needs_newline=False): 39 | """ 40 | Output printing either prints a block (content starting and ending with a 41 | newline) or attempts to continue printing on the current line if the 42 | content is narrow enough. 43 | """ 44 | local_buffer = "" 45 | 46 | if needs_newline and self.buffer[-1] != "\n": 47 | local_buffer += "\n" 48 | elif (self.current_line_len and is_block) or ( 49 | self.current_line_len + len(content) > self.max_line_len 50 | ): 51 | # content requires newline, either because it's a block or because 52 | # it would overflow the desired line length. 53 | local_buffer += "\n" 54 | self.current_line_len = 0 55 | 56 | if not self.current_line_len: 57 | local_buffer += f"{self.resolve_indent()}" 58 | else: 59 | local_buffer += " " 60 | 61 | local_buffer += content 62 | 63 | if is_block: 64 | local_buffer += "\n" 65 | self.current_line_len = 0 66 | else: 67 | self.current_line_len += len(content) 68 | 69 | self.buffer += local_buffer 70 | 71 | def _visit_html_attribute(self, ctx): 72 | return ( 73 | f"{ctx.htmlAttributeName().getText()}={ctx.htmlAttributeValue().getText()}" 74 | ) 75 | 76 | def outputOpeningTag(self, ctx, tag_name: Optional[str] = None): 77 | """ 78 | tag_name: If the tag name should not be detected (e.g. for script) 79 | """ 80 | is_self_closing = False 81 | 82 | name = tag_name 83 | 84 | if not name: 85 | # Tags with a closing tag have 2 instances of htmlTagName 86 | name = ctx.htmlTagName() 87 | if isinstance(name, list): 88 | name = name[0].getText() 89 | else: 90 | is_self_closing = True 91 | name = name.getText() 92 | 93 | open_tag_parts = [ 94 | self._visit_html_attribute(attr) for attr in ctx.htmlAttribute() 95 | ] 96 | if self.sort_html_attributes: 97 | # Alphabetise the attributes 98 | open_tag_parts.sort() 99 | 100 | # The tag name 101 | open_tag_parts.insert(0, name) 102 | 103 | join_str = " " 104 | close_str = "/>" if is_self_closing else ">" 105 | 106 | # Detect if attrs need spliting over lines 107 | length = ( 108 | sum(len(part) for part in open_tag_parts) # characters in attrs 109 | + len(open_tag_parts) # spaces between attrs 110 | + len(self.resolve_indent()) # starting indent 111 | ) 112 | if length > self.max_line_len: 113 | self.enter_scope() 114 | join_str = f"\n{self.resolve_indent()}" 115 | self.exit_scope() 116 | close_str = f"\n{self.resolve_indent()}{close_str}" 117 | 118 | self.output(f"<{join_str.join(open_tag_parts)}{close_str}") 119 | 120 | def outputClosingTag(self, ctx, tag_name: Optional[str] = None): 121 | """ 122 | tag_name: If the tag name should not be detected (e.g. for script) 123 | """ 124 | name = tag_name or ctx.htmlTagName()[1].getText() 125 | self.output(f"") 126 | 127 | def visit_lang_block(self, ctx, tag_name="script", content="scriptBody"): 128 | # TODO: format other languages - maybe with jsbeautify 129 | self.outputOpeningTag(ctx, tag_name=tag_name) 130 | self.enter_scope() 131 | for line in ( 132 | getattr(ctx, content)() 133 | .getText() 134 | .replace(f"", "") 135 | .strip() 136 | .split("\n") 137 | ): 138 | self.output(line.strip()) 139 | self.exit_scope() 140 | self.outputClosingTag(ctx, tag_name=tag_name) 141 | 142 | # Visitors - all named to match HTMLParserVisitor 143 | def visitTag(self, ctx): 144 | self.outputOpeningTag(ctx) 145 | self.enter_scope() 146 | self.visitChildren(ctx) 147 | self.exit_scope() 148 | self.outputClosingTag(ctx) 149 | 150 | def visitSelfClosingTag(self, ctx): 151 | self.outputOpeningTag(ctx) 152 | 153 | def _reflow_html_text(self, text): 154 | """ 155 | Reflow text, accounting for the required indent for the current scope. 156 | """ 157 | indent_size = len(self.resolve_indent()) 158 | parts = text.split() 159 | 160 | while parts: 161 | # Width looks at the current line width since html text is not 162 | # printed as a block 163 | if ( 164 | # is contining a line 165 | self.current_line_len 166 | # the first chunk is not too wide 167 | and self.current_line_len + len(parts[0]) <= self.max_line_len 168 | ): 169 | target_width = self.max_line_len - self.current_line_len 170 | else: 171 | target_width = self.max_line_len - indent_size 172 | 173 | content_buffer = "" 174 | current_len = 0 175 | 176 | while current_len < target_width and parts: 177 | # always consume the first chunk of a line, then 178 | # append current chunk if it will fit within the `width` 179 | # or break to yield the line 180 | if current_len and (current_len + len(parts[0]) + 1) > target_width: 181 | break 182 | content_buffer += " " + parts.pop(0) 183 | current_len = len(content_buffer) 184 | 185 | yield content_buffer.strip() 186 | 187 | def visitHtmlChardata(self, ctx): 188 | text = ctx.HTML_TEXT() 189 | if not text: 190 | return 191 | 192 | for line in self._reflow_html_text(text.getText()): 193 | self.output(line, is_block=False) 194 | 195 | def visitRaw(self, ctx): 196 | self.output(ctx.getText()) 197 | 198 | visitHtmlComment = visitRaw 199 | visitXhtmlCDATA = visitRaw 200 | visitDtd = visitRaw 201 | visitXml = visitRaw 202 | visitScriptlet = visitRaw 203 | 204 | visitScript = partialmethod( 205 | visit_lang_block, tag_name="script", content="scriptBody" 206 | ) 207 | visitStyle = partialmethod(visit_lang_block, tag_name="style", content="styleBody") 208 | 209 | def visitTemplateTag(self, ctx): 210 | """ Doesn't currently do much other than normalise whitespace """ 211 | parts = [part.getText() for part in ctx.templateContent()] 212 | command = parts[0] 213 | 214 | # Logic for if this is entering/exiting a scope 215 | # Unmatched commands are assumed to not affect scope 216 | enters = False 217 | if command in TEMPLATE_SCOPE_OPEN_TAGS: 218 | enters = True 219 | 220 | exits = False 221 | if command.startswith("end"): 222 | exits = True 223 | 224 | if command in TEMPLATE_SCOPE_CLOSE_OPEN_TAGS: 225 | exits = True 226 | enters = True 227 | 228 | if exits: 229 | self.exit_scope() 230 | 231 | self.output(f"{ctx.openTag.text} {' '.join(parts)} {ctx.closeTag.text}") 232 | 233 | if enters: 234 | self.enter_scope() 235 | 236 | def visitTemplateComment(self, ctx): 237 | """ In jinja2 these are block comments, in django they are single line """ 238 | unformatted_contents = ctx.getText() 239 | if "\n" in unformatted_contents: # Assume multi-line comment - i.e. jinja2 240 | self.output("{#") 241 | self.enter_scope() 242 | self.visitChildren(ctx) 243 | self.exit_scope() 244 | self.output("#}") 245 | else: 246 | # Single line 247 | self.output(unformatted_contents) 248 | 249 | def visitTemplateVariable(self, ctx): 250 | parts = [part.getText() for part in ctx.templateContent()] 251 | self.output(f"{{{{ {' '.join(parts)} }}}}", is_block=False) 252 | 253 | 254 | def print_to_string(tree, settings): 255 | visitor = HTMLPrinter(settings) 256 | return visitor.visit(tree) 257 | -------------------------------------------------------------------------------- /vsot/settings.py: -------------------------------------------------------------------------------- 1 | """Configuration helpers - mostly from black """ 2 | from dataclasses import dataclass 3 | from enum import Enum 4 | import toml 5 | from typing import Any, Dict, Optional, Union, Iterable 6 | from functools import lru_cache 7 | from pathlib import Path 8 | 9 | import click 10 | 11 | from .constants import DEFAULT_LINE_LENGTH, DEFAULT_INDENT_SIZE 12 | 13 | 14 | class WriteBack(Enum): 15 | NO = 0 16 | YES = 1 17 | DIFF = 2 18 | CHECK = 3 19 | 20 | @classmethod 21 | def from_configuration(cls, *, check: bool, diff: bool) -> "WriteBack": 22 | if check and not diff: 23 | return cls.CHECK 24 | 25 | return cls.DIFF if diff else cls.YES 26 | 27 | 28 | class Changed(Enum): 29 | NO = 0 30 | # CACHED = 1 # Not used yet 31 | YES = 2 32 | 33 | 34 | @dataclass 35 | class Settings: 36 | # Setings with defaults 37 | line_length: int = DEFAULT_LINE_LENGTH 38 | indent_size: int = DEFAULT_INDENT_SIZE 39 | write_back: WriteBack = WriteBack.YES 40 | 41 | 42 | def find_pyproject_toml(path_search_start: str) -> Optional[str]: 43 | """Find the absolute filepath to a pyproject.toml if it exists""" 44 | path_project_root = find_project_root(path_search_start) 45 | path_pyproject_toml = path_project_root / "pyproject.toml" 46 | return str(path_pyproject_toml) if path_pyproject_toml.is_file() else None 47 | 48 | 49 | def parse_pyproject_toml(path_config: str) -> Dict[str, Any]: 50 | """Parse a pyproject toml file, pulling out relevant parts for Black 51 | If parsing fails, will raise a toml.TomlDecodeError 52 | """ 53 | pyproject_toml = toml.load(path_config) 54 | config = pyproject_toml.get("tool", {}).get("vsot", {}) 55 | return {k.replace("--", "").replace("-", "_"): v for k, v in config.items()} 56 | 57 | 58 | def read_pyproject_toml( 59 | ctx: click.Context, param: click.Parameter, value: Union[str, int, bool, None] 60 | ) -> Optional[str]: 61 | """Inject Black configuration from "pyproject.toml" into defaults in `ctx`. 62 | Returns the path to a successfully found and read configuration file, None 63 | otherwise. 64 | """ 65 | assert not isinstance(value, (int, bool)), "Invalid parameter type passed" 66 | if not value: 67 | value = find_pyproject_toml(ctx.params.get("src", ())) 68 | if value is None: 69 | return None 70 | 71 | try: 72 | config = parse_pyproject_toml(value) 73 | except (toml.TomlDecodeError, OSError) as e: 74 | raise click.FileError( 75 | filename=value, hint=f"Error reading configuration file: {e}" 76 | ) 77 | 78 | if not config: 79 | return None 80 | 81 | if ctx.default_map is None: 82 | ctx.default_map = {} 83 | ctx.default_map.update(config) # type: ignore # bad types in .pyi 84 | return value 85 | 86 | 87 | @lru_cache() 88 | def find_project_root(srcs: Iterable[str]) -> Path: 89 | """Return a directory containing .git, .hg, or pyproject.toml. 90 | That directory can be one of the directories passed in `srcs` or their 91 | common parent. 92 | If no directory in the tree contains a marker that would specify it's the 93 | project root, the root of the file system is returned. 94 | """ 95 | if not srcs: 96 | return Path("/").resolve() 97 | 98 | common_base = min(Path(src).resolve() for src in srcs) 99 | if common_base.is_dir(): 100 | # Append a fake file so `parents` below returns `common_base_dir`, too. 101 | common_base /= "fake-file" 102 | for directory in common_base.parents: 103 | if (directory / ".git").exists(): 104 | return directory 105 | 106 | if (directory / ".hg").is_dir(): 107 | return directory 108 | 109 | if (directory / "pyproject.toml").is_file(): 110 | return directory 111 | 112 | return directory 113 | -------------------------------------------------------------------------------- /vsot/utils.py: -------------------------------------------------------------------------------- 1 | import io 2 | import tempfile 3 | import tokenize 4 | from typing import Callable, Tuple, List, Pattern, Iterator 5 | from dataclasses import dataclass 6 | from pathlib import Path 7 | from functools import lru_cache 8 | 9 | import click 10 | from pathspec import PathSpec 11 | 12 | from .settings import Changed 13 | 14 | FileContent = str 15 | Encoding = str 16 | NewLine = str 17 | 18 | 19 | def decode_bytes(src: bytes) -> Tuple[FileContent, Encoding, NewLine]: 20 | """Return a tuple of (decoded_contents, encoding, newline). 21 | `newline` is either CRLF or LF but `decoded_contents` is decoded with 22 | universal newlines (i.e. only contains LF). 23 | """ 24 | srcbuf = io.BytesIO(src) 25 | encoding, lines = tokenize.detect_encoding(srcbuf.readline) 26 | if not lines: 27 | return "", encoding, "\n" 28 | 29 | newline = "\r\n" if b"\r\n" == lines[0][-2:] else "\n" 30 | srcbuf.seek(0) 31 | with io.TextIOWrapper(srcbuf, encoding) as tiow: 32 | return tiow.read(), encoding, newline 33 | 34 | 35 | def diff(a: str, b: str, a_name: str, b_name: str) -> str: 36 | """ 37 | Return a unified diff string between strings `a` and `b`. 38 | 39 | Borrowed from black 40 | """ 41 | import difflib 42 | 43 | a_lines = [line + "\n" for line in a.splitlines()] 44 | b_lines = [line + "\n" for line in b.splitlines()] 45 | return "".join( 46 | difflib.unified_diff(a_lines, b_lines, fromfile=a_name, tofile=b_name, n=5) 47 | ) 48 | 49 | 50 | def dump_to_file(*output: str) -> str: 51 | """ 52 | Dump `output` to a temporary file. Return path to the file. 53 | 54 | borrowed from black 55 | """ 56 | with tempfile.NamedTemporaryFile( 57 | mode="w", prefix="blk_", suffix=".log", delete=False, encoding="utf8" 58 | ) as f: 59 | for lines in output: 60 | f.write(lines) 61 | if lines and lines[-1] != "\n": 62 | f.write("\n") 63 | return f.name 64 | 65 | 66 | @dataclass 67 | class Report: 68 | """Provides a reformatting counter. Can be rendered with `str(report)`.""" 69 | 70 | check: bool = False 71 | diff: bool = False 72 | quiet: bool = False 73 | verbose: bool = False 74 | change_count: int = 0 75 | same_count: int = 0 76 | failure_count: int = 0 77 | 78 | out: Callable[[str], None] = print 79 | err: Callable[[str], None] = print 80 | 81 | def done(self, src: Path, changed: Changed) -> None: 82 | """Increment the counter for successful reformatting. Write out a message.""" 83 | if changed is Changed.YES: 84 | reformatted = "would reformat" if self.check or self.diff else "reformatted" 85 | if self.verbose or not self.quiet: 86 | self.out(f"{reformatted} {src}") 87 | self.change_count += 1 88 | else: 89 | if self.verbose: 90 | if changed is Changed.NO: 91 | msg = f"{src} already well formatted, good job." 92 | else: 93 | msg = f"{src} wasn't modified on disk since last run." 94 | self.out(msg, bold=False) 95 | self.same_count += 1 96 | 97 | def failed(self, src: Path, message: str) -> None: 98 | """Increment the counter for failed reformatting. Write out a message.""" 99 | self.err(f"error: cannot format {src}: {message}") 100 | self.failure_count += 1 101 | 102 | def path_ignored(self, path: Path, message: str) -> None: 103 | if self.verbose: 104 | self.out(f"{path} ignored: {message}", bold=False) 105 | 106 | @property 107 | def return_code(self) -> int: 108 | """Return the exit code that the app should use. 109 | This considers the current state of changed files and failures: 110 | - if there were any failures, return 123; 111 | - if any files were changed and --check is being used, return 1; 112 | - otherwise return 0. 113 | """ 114 | # According to http://tldp.org/LDP/abs/html/exitcodes.html starting with 115 | # 126 we have special return codes reserved by the shell. 116 | if self.failure_count: 117 | return 123 118 | 119 | elif self.change_count and self.check: 120 | return 1 121 | 122 | return 0 123 | 124 | def __str__(self) -> str: 125 | """Render a color report of the current state. 126 | Use `click.unstyle` to remove colors. 127 | """ 128 | if self.check or self.diff: 129 | reformatted = "would be reformatted" 130 | unchanged = "would be left unchanged" 131 | failed = "would fail to reformat" 132 | else: 133 | reformatted = "reformatted" 134 | unchanged = "left unchanged" 135 | failed = "failed to reformat" 136 | report = [] 137 | if self.change_count: 138 | s = "s" if self.change_count > 1 else "" 139 | report.append( 140 | click.style(f"{self.change_count} file{s} {reformatted}", bold=True) 141 | ) 142 | if self.same_count: 143 | s = "s" if self.same_count > 1 else "" 144 | report.append(f"{self.same_count} file{s} {unchanged}") 145 | if self.failure_count: 146 | s = "s" if self.failure_count > 1 else "" 147 | report.append( 148 | click.style(f"{self.failure_count} file{s} {failed}", fg="red") 149 | ) 150 | return ", ".join(report) + "." 151 | 152 | 153 | @lru_cache() 154 | def get_gitignore(root: Path) -> PathSpec: 155 | """ Return a PathSpec matching gitignore content if present.""" 156 | gitignore = root / ".gitignore" 157 | lines: List[str] = [] 158 | if gitignore.is_file(): 159 | with gitignore.open() as gf: 160 | lines = gf.readlines() 161 | return PathSpec.from_lines("gitwildmatch", lines) 162 | 163 | 164 | def gen_html_files_in_dir( 165 | path: Path, 166 | root: Path, 167 | include: Pattern[str], 168 | exclude: Pattern[str], 169 | report: "Report", 170 | gitignore: PathSpec, 171 | ) -> Iterator[Path]: 172 | """Generate all files under `path` whose paths are not excluded by the 173 | `exclude` regex, but are included by the `include` regex. 174 | Symbolic links pointing outside of the `root` directory are ignored. 175 | `report` is where output about exclusions goes. 176 | """ 177 | assert root.is_absolute(), f"INTERNAL ERROR: `root` must be absolute but is {root}" 178 | for child in path.iterdir(): 179 | # First ignore files matching .gitignore 180 | if gitignore.match_file(child.as_posix()): 181 | report.path_ignored(child, "matches the .gitignore file content") 182 | continue 183 | 184 | # Then ignore with `exclude` option. 185 | try: 186 | normalized_path = "/" + child.resolve().relative_to(root).as_posix() 187 | except OSError as e: 188 | report.path_ignored(child, f"cannot be read because {e}") 189 | continue 190 | 191 | except ValueError: 192 | if child.is_symlink(): 193 | report.path_ignored( 194 | child, f"is a symbolic link that points outside {root}" 195 | ) 196 | continue 197 | 198 | raise 199 | 200 | if child.is_dir(): 201 | normalized_path += "/" 202 | 203 | exclude_match = exclude.search(normalized_path) 204 | if exclude_match and exclude_match.group(0): 205 | report.path_ignored(child, "matches the --exclude regular expression") 206 | continue 207 | 208 | if child.is_dir(): 209 | yield from gen_html_files_in_dir( 210 | child, root, include, exclude, report, gitignore 211 | ) 212 | 213 | elif child.is_file(): 214 | include_match = include.search(normalized_path) 215 | if include_match: 216 | yield child 217 | --------------------------------------------------------------------------------