├── Dockerfile
├── .dockerignore
├── .flake8
├── docs
└── example-ninja_build_1.8.2.png
├── .pre-commit-config.yaml
├── .travis.yml
├── pyproject.toml
├── LICENSE
├── README.md
├── .gitignore
├── ninjavis.py
└── poetry.lock
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.7
2 | RUN pip install ninjavis
3 | ENTRYPOINT ["ninjavis"]
4 |
5 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | docs/
2 | .gitignore
3 | .travis.yml
4 | Dockerfile
5 | LICENSE
6 | README.md
7 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 120
3 | max-complexity = 10
4 | select = B,C,E,F,W,T4,B9
5 |
6 |
--------------------------------------------------------------------------------
/docs/example-ninja_build_1.8.2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chagui/ninjavis/HEAD/docs/example-ninja_build_1.8.2.png
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v6.0.0
4 | hooks:
5 | - id: check-ast
6 | - id: check-docstring-first
7 | - id: check-merge-conflict
8 | - id: check-toml
9 | - id: check-yaml
10 | - id: fix-encoding-pragma
11 | - id: mixed-line-ending
12 | - id: trailing-whitespace
13 |
14 | - repo: https://github.com/ambv/black
15 | rev: 25.1.0
16 | hooks:
17 | - id: black
18 |
19 |
20 | - repo: https://github.com/pycqa/isort
21 | rev: 6.0.1
22 | hooks:
23 | - id: isort
24 | name: isort
25 |
26 | - repo: https://github.com/pycqa/flake8
27 | rev: 7.3.0
28 | hooks:
29 | - id: flake8
30 | additional_dependencies: [flake8-bugbear, flake8-comprehensions]
31 |
32 | - repo: https://github.com/pre-commit/mirrors-mypy
33 | rev: v1.17.1
34 | hooks:
35 | - id: mypy
36 |
37 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: xenial
2 | language: python
3 | python:
4 | - '3.6'
5 | before_install:
6 | - pip install poetry
7 | install:
8 | - poetry install -v
9 | script:
10 | - poetry run pylint ninjavis.py
11 | - poetry run mypy ninjavis.py
12 | deploy:
13 | provider: script
14 | script: poetry publish --build
15 | skip_existing: true
16 | on:
17 | tags: true
18 | branch: main
19 | env:
20 | global:
21 | - secure: mYX813z6kURtqP6DrR19khqkFeqxcXz8o7jqCS5Weecw2VuHdlSO4P0QGJD3KwqDXsOtx4TftE2D1cevcTu4UrZijJVmFZKZOYGxuYyCzjy2E4CfXw5HsQmahGDmpud4hPZlMLCK9lgT1Bif7YEGG7w0i/4YhBUYNyLUageKTTTpuCNgcSx+IfczDqoDNSLgmrYcnm/c6VFVa8M9AEpY5MAvHZILrz/JPY/+h68O2G8mkDFW1DNBth9Sfh/214TSUyscivl8IB5mo6YHYB0U7f4nmR3zY0YlWYL5Rls7nPfN6jyBYYowiICm1OSGVtcUlQyzy4hFMfqMUkLPrh+E7aSxOgze0dW0UC8WbPUFb6gSRSAjuKIwY8gpECouGWvdBl8Mp7+9aZtN8OVT3jAg+FpxL6VyqNgPahhW8KuAFxn3qbKr4Jwp0oHx3s+RTEnQKbSLB2qg3OErS1uCOaSM+T5i5tVFJ7+IwbWLYjgmkJpL0NybRTopj+g/Plf4UjyJpFVDiyzpFCl/TDV1ofLxi5SIvfbyc5EiMoBNhyhIyIwvxHJgvrDbvAuK2p5lINax28c0LJoFAkqfDr7PokaSLy93Bnl/plUlN3a7PCAMVZCHEKX3rmsnGCR4yt1tncrFiRqeDaFXGRoN0g+GGkbBW34ZwGQoOIuidJ2cjiLhYic=
22 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "ninjavis"
3 | version = "0.2.1"
4 | description = "Generate visualization from Ninja build logs."
5 | authors = ["Guilhem Charles"]
6 | readme = "README.md"
7 | license = "MIT"
8 | homepage = "https://github.com/chagui/ninjavis"
9 | repository = "https://github.com/chagui/ninjavis"
10 |
11 | [tool.poetry.dependencies]
12 | python = "^3.12"
13 |
14 | [tool.poetry.dev-dependencies]
15 | pylint = "^3.1"
16 | mypy = "^1.10"
17 | black = "^24.4"
18 | isort = "^5.13"
19 | flake8 = "7.0"
20 | flake8-bugbear = "^24.4"
21 | flake8-comprehensions = "^3.10.0"
22 |
23 | [tool.poetry.scripts]
24 | ninjavis = "ninjavis:main"
25 |
26 | [build-system]
27 | requires = ["poetry-core>=1.0.0"]
28 | build-backend = "poetry.core.masonry.api"
29 |
30 | [tool.black]
31 | line-length = 120
32 | target-version = ["py37", "py38", "py39", "py310"]
33 | include = '\.pyi?$'
34 |
35 | [tool.isort]
36 | balanced_wrapping = true
37 | combine_as_imports = true
38 | indent = " "
39 | lexicographical = true
40 | multi_line_output = 3
41 | order_by_type = false
42 | profile = "black"
43 |
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Guilhem C.
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ninjavis #
2 | [](https://travis-ci.org/chagui/ninjavis)
3 | [](https://pypi.python.org/pypi/ninjavis)
4 | [](https://pythonwheels.com/)
5 | [](https://pypi.python.org/pypi/ninjavis)
6 |
7 |
8 | ## Introduction ##
9 | Generate visualization from [Ninja](https://github.com/ninja-build/ninja) build logs. Ninjavis parse the ninja build
10 | logs and for each item of the build extract its target, starting and end time.
11 | It output those information in a template containing a simple timeline ; the visualization is done by [vis.js](http://visjs.org/).
12 |
13 | Inspired by [buildbloat](https://github.com/nico/buildbloat).
14 |
15 | ## Usage ##
16 | ```bash
17 | usage: ninjavis --title "my build" ninja_build.log build_profile.html
18 | ```
19 | :warning: Run ``ninja -t recompact`` first to make sure that no duplicate entries are in the build log.
20 |
21 | ## Example ##
22 | Profile of Ninja 1.8.2 build
23 | 
24 |
25 |
--------------------------------------------------------------------------------
/.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 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
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 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
--------------------------------------------------------------------------------
/ninjavis.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # :copyright: (c) 2019-2020 Guilhem Charles. All rights reserved.
4 | """Generate visualization of a ninja build from its logs."""
5 | import argparse
6 | import re
7 | import sys
8 | from os.path import getmtime
9 | from typing import List, Optional
10 |
11 | TIMELINE = """
12 |
13 |
14 |
15 | {title}
16 |
17 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
41 |
42 |
43 | """
44 |
45 |
46 | def generate_build_profile(logfile: str, time_offset: int) -> List[dict]:
47 | """
48 | Parse a ninja build log file and generates a profile. A profile consist of the list of item
49 | part of the build.
50 |
51 | :param logfile: Path to the build log file.
52 | :param time_offset: Start time of the visualization.
53 | :return: Profile of the build.
54 | """
55 |
56 | def parse_build_entry(line: str) -> Optional[dict]:
57 | try:
58 | # ignore comments
59 | if line[:1] != "#":
60 | start_time, end_time, _, command, _ = line.split()
61 | return {
62 | "content": command,
63 | "start": int(start_time) + time_offset,
64 | "end": int(end_time) + time_offset,
65 | }
66 | except ValueError:
67 | print(f"error: could not parse {line}", file=sys.stderr)
68 | return None
69 |
70 | profile = []
71 | with open(logfile, "r") as build_log:
72 | # first line might be a header specifying ninja build log version
73 | header = build_log.readline()
74 | log_version = re.search(r"# ninja log v(\d+)", header)
75 | if log_version:
76 | parsed_version = log_version.group(1)
77 | if int(parsed_version) != 5:
78 | raise RuntimeError(f"unsupported log file version: {parsed_version}")
79 | else:
80 | # header is a log entry
81 | parsed_project = parse_build_entry(header)
82 | if parsed_project:
83 | profile = [parsed_project]
84 | # handle remaining lines, filter out entries that could not be parsed
85 | profile.extend(filter(None, (parse_build_entry(line) for line in build_log)))
86 | return profile
87 |
88 |
89 | def generate_timeline_from(profile: List[dict], output: str, title: str):
90 | """
91 | Generate a visjs timeline from the ninja build profile.
92 |
93 | :param profile: Ninja build information.
94 | :param output: File to output the visualization.
95 | :param title: Title of the visualization.
96 | :return:
97 | """
98 | try:
99 | with open(output, "w") as visualization:
100 | visualization.write(TIMELINE.format(title=title, dataset=profile))
101 | except RuntimeError as exc:
102 | print(f"error: could not generate timeline: {exc}", file=sys.stderr)
103 | sys.exit(1)
104 |
105 |
106 | def get_argparser() -> argparse.ArgumentParser:
107 | """
108 | ninjavis arguments parser.
109 |
110 | :return: Arguments parser.
111 | """
112 | parser = argparse.ArgumentParser(
113 | prog="ninjavis",
114 | description="Parse ninja build log file and " "generates a timeline of the build",
115 | )
116 | parser.add_argument("logfile", help="Ninja build log (.ninja_log)")
117 | parser.add_argument("output", help="Output file for the visualization")
118 | parser.add_argument("--title", help="Visualization title", default="Ninja build")
119 | return parser
120 |
121 |
122 | def main():
123 | """
124 | Parse the arguments and try to generate a visualization out of the provided build log.
125 |
126 | :return:
127 | """
128 | args = get_argparser().parse_args(sys.argv[1:])
129 |
130 | try:
131 | profile = generate_build_profile(args.logfile, int(getmtime(args.logfile)))
132 | generate_timeline_from(profile, args.output, args.title)
133 | except (RuntimeError, FileNotFoundError) as err:
134 | print(err, file=sys.stderr)
135 | sys.exit(1)
136 | sys.exit(0)
137 |
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
2 |
3 | [[package]]
4 | name = "astroid"
5 | version = "3.1.0"
6 | description = "An abstract syntax tree for Python with inference support."
7 | optional = false
8 | python-versions = ">=3.8.0"
9 | files = [
10 | {file = "astroid-3.1.0-py3-none-any.whl", hash = "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819"},
11 | {file = "astroid-3.1.0.tar.gz", hash = "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"},
12 | ]
13 |
14 | [[package]]
15 | name = "attrs"
16 | version = "23.2.0"
17 | description = "Classes Without Boilerplate"
18 | optional = false
19 | python-versions = ">=3.7"
20 | files = [
21 | {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"},
22 | {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
23 | ]
24 |
25 | [package.extras]
26 | cov = ["attrs[tests]", "coverage[toml] (>=5.3)"]
27 | dev = ["attrs[tests]", "pre-commit"]
28 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"]
29 | tests = ["attrs[tests-no-zope]", "zope-interface"]
30 | tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"]
31 | tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"]
32 |
33 | [[package]]
34 | name = "black"
35 | version = "24.4.1"
36 | description = "The uncompromising code formatter."
37 | optional = false
38 | python-versions = ">=3.8"
39 | files = [
40 | {file = "black-24.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f7749fd0d97ff9415975a1432fac7df89bf13c3833cea079e55fa004d5f28c0"},
41 | {file = "black-24.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859f3cc5d2051adadf8fd504a01e02b0fd866d7549fff54bc9202d524d2e8bd7"},
42 | {file = "black-24.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59271c9c29dfa97f7fda51f56c7809b3f78e72fd8d2205189bbd23022a0618b6"},
43 | {file = "black-24.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:5ed9c34cba223149b5a0144951a0f33d65507cf82c5449cb3c35fe4b515fea9a"},
44 | {file = "black-24.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dae3ae59d6f2dc93700fd5034a3115434686e66fd6e63d4dcaa48d19880f2b0"},
45 | {file = "black-24.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5f8698974a81af83283eb47644f2711b5261138d6d9180c863fce673cbe04b13"},
46 | {file = "black-24.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f404b6e77043b23d0321fb7772522b876b6de737ad3cb97d6b156638d68ce81"},
47 | {file = "black-24.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:c94e52b766477bdcd010b872ba0714d5458536dc9d0734eff6583ba7266ffd89"},
48 | {file = "black-24.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:962d9e953872cdb83b97bb737ad47244ce2938054dc946685a4cad98520dab38"},
49 | {file = "black-24.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d8e3b2486b7dd522b1ab2ba1ec4907f0aa8f5e10a33c4271fb331d1d10b70c"},
50 | {file = "black-24.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed77e214b785148f57e43ca425b6e0850165144aa727d66ac604e56a70bb7825"},
51 | {file = "black-24.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:4ef4764437d7eba8386689cd06e1fb5341ee0ae2e9e22582b21178782de7ed94"},
52 | {file = "black-24.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:92b183f8eef5baf7b20a513abcf982ad616f544f593f6688bb2850d2982911f1"},
53 | {file = "black-24.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:945abd7b3572add997757c94295bb3e73c6ffaf3366b1f26cb2356a4bffd1dc3"},
54 | {file = "black-24.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db5154b9e5b478031371d8bc41ff37b33855fa223a6cfba456c9b73fb96f77d4"},
55 | {file = "black-24.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:afc84c33c1a9aaf3d73140cee776b4ddf73ff429ffe6b7c56dc1c9c10725856d"},
56 | {file = "black-24.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0889f4eb8b3bdf8b189e41a71cf0dbb8141a98346cd1a2695dea5995d416e940"},
57 | {file = "black-24.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5bb0143f175db45a55227eefd63e90849d96c266330ba31719e9667d0d5ec3b9"},
58 | {file = "black-24.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:713a04a78e78f28ef7e8df7a16fe075670ea164860fcef3885e4f3dffc0184b3"},
59 | {file = "black-24.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:171959bc879637a8cdbc53dc3fddae2a83e151937a28cf605fd175ce61e0e94a"},
60 | {file = "black-24.4.1-py3-none-any.whl", hash = "sha256:ecbab810604fe02c70b3a08afd39beb599f7cc9afd13e81f5336014133b4fe35"},
61 | {file = "black-24.4.1.tar.gz", hash = "sha256:5241612dc8cad5b6fd47432b8bd04db80e07cfbc53bb69e9ae18985063bcb8dd"},
62 | ]
63 |
64 | [package.dependencies]
65 | click = ">=8.0.0"
66 | mypy-extensions = ">=0.4.3"
67 | packaging = ">=22.0"
68 | pathspec = ">=0.9.0"
69 | platformdirs = ">=2"
70 |
71 | [package.extras]
72 | colorama = ["colorama (>=0.4.3)"]
73 | d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
74 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
75 | uvloop = ["uvloop (>=0.15.2)"]
76 |
77 | [[package]]
78 | name = "click"
79 | version = "8.1.7"
80 | description = "Composable command line interface toolkit"
81 | optional = false
82 | python-versions = ">=3.7"
83 | files = [
84 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
85 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
86 | ]
87 |
88 | [package.dependencies]
89 | colorama = {version = "*", markers = "platform_system == \"Windows\""}
90 |
91 | [[package]]
92 | name = "colorama"
93 | version = "0.4.6"
94 | description = "Cross-platform colored terminal text."
95 | optional = false
96 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
97 | files = [
98 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
99 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
100 | ]
101 |
102 | [[package]]
103 | name = "dill"
104 | version = "0.3.8"
105 | description = "serialize all of Python"
106 | optional = false
107 | python-versions = ">=3.8"
108 | files = [
109 | {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"},
110 | {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"},
111 | ]
112 |
113 | [package.extras]
114 | graph = ["objgraph (>=1.7.2)"]
115 | profile = ["gprof2dot (>=2022.7.29)"]
116 |
117 | [[package]]
118 | name = "flake8"
119 | version = "7.0.0"
120 | description = "the modular source code checker: pep8 pyflakes and co"
121 | optional = false
122 | python-versions = ">=3.8.1"
123 | files = [
124 | {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"},
125 | {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"},
126 | ]
127 |
128 | [package.dependencies]
129 | mccabe = ">=0.7.0,<0.8.0"
130 | pycodestyle = ">=2.11.0,<2.12.0"
131 | pyflakes = ">=3.2.0,<3.3.0"
132 |
133 | [[package]]
134 | name = "flake8-bugbear"
135 | version = "24.4.21"
136 | description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle."
137 | optional = false
138 | python-versions = ">=3.8.1"
139 | files = [
140 | {file = "flake8_bugbear-24.4.21-py3-none-any.whl", hash = "sha256:58581060a1650f4b11344795db8a4934867d4450486319ece86d7720a9414036"},
141 | {file = "flake8_bugbear-24.4.21.tar.gz", hash = "sha256:d1a87b8f6ca1ed28772c36515f751ea3709e041d78bca60590a570b9cb802e55"},
142 | ]
143 |
144 | [package.dependencies]
145 | attrs = ">=19.2.0"
146 | flake8 = ">=6.0.0"
147 |
148 | [package.extras]
149 | dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "pytest", "tox"]
150 |
151 | [[package]]
152 | name = "flake8-comprehensions"
153 | version = "3.14.0"
154 | description = "A flake8 plugin to help you write better list/set/dict comprehensions."
155 | optional = false
156 | python-versions = ">=3.8"
157 | files = [
158 | {file = "flake8_comprehensions-3.14.0-py3-none-any.whl", hash = "sha256:7b9d07d94aa88e62099a6d1931ddf16c344d4157deedf90fe0d8ee2846f30e97"},
159 | {file = "flake8_comprehensions-3.14.0.tar.gz", hash = "sha256:81768c61bfc064e1a06222df08a2580d97de10cb388694becaf987c331c6c0cf"},
160 | ]
161 |
162 | [package.dependencies]
163 | flake8 = ">=3.0,<3.2.0 || >3.2.0"
164 |
165 | [[package]]
166 | name = "isort"
167 | version = "5.13.2"
168 | description = "A Python utility / library to sort Python imports."
169 | optional = false
170 | python-versions = ">=3.8.0"
171 | files = [
172 | {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
173 | {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
174 | ]
175 |
176 | [package.extras]
177 | colors = ["colorama (>=0.4.6)"]
178 |
179 | [[package]]
180 | name = "mccabe"
181 | version = "0.7.0"
182 | description = "McCabe checker, plugin for flake8"
183 | optional = false
184 | python-versions = ">=3.6"
185 | files = [
186 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
187 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
188 | ]
189 |
190 | [[package]]
191 | name = "mypy"
192 | version = "1.10.0"
193 | description = "Optional static typing for Python"
194 | optional = false
195 | python-versions = ">=3.8"
196 | files = [
197 | {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"},
198 | {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"},
199 | {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"},
200 | {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"},
201 | {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"},
202 | {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"},
203 | {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"},
204 | {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"},
205 | {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"},
206 | {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"},
207 | {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"},
208 | {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"},
209 | {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"},
210 | {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"},
211 | {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"},
212 | {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"},
213 | {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"},
214 | {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"},
215 | {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"},
216 | {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"},
217 | {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"},
218 | {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"},
219 | {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"},
220 | {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"},
221 | {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"},
222 | {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"},
223 | {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"},
224 | ]
225 |
226 | [package.dependencies]
227 | mypy-extensions = ">=1.0.0"
228 | typing-extensions = ">=4.1.0"
229 |
230 | [package.extras]
231 | dmypy = ["psutil (>=4.0)"]
232 | install-types = ["pip"]
233 | mypyc = ["setuptools (>=50)"]
234 | reports = ["lxml"]
235 |
236 | [[package]]
237 | name = "mypy-extensions"
238 | version = "1.0.0"
239 | description = "Type system extensions for programs checked with the mypy type checker."
240 | optional = false
241 | python-versions = ">=3.5"
242 | files = [
243 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
244 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
245 | ]
246 |
247 | [[package]]
248 | name = "packaging"
249 | version = "24.0"
250 | description = "Core utilities for Python packages"
251 | optional = false
252 | python-versions = ">=3.7"
253 | files = [
254 | {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
255 | {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
256 | ]
257 |
258 | [[package]]
259 | name = "pathspec"
260 | version = "0.12.1"
261 | description = "Utility library for gitignore style pattern matching of file paths."
262 | optional = false
263 | python-versions = ">=3.8"
264 | files = [
265 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
266 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
267 | ]
268 |
269 | [[package]]
270 | name = "platformdirs"
271 | version = "4.2.1"
272 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
273 | optional = false
274 | python-versions = ">=3.8"
275 | files = [
276 | {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"},
277 | {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"},
278 | ]
279 |
280 | [package.extras]
281 | docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
282 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
283 | type = ["mypy (>=1.8)"]
284 |
285 | [[package]]
286 | name = "pycodestyle"
287 | version = "2.11.1"
288 | description = "Python style guide checker"
289 | optional = false
290 | python-versions = ">=3.8"
291 | files = [
292 | {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"},
293 | {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"},
294 | ]
295 |
296 | [[package]]
297 | name = "pyflakes"
298 | version = "3.2.0"
299 | description = "passive checker of Python programs"
300 | optional = false
301 | python-versions = ">=3.8"
302 | files = [
303 | {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"},
304 | {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
305 | ]
306 |
307 | [[package]]
308 | name = "pylint"
309 | version = "3.1.0"
310 | description = "python code static checker"
311 | optional = false
312 | python-versions = ">=3.8.0"
313 | files = [
314 | {file = "pylint-3.1.0-py3-none-any.whl", hash = "sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74"},
315 | {file = "pylint-3.1.0.tar.gz", hash = "sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"},
316 | ]
317 |
318 | [package.dependencies]
319 | astroid = ">=3.1.0,<=3.2.0-dev0"
320 | colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
321 | dill = {version = ">=0.3.7", markers = "python_version >= \"3.12\""}
322 | isort = ">=4.2.5,<5.13.0 || >5.13.0,<6"
323 | mccabe = ">=0.6,<0.8"
324 | platformdirs = ">=2.2.0"
325 | tomlkit = ">=0.10.1"
326 |
327 | [package.extras]
328 | spelling = ["pyenchant (>=3.2,<4.0)"]
329 | testutils = ["gitpython (>3)"]
330 |
331 | [[package]]
332 | name = "tomlkit"
333 | version = "0.12.4"
334 | description = "Style preserving TOML library"
335 | optional = false
336 | python-versions = ">=3.7"
337 | files = [
338 | {file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"},
339 | {file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"},
340 | ]
341 |
342 | [[package]]
343 | name = "typing-extensions"
344 | version = "4.11.0"
345 | description = "Backported and Experimental Type Hints for Python 3.8+"
346 | optional = false
347 | python-versions = ">=3.8"
348 | files = [
349 | {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
350 | {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
351 | ]
352 |
353 | [metadata]
354 | lock-version = "2.0"
355 | python-versions = "^3.12"
356 | content-hash = "0911f1698e7609a2426ccb9d0fa5d68c934498c22e3777710519c1d4539571cd"
357 |
--------------------------------------------------------------------------------