diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..89635cc --- /dev/null +++ b/.flake8 @@ -0,0 +1,13 @@ +[flake8] +max-line-length = 120 +max-complexity = 10 +select = C,E,F,W,B,B950 +ignore = E203,E501,W503,E722,B001,C901 +exclude = + .git, + test_*, + __pycache__, + *.egg-info, + .nox, + .pytest_cache, + .mypy_cache diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..fcca4ea --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,31 @@ +name-template: 'v$RESOLVED_VERSION 🌈' +tag-template: 'v$RESOLVED_VERSION' +categories: + - title: '🚀 Features' + labels: + - 'feature' + - 'enhancement' + - title: '🐛 Bug Fixes' + labels: + - 'fix' + - 'bugfix' + - 'bug' + - title: '🧰 Maintenance' + label: 'chore' +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'minor' + patch: + labels: + - 'patch' + default: patch +template: | + ## Changes + + $CHANGES diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 0000000..0db7e2c --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,43 @@ +name: Publish Pypi +on: + release: + types: [ published ] + +jobs: + pytest: + name: Publish to PyPi + runs-on: ubuntu-latest + env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: true + steps: + - uses: actions/checkout@master + - name: Set up Python 3.8 + uses: actions/setup-python@v1 + with: + python-version: 3.8 + + - name: Install Poetry + uses: dschep/install-poetry-action@v1.3 + + - name: Cache Poetry virtualenv + uses: actions/cache@v1 + id: cache + with: + path: ~/.virtualenvs + key: poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + poetry-${{ hashFiles('**/poetry.lock') }} + + - name: Set Poetry config + run: | + poetry config virtualenvs.in-project false + poetry config virtualenvs.path ~/.virtualenvs + + - name: Install Dependencies + run: poetry install + if: steps.cache.outputs.cache-hit != 'true' + + - name: Publish to PyPI + if: github.event_name == 'release' + run: | + poetry publish -u __token__ -p ${{ secrets.PYPI_TOKEN }} --build diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 0000000..ce6fffd --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,32 @@ +name: Release Drafter + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - main + # pull_request event is required only for autolabeler + pull_request: + # Only following types are handled by the action, but one can default to all as well + types: [opened, reopened, synchronize] + # pull_request_target event is required for autolabeler to support PRs from forks + # pull_request_target: + # types: [opened, reopened, synchronize] + +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + # (Optional) GitHub Enterprise requires GHE_HOST variable set + #- name: Set GHE_HOST + # run: | + # echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV + + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@v5 + # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml + # with: + # config-name: my-config.yml + # disable-autolabeler: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml new file mode 100644 index 0000000..80f104c --- /dev/null +++ b/.github/workflows/tox.yml @@ -0,0 +1,45 @@ +name: Run Tests using tox +on: + pull_request: + push: + branches: + - master + +jobs: + pytest: + strategy: + matrix: + python-version: [ 3.8, 3.9 ] + os: [ ubuntu-latest ] + env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: true + USING_COVERAGE: "3.9" + + runs-on: ${{ matrix.os }} + name: os ${{ matrix.os }} python ${{ matrix.python-version }} Linting, testing, and compliance + steps: + - uses: actions/checkout@master + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + + - uses: docker-practice/actions-setup-docker@master + + - name: Install Poetry + uses: dschep/install-poetry-action@v1.3 + + - name: Install Tox + run: | + pip3 install black coverage flake8 tox tox-docker tox-poetry + + - name: Run tox + run: | + tox + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + if: contains(env.USING_COVERAGE, matrix.python-version) + with: + fail_ci_if_error: true diff --git a/.gitignore b/.gitignore index b6e4761..fa181fd 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ + +# ide +.idea diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..e69de29 diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..62ed4c8 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,796 @@ +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "black" +version = "22.3.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2021.10.8" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.1.2" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "coverage" +version = "6.3.2" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "distlib" +version = "0.3.4" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "filelock" +version = "3.6.0" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] + +[[package]] +name = "flake8" +version = "4.0.1" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.8.0,<2.9.0" +pyflakes = ">=2.4.0,<2.5.0" + +[[package]] +name = "gitdb" +version = "4.0.9" +description = "Git Object Database" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "gitpython" +version = "3.1.27" +description = "GitPython is a python library used to interact with Git repositories" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +gitdb = ">=4.0.1,<5" + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "importlib-resources" +version = "5.6.0" +description = "Read resources from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "jsonschema" +version = "4.4.0" +description = "An implementation of JSON Schema validation for Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=17.4.0" +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format_nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "more-itertools" +version = "8.12.0" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "numpy" +version = "1.22.3" +description = "NumPy is the fundamental package for array computing with Python." +category = "main" +optional = false +python-versions = ">=3.8" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pandas" +version = "1.4.2" +description = "Powerful data structures for data analysis, time series, and statistics" +category = "main" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +numpy = [ + {version = ">=1.18.5", markers = "platform_machine != \"aarch64\" and platform_machine != \"arm64\" and python_version < \"3.10\""}, + {version = ">=1.19.2", markers = "platform_machine == \"aarch64\" and python_version < \"3.10\""}, + {version = ">=1.20.0", markers = "platform_machine == \"arm64\" and python_version < \"3.10\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, +] +python-dateutil = ">=2.8.1" +pytz = ">=2020.1" + +[package.extras] +test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "platformdirs" +version = "2.5.1" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pycodestyle" +version = "2.8.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyflakes" +version = "2.4.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyparsing" +version = "3.0.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyrsistent" +version = "0.18.1" +description = "Persistent/Functional/Immutable data structures" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "pytest" +version = "5.4.3" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=17.4.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.extras] +checkqa-mypy = ["mypy (==v0.761)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2022.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "requests" +version = "2.27.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "smmap" +version = "5.0.0" +description = "A pure Python implementation of a sliding window memory map manager" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tox" +version = "3.24.5" +description = "tox is a generic virtualenv management and test command line tool" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} +filelock = ">=3.0.0" +packaging = ">=14" +pluggy = ">=0.12.0" +py = ">=1.4.17" +six = ">=1.14.0" +toml = ">=0.9.4" +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" + +[package.extras] +docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] +testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] + +[[package]] +name = "typing-extensions" +version = "4.1.1" +description = "Backported and Experimental Type Hints for Python 3.6+" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "urllib3" +version = "1.26.9" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.14.0" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +distlib = ">=0.3.1,<1" +filelock = ">=3.2,<4" +platformdirs = ">=2,<3" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "zipp" +version = "3.6.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "1447970a46fd5ea65aa5962ee6b7329007fd384f75b476d731ff86ce5bb5c8ad" + +[metadata.files] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +black = [ + {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, + {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, + {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, + {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, + {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, + {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, + {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, + {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, + {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, + {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, + {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, + {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, + {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, + {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, + {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, + {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, + {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, + {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, + {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, +] +certifi = [ + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, +] +click = [ + {file = "click-8.1.2-py3-none-any.whl", hash = "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e"}, + {file = "click-8.1.2.tar.gz", hash = "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +coverage = [ + {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, + {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, + {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, + {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, + {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, + {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, + {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, + {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, + {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, + {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, + {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, + {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, +] +distlib = [ + {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, + {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, +] +filelock = [ + {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, + {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, +] +flake8 = [ + {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, + {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, +] +gitdb = [ + {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, + {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, +] +gitpython = [ + {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, + {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +importlib-resources = [ + {file = "importlib_resources-5.6.0-py3-none-any.whl", hash = "sha256:a9dd72f6cc106aeb50f6e66b86b69b454766dd6e39b69ac68450253058706bcc"}, + {file = "importlib_resources-5.6.0.tar.gz", hash = "sha256:1b93238cbf23b4cde34240dd8321d99e9bf2eb4bc91c0c99b2886283e7baad85"}, +] +jsonschema = [ + {file = "jsonschema-4.4.0-py3-none-any.whl", hash = "sha256:77281a1f71684953ee8b3d488371b162419767973789272434bbc3f29d9c8823"}, + {file = "jsonschema-4.4.0.tar.gz", hash = "sha256:636694eb41b3535ed608fe04129f26542b59ed99808b4f688aa32dcf55317a83"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +more-itertools = [ + {file = "more-itertools-8.12.0.tar.gz", hash = "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064"}, + {file = "more_itertools-8.12.0-py3-none-any.whl", hash = "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +numpy = [ + {file = "numpy-1.22.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75"}, + {file = "numpy-1.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab"}, + {file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e"}, + {file = "numpy-1.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4"}, + {file = "numpy-1.22.3-cp310-cp310-win32.whl", hash = "sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430"}, + {file = "numpy-1.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4"}, + {file = "numpy-1.22.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce"}, + {file = "numpy-1.22.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe"}, + {file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5"}, + {file = "numpy-1.22.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1"}, + {file = "numpy-1.22.3-cp38-cp38-win32.whl", hash = "sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62"}, + {file = "numpy-1.22.3-cp38-cp38-win_amd64.whl", hash = "sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676"}, + {file = "numpy-1.22.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123"}, + {file = "numpy-1.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802"}, + {file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d"}, + {file = "numpy-1.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168"}, + {file = "numpy-1.22.3-cp39-cp39-win32.whl", hash = "sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa"}, + {file = "numpy-1.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a"}, + {file = "numpy-1.22.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f"}, + {file = "numpy-1.22.3.zip", hash = "sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pandas = [ + {file = "pandas-1.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be67c782c4f1b1f24c2f16a157e12c2693fd510f8df18e3287c77f33d124ed07"}, + {file = "pandas-1.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5a206afa84ed20e07603f50d22b5f0db3fb556486d8c2462d8bc364831a4b417"}, + {file = "pandas-1.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0010771bd9223f7afe5f051eb47c4a49534345dfa144f2f5470b27189a4dd3b5"}, + {file = "pandas-1.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3228198333dd13c90b6434ddf61aa6d57deaca98cf7b654f4ad68a2db84f8cfe"}, + {file = "pandas-1.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b79af3a69e5175c6fa7b4e046b21a646c8b74e92c6581a9d825687d92071b51"}, + {file = "pandas-1.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:5586cc95692564b441f4747c47c8a9746792e87b40a4680a2feb7794defb1ce3"}, + {file = "pandas-1.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:061609334a8182ab500a90fe66d46f6f387de62d3a9cb9aa7e62e3146c712167"}, + {file = "pandas-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b8134651258bce418cb79c71adeff0a44090c98d955f6953168ba16cc285d9f7"}, + {file = "pandas-1.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:df82739e00bb6daf4bba4479a40f38c718b598a84654cbd8bb498fd6b0aa8c16"}, + {file = "pandas-1.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:385c52e85aaa8ea6a4c600a9b2821181a51f8be0aee3af6f2dcb41dafc4fc1d0"}, + {file = "pandas-1.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:295872bf1a09758aba199992c3ecde455f01caf32266d50abc1a073e828a7b9d"}, + {file = "pandas-1.4.2-cp38-cp38-win32.whl", hash = "sha256:95c1e422ced0199cf4a34385ff124b69412c4bc912011ce895582bee620dfcaa"}, + {file = "pandas-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:5c54ea4ef3823108cd4ec7fb27ccba4c3a775e0f83e39c5e17f5094cb17748bc"}, + {file = "pandas-1.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c072c7f06b9242c855ed8021ff970c0e8f8b10b35e2640c657d2a541c5950f59"}, + {file = "pandas-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f549097993744ff8c41b5e8f2f0d3cbfaabe89b4ae32c8c08ead6cc535b80139"}, + {file = "pandas-1.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff08a14ef21d94cdf18eef7c569d66f2e24e0bc89350bcd7d243dd804e3b5eb2"}, + {file = "pandas-1.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c5bf555b6b0075294b73965adaafb39cf71c312e38c5935c93d78f41c19828a"}, + {file = "pandas-1.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51649ef604a945f781105a6d2ecf88db7da0f4868ac5d45c51cb66081c4d9c73"}, + {file = "pandas-1.4.2-cp39-cp39-win32.whl", hash = "sha256:d0d4f13e4be7ce89d7057a786023c461dd9370040bdb5efa0a7fe76b556867a0"}, + {file = "pandas-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:09d8be7dd9e1c4c98224c4dfe8abd60d145d934e9fc1f5f411266308ae683e6a"}, + {file = "pandas-1.4.2.tar.gz", hash = "sha256:92bc1fc585f1463ca827b45535957815b7deb218c549b7c18402c322c7549a12"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +platformdirs = [ + {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, + {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pycodestyle = [ + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, +] +pyflakes = [ + {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, + {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, +] +pyparsing = [ + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, +] +pyrsistent = [ + {file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"}, + {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26"}, + {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e"}, + {file = "pyrsistent-0.18.1-cp310-cp310-win32.whl", hash = "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6"}, + {file = "pyrsistent-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286"}, + {file = "pyrsistent-0.18.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6"}, + {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec"}, + {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c"}, + {file = "pyrsistent-0.18.1-cp38-cp38-win32.whl", hash = "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca"}, + {file = "pyrsistent-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a"}, + {file = "pyrsistent-0.18.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5"}, + {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045"}, + {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c"}, + {file = "pyrsistent-0.18.1-cp39-cp39-win32.whl", hash = "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc"}, + {file = "pyrsistent-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07"}, + {file = "pyrsistent-0.18.1.tar.gz", hash = "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96"}, +] +pytest = [ + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +pytz = [ + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, +] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +smmap = [ + {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, + {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +tox = [ + {file = "tox-3.24.5-py2.py3-none-any.whl", hash = "sha256:be3362472a33094bce26727f5f771ca0facf6dafa217f65875314e9a6600c95c"}, + {file = "tox-3.24.5.tar.gz", hash = "sha256:67e0e32c90e278251fea45b696d0fef3879089ccbe979b0c556d35d5a70e2993"}, +] +typing-extensions = [ + {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, + {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, +] +urllib3 = [ + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, +] +virtualenv = [ + {file = "virtualenv-20.14.0-py2.py3-none-any.whl", hash = "sha256:1e8588f35e8b42c6ec6841a13c5e88239de1e6e4e4cedfd3916b306dc826ec66"}, + {file = "virtualenv-20.14.0.tar.gz", hash = "sha256:8e5b402037287126e81ccde9432b95a8be5b19d36584f64957060a3488c11ca8"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] +zipp = [ + {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, + {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c7c845d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[tool.poetry] +name = "speedscope-to-codeperf" +version = "0.1.0" +description = "" +authors = ["filipecosta90 "] + +[tool.poetry.dependencies] +python = "^3.8" +jsonschema = "^4.4.0" +pandas = "^1.4.2" +toml = "^0.10.2" +GitPython = "^3.1.27" +requests = "^2.27.1" + +[tool.poetry.dev-dependencies] +pytest = "^5.2" +tox = "^3.24.5" +black = "^22.3.0" +coverage = "^6.3.2" +flake8 = "^4.0.1" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +speedscope-to-codeperf = "speedscope_to_codeperf.cli:main" \ No newline at end of file diff --git a/speedscope_to_codeperf/__init__.py b/speedscope_to_codeperf/__init__.py new file mode 100644 index 0000000..c9660ad --- /dev/null +++ b/speedscope_to_codeperf/__init__.py @@ -0,0 +1,14 @@ +# Apache License Version 2.0 +# +# Copyright (c) 2022., codeperf.io +# All rights reserved. +# + +# This attribute is the only one place that the version number is written down, +# so there is only one place to change it when the version number changes. +import pkg_resources + +try: + __version__ = pkg_resources.get_distribution("speedscope-to-codeperf").version +except (pkg_resources.DistributionNotFound, AttributeError): + __version__ = "99.99.99" # like redis modules diff --git a/speedscope_to_codeperf/cli.py b/speedscope_to_codeperf/cli.py new file mode 100644 index 0000000..4ad39b2 --- /dev/null +++ b/speedscope_to_codeperf/cli.py @@ -0,0 +1,196 @@ +# Apache License Version 2.0 +# +# Copyright (c) 2021., Redis Labs Modules +# All rights reserved. +# + +import argparse +import json +import logging +import os +import sys + +import requests +import toml + +from speedscope_to_codeperf import __version__ +from speedscope_to_codeperf.git import extract_git_vars +from speedscope_to_codeperf.schema.speedscope import ( + validate_speedscope_json, + get_cpu_by_function, + get_flamegraph, +) + +LOG_LEVEL = logging.DEBUG +if os.getenv("VERBOSE", "0") == "0": + LOG_LEVEL = logging.INFO +LOG_FORMAT = "%(asctime)s %(levelname)-4s %(message)s" +LOG_DATEFMT = "%Y-%m-%d %H:%M:%S" + +LONG_DESCRIPTION = """ __ ____ _ + _________ ____/ /__ ____ ___ _____/ __/ (_)___ + / ___/ __ \\/ __ / _ \\/ __ \\/ _ \\/ ___/ /_ / / __ \\ +/ /__/ /_/ / /_/ / __/ /_/ / __/ / / __/ _ / / /_/ / +\\___/\\____/\\__,_/\\___/ .___/\\___/_/ /_/ (_) /_/\\____/ + /_/ +Export and persist speedscope's profiling data locally, or into https://codeperf.io for FREE.""" + + +def populate_with_poetry_data(): + project_name = "speedscope-to-codeperf" + project_version = __version__ + project_description = None + try: + poetry_data = toml.load("pyproject.toml")["tool"]["poetry"] + project_name = poetry_data["name"] + project_version = poetry_data["version"] + project_description = poetry_data["description"] + except FileNotFoundError: + pass + + return project_name, project_description, project_version + + +def main(): + ( + github_org_name, + github_repo_name, + github_sha, + github_actor, + github_branch, + github_branch_detached, + ) = extract_git_vars() + project_name, project_description, project_version = populate_with_poetry_data() + parser = argparse.ArgumentParser( + description=project_description, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + # common arguments to all tools + parser.add_argument( + "--version", default=False, action="store_true", help="print version and exit" + ) + parser.add_argument( + "--git-commit", type=str, default=github_sha, help="git commit hash" + ) + parser.add_argument("--git-org", type=str, default=github_org_name, help="git org") + parser.add_argument( + "--git-repo", type=str, default=github_repo_name, help="git repo" + ) + parser.add_argument( + "--codeperf-url", type=str, default="https://codeperf.io", help="codeperf URL" + ) + parser.add_argument( + "--api-codeperf-url", + type=str, + default="https://api.codeperf.io", + help="codeperf URL", + ) + parser.add_argument( + "--bench", type=str, default=None, required=True, help="Benchmark name" + ) + parser.add_argument( + "--input", type=str, default=None, required=True, help="input filename" + ) + + args = parser.parse_args() + if args.version: + print_version(project_name, project_version) + sys.exit(0) + + logger = logging.getLogger() + logger.setLevel(LOG_LEVEL) + + print(LONG_DESCRIPTION) + log_version(project_name, project_version) + + if args.git_commit is None: + logging.error("git commit cannot be None. Exiting...") + sys.exit(1) + if args.git_org is None: + logging.error("git org cannot be None. Exiting...") + sys.exit(1) + if args.git_repo is None: + logging.error("git repo cannot be None. Exiting...") + sys.exit(1) + + with open(args.input, "r") as speedscope_fd: + speedscope_json = json.load(speedscope_fd) + res_validate = validate_speedscope_json(speedscope_json) + logging.info("Validating input file schema") + if res_validate is False: + logging.error( + "The provided file {} does not contain a valid speedscope schema. Exiting...".format( + args.input + ) + ) + else: + codeperf_url = args.codeperf_url + api_codeperf_url = args.api_codeperf_url + git_org = args.git_org + git_repo = args.git_repo + git_commit = args.git_commit + bench = args.bench + for granularity in ["functions", "lines"]: + cpu_by_function_json = get_cpu_by_function(speedscope_json, granularity) + endpoint = "{}/v1/gh/{}/{}/commit/{}/bench/{}/cpu/{}".format( + api_codeperf_url, git_org, git_repo, git_commit, bench, granularity + ) + resp = requests.post(endpoint, json=cpu_by_function_json) + if resp.status_code == 200: + logging.info( + "Successfully published profile data in granularity: {}".format( + granularity + ) + ) + flamegraph = get_flamegraph(speedscope_json) + granularity = "flamegraph" + endpoint = "{}/v1/gh/{}/{}/commit/{}/bench/{}/cpu/{}".format( + api_codeperf_url, git_org, git_repo, git_commit, bench, granularity + ) + resp = requests.post(endpoint, json=flamegraph) + if resp.status_code == 200: + logging.info( + "Successfully published profile data in granularity: {}".format( + granularity + ) + ) + + logging.info( + "Check it at: {}/gh/{}/{}/commit/{}/bench/{}/cpu".format( + codeperf_url, + git_org, + git_repo, + git_commit, + bench, + ) + ) + + +def print_stdout_effective_log_level(): + effective_log_level = "N/A" + effective_log_level = logging.getLogger().getEffectiveLevel() + if effective_log_level == logging.DEBUG: + effective_log_level = "DEBUG" + if effective_log_level == logging.INFO: + effective_log_level = "INFO" + if effective_log_level == logging.WARN: + effective_log_level = "WARN" + if effective_log_level == logging.ERROR: + effective_log_level = "ERROR" + print("Effective log level set to {}".format(effective_log_level)) + + +def print_version(project_name, project_version): + print( + "{project_name} {project_version}".format( + project_name=project_name, project_version=project_version + ) + ) + + +def log_version(project_name, project_version): + logging.info( + "Using {project_name} {project_version}".format( + project_name=project_name, project_version=project_version + ) + ) diff --git a/speedscope_to_codeperf/git.py b/speedscope_to_codeperf/git.py new file mode 100644 index 0000000..d534a5e --- /dev/null +++ b/speedscope_to_codeperf/git.py @@ -0,0 +1,88 @@ +import configparser +import logging +import git +from git import Repo + + +def get_git_root(path): + git_repo = git.Repo(path, search_parent_directories=True) + git_root = git_repo.git.rev_parse("--show-toplevel") + return git_root + + +# Abbreviate the long hash to a short hash (7 digits) +def get_short_hash(hash, ndigits=7): + if len(hash) < ndigits: + short = hash + else: + short = hash[:ndigits] + return short + + +def extract_git_vars(path=None, github_url=None): + github_org_name = None + github_repo_name = None + github_sha = None + github_actor = None + github_branch = None + github_branch_detached = None + try: + if path is None: + path = get_git_root(".") + github_repo = Repo(path) + if github_url is None: + github_url = github_repo.remotes[0].config_reader.get("url") + if "/" in github_url[-1:]: + github_url = github_url[:-1] + if "http" in github_url: + github_org_name = github_url.split("/")[-2] + github_repo_name = github_url.split("/")[-1].split(".")[0] + else: + github_url = github_url.replace(".git", "") + github_org_name = github_url.split(":")[1].split("/")[0] + github_repo_name = github_url.split(":")[1].split("/")[1] + try: + github_sha = get_short_hash(github_repo.head.object.hexsha) + except ValueError as e: + logging.debug( + "Unable to detected github_sha. caught the following error: {}".format( + e.__str__() + ) + ) + github_branch = None + github_branch_detached = False + try: + github_branch = github_repo.active_branch + except TypeError as e: + logging.debug( + "Unable to detected github_branch. caught the following error: {}".format( + e.__str__() + ) + ) + github_branch_detached = True + + github_actor = None + try: + github_actor = github_repo.config_reader().get_value("user", "name") + except configparser.NoSectionError as e: + logging.debug( + "Unable to detected github_actor. caught the following error: {}".format( + e.__str__() + ) + ) + github_branch_detached = True + except git.exc.InvalidGitRepositoryError as e: + logging.debug( + "Unable to fill git vars. caught the following error: {}".format( + e.__str__() + ) + ) + github_branch_detached = True + return ( + github_org_name, + github_repo_name, + github_sha, + github_actor, + github_branch, + github_branch_detached, + ) diff --git a/speedscope_to_codeperf/schema/speedscope.py b/speedscope_to_codeperf/schema/speedscope.py new file mode 100644 index 0000000..c0d1bcc --- /dev/null +++ b/speedscope_to_codeperf/schema/speedscope.py @@ -0,0 +1,443 @@ +import copy +import json +import jsonschema +import pandas as pd + +# from https://www.speedscope.app/file-format-schema.json +speedscope_schema = json.loads( + """{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "CloseFrameEvent": { + "properties": { + "at": { + "title": "at", + "type": "number" + }, + "frame": { + "title": "frame", + "type": "number" + }, + "type": { + "enum": [ + "C" + ], + "title": "type", + "type": "string" + } + }, + "required": [ + "at", + "frame", + "type" + ], + "title": "CloseFrameEvent", + "type": "object" + }, + "FileFormat.EventType": { + "enum": [ + "C", + "O" + ], + "title": "FileFormat.EventType", + "type": "string" + }, + "FileFormat.EventedProfile": { + "properties": { + "endValue": { + "title": "endValue", + "type": "number" + }, + "events": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/OpenFrameEvent" + }, + { + "$ref": "#/definitions/CloseFrameEvent" + } + ] + }, + "title": "events", + "type": "array" + }, + "name": { + "title": "name", + "type": "string" + }, + "startValue": { + "title": "startValue", + "type": "number" + }, + "type": { + "enum": [ + "evented" + ], + "title": "type", + "type": "string" + }, + "unit": { + "$ref": "#/definitions/FileFormat.ValueUnit", + "title": "unit" + } + }, + "required": [ + "endValue", + "events", + "name", + "startValue", + "type", + "unit" + ], + "title": "FileFormat.EventedProfile", + "type": "object" + }, + "FileFormat.File": { + "properties": { + "$schema": { + "enum": [ + "https://www.speedscope.app/file-format-schema.json" + ], + "title": "$schema", + "type": "string" + }, + "activeProfileIndex": { + "title": "activeProfileIndex", + "type": ["number", "null"] + }, + "exporter": { + "title": "exporter", + "type": "string" + }, + "name": { + "title": "name", + "type": "string" + }, + "profiles": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/FileFormat.EventedProfile" + }, + { + "$ref": "#/definitions/FileFormat.SampledProfile" + } + ] + }, + "title": "profiles", + "type": "array" + }, + "shared": { + "properties": { + "frames": { + "items": { + "$ref": "#/definitions/FileFormat.Frame" + }, + "title": "frames", + "type": "array" + } + }, + "required": [ + "frames" + ], + "title": "shared", + "type": "object" + } + }, + "required": [ + "$schema", + "profiles", + "shared" + ], + "title": "FileFormat.File", + "type": "object" + }, + "FileFormat.Frame": { + "properties": { + "col": { + "title": "col", + "type": ["number", "null"] + }, + "file": { + "title": "file", + "type": "string" + }, + "line": { + "title": "line", + "type": "number" + }, + "name": { + "title": "name", + "type": "string" + } + }, + "required": [ + "name" + ], + "title": "FileFormat.Frame", + "type": "object" + }, + "FileFormat.IProfile": { + "properties": { + "type": { + "$ref": "#/definitions/FileFormat.ProfileType", + "title": "type" + } + }, + "required": [ + "type" + ], + "title": "FileFormat.IProfile", + "type": "object" + }, + "FileFormat.Profile": { + "anyOf": [ + { + "$ref": "#/definitions/FileFormat.EventedProfile" + }, + { + "$ref": "#/definitions/FileFormat.SampledProfile" + } + ] + }, + "FileFormat.ProfileType": { + "enum": [ + "evented", + "sampled" + ], + "title": "FileFormat.ProfileType", + "type": "string" + }, + "FileFormat.SampledProfile": { + "properties": { + "endValue": { + "title": "endValue", + "type": "number" + }, + "name": { + "title": "name", + "type": "string" + }, + "samples": { + "items": { + "items": { + "type": "number" + }, + "type": "array" + }, + "title": "samples", + "type": "array" + }, + "startValue": { + "title": "startValue", + "type": "number" + }, + "type": { + "enum": [ + "sampled" + ], + "title": "type", + "type": "string" + }, + "unit": { + "$ref": "#/definitions/FileFormat.ValueUnit", + "title": "unit" + }, + "weights": { + "items": { + "type": "number" + }, + "title": "weights", + "type": "array" + } + }, + "required": [ + "endValue", + "name", + "samples", + "startValue", + "type", + "unit", + "weights" + ], + "title": "FileFormat.SampledProfile", + "type": "object" + }, + "FileFormat.ValueUnit": { + "enum": [ + "bytes", + "microseconds", + "milliseconds", + "nanoseconds", + "none", + "seconds" + ], + "title": "FileFormat.ValueUnit", + "type": "string" + }, + "IEvent": { + "properties": { + "at": { + "title": "at", + "type": "number" + }, + "type": { + "$ref": "#/definitions/FileFormat.EventType", + "title": "type" + } + }, + "required": [ + "at", + "type" + ], + "title": "IEvent", + "type": "object" + }, + "OpenFrameEvent": { + "properties": { + "at": { + "title": "at", + "type": "number" + }, + "frame": { + "title": "frame", + "type": "number" + }, + "type": { + "enum": [ + "O" + ], + "title": "type", + "type": "string" + } + }, + "required": [ + "at", + "frame", + "type" + ], + "title": "OpenFrameEvent", + "type": "object" + }, + "SampledStack": { + "items": { + "type": "number" + }, + "type": "array" + } + }, + "$ref": "#/definitions/FileFormat.File" +} +""" +) + + +def validate_speedscope_json(json_data): + try: + jsonschema.validate(instance=json_data, schema=speedscope_schema) + except jsonschema.exceptions.ValidationError as err: + raise err + return False + return True + + +def get_cpu_by_function(json_data, granularity="functions"): + frames = json_data["shared"]["frames"] + profile = json_data["profiles"][0] + samples = profile["samples"] + intermediate_dict = {} + for stack in samples: + stack_l = [] + last_n = len(stack) + for n, frame_pos in enumerate(stack, 1): + frame = frames[frame_pos] + name = frame["name"] + file = "" + if "file" in frame: + file = frame["file"] + line = "" + if "line" in frame: + line = frame["line"] + full_fname = "{} {}".format(name, file) + if granularity == "lines" and line != "": + full_fname = "{}:{} {}".format(name, line, file) + full_fname = full_fname.strip(" ") + if full_fname not in intermediate_dict: + intermediate_dict[full_fname] = {"flat": 0, "cum": 0} + + if full_fname not in stack_l: + intermediate_dict[full_fname]["cum"] = ( + intermediate_dict[full_fname]["cum"] + 1 + ) + stack_l.append(full_fname) + if n == last_n: + intermediate_dict[full_fname]["flat"] = ( + intermediate_dict[full_fname]["flat"] + 1 + ) + al = [] + for k, v in intermediate_dict.items(): + flat_p = float(v["flat"]) / len(samples) * 100.0 + cum_p = float(v["cum"]) / len(samples) * 100.0 + al.append((k, flat_p, cum_p)) + df = pd.DataFrame(al, columns=["symbol", "flat%", "cum%"]) + df = df.sort_values(by=["flat%"], ascending=False) + df["flat%"] = df["flat%"].map("{:,.2f}".format) + df["cum%"] = df["cum%"].map("{:,.2f}".format) + js = json.loads(df.to_json(orient="records")) + + res = {"data": js, "totalRows": len(al), "totalPages": 1, "labels": []} + return res + + +def get_node(name, function, value=0): + node = {"n": name, "f": function, "v": value, "c": {}} + return node + + +def touch_node(parent_node, key, name, function, value=0): + if key not in parent_node["c"]: + parent_node["c"][key] = get_node(name, function, value) + return parent_node["c"][key] + + +def get_flamegraph(json_data): + root_node = get_node("root", "root", 0) + frames = json_data["shared"]["frames"] + profile = json_data["profiles"][0] + samples = profile["samples"] + for stack in samples: + previous_node = root_node + last_n = len(stack) + for n, frame_pos in enumerate(stack, 1): + frame = frames[frame_pos] + name = frame["name"] + file = "" + if "file" in frame: + file = frame["file"] + line = "" + if "line" in frame: + line = frame["line"] + full_fname = "{} {}".format(name, file) + if line != "": + full_fname = "{}:{} {}".format(name, line, file) + # name = "{}:{}".format(name, line) + full_fname = full_fname.strip(" ") + name = name.strip(" ") + current_node = touch_node(previous_node, full_fname, full_fname, full_fname) + if n == last_n: + current_node["v"] = current_node["v"] + 1 + previous_node = current_node + + res = flatten_tree(root_node) + print(res) + return res + + +def flatten_tree(root_node): + res = copy.deepcopy(root_node) + res["c"] = [] + for v in root_node["c"].values(): + # print("flattening {}".format(v["f"])) + res["c"].append(flatten_tree(v)) + return res diff --git a/tests/2.json b/tests/2.json new file mode 100644 index 0000000..708d156 --- /dev/null +++ b/tests/2.json @@ -0,0 +1,8 @@ +{ + "data": [ + + ], + "totalRows": 4, + "totalPages": 1, + "labels": [] +} \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fib.json b/tests/fib.json new file mode 100644 index 0000000..9bfe955 --- /dev/null +++ b/tests/fib.json @@ -0,0 +1,11194 @@ +{ + "$schema": "https://www.speedscope.app/file-format-schema.json", + "profiles": [ + { + "type": "sampled", + "name": "Process 9185 Thread 0x200EC2600 \"MainThread\"", + "unit": "seconds", + "startValue": 0, + "endValue": 4, + "samples": [ + [ + 15, + 14, + 7, + 6, + 5, + 4, + 3, + 13, + 7, + 6, + 5, + 4, + 3, + 12, + 11, + 3, + 7, + 6, + 5, + 4, + 3, + 10, + 7, + 6, + 5, + 4, + 3, + 9, + 7, + 6, + 5, + 4, + 3, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1, + 0 + ], + [ + 15, + 14, + 7, + 6, + 5, + 4, + 3, + 13, + 7, + 6, + 5, + 4, + 3, + 12, + 11, + 3, + 7, + 6, + 5, + 4, + 3, + 10, + 7, + 6, + 5, + 4, + 3, + 18, + 1, + 17, + 16 + ], + [ + 15, + 14, + 7, + 6, + 5, + 4, + 3, + 26, + 7, + 6, + 5, + 4, + 3, + 25, + 7, + 6, + 24, + 23, + 22, + 21, + 20, + 19 + ], + [ + 15, + 14, + 7, + 6, + 5, + 4, + 3, + 32, + 7, + 6, + 5, + 4, + 3, + 31, + 7, + 6, + 5, + 4, + 3, + 30, + 29, + 28, + 1, + 27 + ], + [ + 15, + 14, + 7, + 6, + 5, + 4, + 3, + 32, + 7, + 6, + 5, + 4, + 3, + 31, + 7, + 6, + 5, + 4, + 3, + 30, + 29, + 28, + 1, + 35, + 34, + 33 + ], + [ + 15, + 50, + 49, + 48, + 47, + 46, + 45, + 44, + 43, + 42, + 41, + 40, + 39, + 38, + 37, + 36 + ], + [ + 15, + 50, + 49, + 48, + 47, + 46, + 45, + 44, + 43, + 42, + 41, + 40, + 39, + 52, + 51 + ], + [ + 15, + 50, + 49, + 48, + 47, + 46, + 45, + 44, + 43, + 42, + 63, + 62, + 61, + 60, + 59, + 7, + 6, + 5, + 57, + 58, + 7, + 6, + 5, + 57, + 56, + 7, + 6, + 5, + 4, + 3, + 55, + 7, + 6, + 24, + 54, + 53, + 3 + ], + [ + 15, + 50, + 49, + 48, + 47, + 46, + 45, + 44, + 43, + 42, + 63, + 62, + 61, + 60, + 59, + 7, + 6, + 5, + 57, + 58, + 7, + 6, + 5, + 57, + 67, + 7, + 6, + 5, + 4, + 3, + 66, + 65, + 64 + ], + [ + 15, + 50, + 49, + 48, + 47, + 46, + 45, + 44, + 43, + 42, + 63, + 72, + 71, + 70, + 69, + 68 + ], + [ + 15, + 50, + 49, + 48, + 47, + 46, + 45, + 44, + 43, + 42, + 63, + 62, + 61, + 60, + 59, + 7, + 88, + 3, + 59, + 7, + 6, + 5, + 57, + 87, + 7, + 6, + 5, + 57, + 86, + 11, + 3, + 7, + 6, + 5, + 57, + 85, + 7, + 6, + 5, + 57, + 84, + 7, + 6, + 5, + 57, + 83, + 7, + 6, + 5, + 4, + 3, + 82, + 7, + 6, + 5, + 4, + 3, + 81, + 11, + 3, + 7, + 6, + 5, + 4, + 3, + 80, + 7, + 79, + 78, + 77, + 76, + 75, + 74, + 73 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 89 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 109 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 109 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 110 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 109 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 109 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 111 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 112 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 89 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 110 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 109 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 113 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 114 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 115 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 111 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 111 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 110 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 110 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 114 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 114 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 110 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 116 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 112 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 110 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 110 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 110 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 89 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 110 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 110 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 89 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 111 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 112 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 89 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 89 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 109 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 89 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 109 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 109 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 89 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 117 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 110 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 111 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 115 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 111 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 109 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 111 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 89 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 115 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 110 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 111 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 91, + 90, + 109 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 121, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 121, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 121, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 121, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 122 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 121, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 92, + 121, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 122 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 124 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 122 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 124 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 122 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 122 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 123, + 120, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 125, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 125, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 125, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 125, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 125, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 125, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 125, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 118 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 125, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 125, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 125, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119 + ], + [ + 15, + 50, + 49, + 108, + 46, + 45, + 44, + 107, + 106, + 105, + 46, + 45, + 44, + 104, + 46, + 45, + 44, + 103, + 102, + 101, + 100, + 99, + 98, + 46, + 45, + 44, + 97, + 96, + 46, + 45, + 44, + 95, + 94, + 93, + 125, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 119, + 122 + ] + ], + "weights": [ + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612, + 0.02040816326530612 + ] + } + ], + "shared": { + "frames": [ + { + "name": "__getitem__", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/typing.py", + "line": 365, + "col": null + }, + { + "name": "inner", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/typing.py", + "line": 258, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/pathlib.py", + "line": 563, + "col": null + }, + { + "name": "_call_with_frames_removed", + "file": "", + "line": 219, + "col": null + }, + { + "name": "exec_module", + "file": "", + "line": 783, + "col": null + }, + { + "name": "_load_unlocked", + "file": "", + "line": 671, + "col": null + }, + { + "name": "_find_and_load_unlocked", + "file": "", + "line": 975, + "col": null + }, + { + "name": "_find_and_load", + "file": "", + "line": 991, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/findpaths.py", + "line": 16, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/__init__.py", + "line": 45, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/assertion/rewrite.py", + "line": 38, + "col": null + }, + { + "name": "_handle_fromlist", + "file": "", + "line": 1042, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/assertion/__init__.py", + "line": 9, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest/__init__.py", + "line": 5, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/bin/pytest", + "line": 5, + "col": null + }, + { + "name": "process 9185:\"/opt/homebrew/Caskroom/miniforge/base/bin/python3.8 /opt/homebrew/Caskroom/miniforge/base/bin/pytest --benchmark-min-rounds=10\"", + "file": "", + "line": 0, + "col": null + }, + { + "name": "_subs_tvars", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/typing.py", + "line": 194, + "col": null + }, + { + "name": "__getitem__", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/typing.py", + "line": 687, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/__init__.py", + "line": 1566, + "col": null + }, + { + "name": "cache_from_source", + "file": "", + "line": 321, + "col": null + }, + { + "name": "_get_cached", + "file": "", + "line": 427, + "col": null + }, + { + "name": "cached", + "file": "", + "line": 382, + "col": null + }, + { + "name": "_init_module_attrs", + "file": "", + "line": 541, + "col": null + }, + { + "name": "module_from_spec", + "file": "", + "line": 562, + "col": null + }, + { + "name": "_load_unlocked", + "file": "", + "line": 657, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/capture.py", + "line": 8, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest/__init__.py", + "line": 7, + "col": null + }, + { + "name": "__getitem_inner__", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/typing.py", + "line": 830, + "col": null + }, + { + "name": "__getitem__", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/typing.py", + "line": 804, + "col": null + }, + { + "name": "TerminalReporter", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/terminal.py", + "line": 1148, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/terminal.py", + "line": 316, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/logging.py", + "line": 35, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest/__init__.py", + "line": 22, + "col": null + }, + { + "name": "__init__", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/typing.py", + "line": 669, + "col": null + }, + { + "name": "copy_with", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/typing.py", + "line": 691, + "col": null + }, + { + "name": "__getitem_inner__", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/typing.py", + "line": 831, + "col": null + }, + { + "name": "_get_optional_kwargs", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/argparse.py", + "line": 1492, + "col": null + }, + { + "name": "add_argument", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/argparse.py", + "line": 1354, + "col": null + }, + { + "name": "_getparser", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/argparsing.py", + "line": 122, + "col": null + }, + { + "name": "parse_known_and_unknown_args", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/argparsing.py", + "line": 155, + "col": null + }, + { + "name": "parse_known_args", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/argparsing.py", + "line": 146, + "col": null + }, + { + "name": "_preparse", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/__init__.py", + "line": 1163, + "col": null + }, + { + "name": "parse", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/__init__.py", + "line": 1283, + "col": null + }, + { + "name": "pytest_cmdline_parse", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/__init__.py", + "line": 1003, + "col": null + }, + { + "name": "_multicall", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pluggy/_callers.py", + "line": 39, + "col": null + }, + { + "name": "_hookexec", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pluggy/_manager.py", + "line": 80, + "col": null + }, + { + "name": "__call__", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pluggy/_hooks.py", + "line": 265, + "col": null + }, + { + "name": "_prepareconfig", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/__init__.py", + "line": 318, + "col": null + }, + { + "name": "main", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/__init__.py", + "line": 143, + "col": null + }, + { + "name": "console_main", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/__init__.py", + "line": 185, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/bin/pytest", + "line": 8, + "col": null + }, + { + "name": "attrs", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/argparsing.py", + "line": 281, + "col": null + }, + { + "name": "_getparser", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/argparsing.py", + "line": 121, + "col": null + }, + { + "name": "create_module", + "file": "", + "line": 1101, + "col": null + }, + { + "name": "module_from_spec", + "file": "", + "line": 556, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/cProfile.py", + "line": 9, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/fixture.py", + "line": 4, + "col": null + }, + { + "name": "exec_module", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/assertion/rewrite.py", + "line": 170, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/plugin.py", + "line": 15, + "col": null + }, + { + "name": "_gcd_import", + "file": "", + "line": 1014, + "col": null + }, + { + "name": "import_module", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/importlib/__init__.py", + "line": 127, + "col": null + }, + { + "name": "load", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/importlib/metadata.py", + "line": 77, + "col": null + }, + { + "name": "load_setuptools_entrypoints", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pluggy/_manager.py", + "line": 287, + "col": null + }, + { + "name": "_preparse", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/__init__.py", + "line": 1172, + "col": null + }, + { + "name": "__enter__", + "file": "", + "line": 148, + "col": null + }, + { + "name": "_find_and_load", + "file": "", + "line": 988, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/statistics.py", + "line": 1108, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/fixture.py", + "line": 19, + "col": null + }, + { + "name": "__init__", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/configparser.py", + "line": 1320, + "col": null + }, + { + "name": "__init__", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/configparser.py", + "line": 611, + "col": null + }, + { + "name": "_from_text", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/importlib/metadata.py", + "line": 96, + "col": null + }, + { + "name": "entry_points", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/importlib/metadata.py", + "line": 240, + "col": null + }, + { + "name": "load_setuptools_entrypoints", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pluggy/_manager.py", + "line": 278, + "col": null + }, + { + "name": "_from_parts", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/pathlib.py", + "line": 687, + "col": null + }, + { + "name": "__new__", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/pathlib.py", + "line": 650, + "col": null + }, + { + "name": "fnmatch_ex", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/pathlib.py", + "line": 403, + "col": null + }, + { + "name": "_early_rewrite_bailout", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/assertion/rewrite.py", + "line": 202, + "col": null + }, + { + "name": "find_spec", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/assertion/rewrite.py", + "line": 91, + "col": null + }, + { + "name": "_find_spec", + "file": "", + "line": 914, + "col": null + }, + { + "name": "_find_and_load_unlocked", + "file": "", + "line": 971, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/asyncio/coroutines.py", + "line": 15, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/asyncio/base_events.py", + "line": 39, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/asyncio/__init__.py", + "line": 8, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/anyio/from_thread.py", + "line": 2, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/anyio/abc/__init__.py", + "line": 28, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/anyio/to_thread.py", + "line": 5, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/anyio/_core/_fileio.py", + "line": 11, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/anyio/__init__.py", + "line": 85, + "col": null + }, + { + "name": "_find_and_load_unlocked", + "file": "", + "line": 961, + "col": null + }, + { + "name": "compute_timer_precision", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/timers.py", + "line": 22, + "col": null + }, + { + "name": "_get_precision", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/fixture.py", + "line": 75, + "col": null + }, + { + "name": "_calibrate_timer", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/fixture.py", + "line": 262, + "col": null + }, + { + "name": "_raw", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/fixture.py", + "line": 149, + "col": null + }, + { + "name": "__call__", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/fixture.py", + "line": 127, + "col": null + }, + { + "name": "test_benchmark_bif", + "file": "/Users/fco/codeperf/example-python-poetry/tests/test_example_python_poetry.py", + "line": 6, + "col": null + }, + { + "name": "pytest_pyfunc_call", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/python.py", + "line": 183, + "col": null + }, + { + "name": "runtest", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/python.py", + "line": 1641, + "col": null + }, + { + "name": "pytest_runtest_call", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/runner.py", + "line": 162, + "col": null + }, + { + "name": "", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/runner.py", + "line": 255, + "col": null + }, + { + "name": "from_call", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/runner.py", + "line": 311, + "col": null + }, + { + "name": "call_runtest_hook", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/runner.py", + "line": 254, + "col": null + }, + { + "name": "call_and_report", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/runner.py", + "line": 215, + "col": null + }, + { + "name": "runtestprotocol", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/runner.py", + "line": 126, + "col": null + }, + { + "name": "pytest_runtest_protocol", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/runner.py", + "line": 109, + "col": null + }, + { + "name": "pytest_runtestloop", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/main.py", + "line": 348, + "col": null + }, + { + "name": "_main", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/main.py", + "line": 323, + "col": null + }, + { + "name": "wrap_session", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/main.py", + "line": 269, + "col": null + }, + { + "name": "pytest_cmdline_main", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/main.py", + "line": 316, + "col": null + }, + { + "name": "main", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/_pytest/config/__init__.py", + "line": 162, + "col": null + }, + { + "name": "compute_timer_precision", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/timers.py", + "line": 23, + "col": null + }, + { + "name": "compute_timer_precision", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/timers.py", + "line": 34, + "col": null + }, + { + "name": "compute_timer_precision", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/timers.py", + "line": 25, + "col": null + }, + { + "name": "compute_timer_precision", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/timers.py", + "line": 38, + "col": null + }, + { + "name": "compute_timer_precision", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/timers.py", + "line": 28, + "col": null + }, + { + "name": "compute_timer_precision", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/timers.py", + "line": 26, + "col": null + }, + { + "name": "compute_timer_precision", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/timers.py", + "line": 37, + "col": null + }, + { + "name": "compute_timer_precision", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/timers.py", + "line": 27, + "col": null + }, + { + "name": "compute_timer_precision", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/timers.py", + "line": 24, + "col": null + }, + { + "name": "fib", + "file": "/Users/fco/codeperf/example-python-poetry/example_python_poetry/fib.py", + "line": 2, + "col": null + }, + { + "name": "fib", + "file": "/Users/fco/codeperf/example-python-poetry/example_python_poetry/fib.py", + "line": 4, + "col": null + }, + { + "name": "runner", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/fixture.py", + "line": 92, + "col": null + }, + { + "name": "_calibrate_timer", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/fixture.py", + "line": 277, + "col": null + }, + { + "name": "fib", + "file": "/Users/fco/codeperf/example-python-poetry/example_python_poetry/fib.py", + "line": 1, + "col": null + }, + { + "name": "_raw", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/fixture.py", + "line": 166, + "col": null + }, + { + "name": "fib", + "file": "/Users/fco/codeperf/example-python-poetry/example_python_poetry/fib.py", + "line": 3, + "col": null + }, + { + "name": "_raw", + "file": "/opt/homebrew/Caskroom/miniforge/base/lib/python3.8/site-packages/pytest_benchmark/fixture.py", + "line": 173, + "col": null + } + ] + }, + "activeProfileIndex": null, + "exporter": "py-spy@0.3.11", + "name": "py-spy profile" +} diff --git a/tests/flame.json b/tests/flame.json new file mode 100644 index 0000000..f92070b --- /dev/null +++ b/tests/flame.json @@ -0,0 +1,97 @@ +{ + "n": "root", + "f": "root", + "v": 0, + "c": [ + { + "n": "launch", + "f": "testing.(*B).launch", + "v": 1234, + "c": [ + { + "n": "runN", + "f": "testing.(*B).runN", + "v": 1234, + "c": [ + { + "n": "BenchmarkFib10", + "f": "example-go.BenchmarkFib10", + "v": 1234, + "c": [ + { + "n": "Fib", + "f": "example-go.Fib", + "v": 1224, + "c": [ + { + "n": "Fib", + "f": "example-go.Fib", + "v": 1217, + "c": [ + { + "n": "Fib", + "f": "example-go.Fib", + "v": 1193, + "c": [ + { + "n": "Fib", + "f": "example-go.Fib", + "v": 1155, + "c": [ + { + "n": "Fib", + "f": "example-go.Fib", + "v": 1082, + "c": [ + { + "n": "Fib", + "f": "example-go.Fib", + "v": 952, + "c": [ + { + "n": "Fib", + "f": "example-go.Fib", + "v": 758, + "c": [ + { + "n": "Fib", + "f": "example-go.Fib", + "v": 423, + "c": [ + { + "n": "Fib", + "f": "example-go.Fib", + "v": 121, + "c": [ + { + "n": "Fib", + "f": "example-go.Fib", + "v": 13, + "c": [] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/tests/sampled.speedscope.json b/tests/sampled.speedscope.json new file mode 100644 index 0000000..89a3551 --- /dev/null +++ b/tests/sampled.speedscope.json @@ -0,0 +1,19 @@ +{ + "exporter": "speedscope@0.6.0", + "$schema": "https://www.speedscope.app/file-format-schema.json", + "name": "Two Samples", + "activeProfileIndex": 1, + "profiles": [ + { + "type": "sampled", + "name": "one", + "unit": "seconds", + "startValue": 0, + "endValue": 14, + "samples": [[0, 1, 2], [0, 1, 2], [0, 1, 3]] + } + ], + "shared": { + "frames": [{"name": "a","line": 1}, {"name": "b","line": 1}, {"name": "c","line": 1}, {"name": "c","line": 2}] + } +} diff --git a/tests/test-requirements.txt b/tests/test-requirements.txt new file mode 100644 index 0000000..c878d54 --- /dev/null +++ b/tests/test-requirements.txt @@ -0,0 +1,5 @@ +pytest +pytest-cov +codecov +black +flake8 diff --git a/tests/test_speedscope.py b/tests/test_speedscope.py new file mode 100644 index 0000000..5dbd616 --- /dev/null +++ b/tests/test_speedscope.py @@ -0,0 +1,63 @@ +import json + +from speedscope_to_codeperf.schema.speedscope import get_cpu_by_function, get_flamegraph + + +def test_get_cpu_by_function(): + with open("./tests/two-sampled.speedscope.0.6.0.json", "r") as fd: + json_data = json.load(fd) + js = get_cpu_by_function(json_data) + assert js == { + "data": [ + {"symbol": "c", "flat%": "66.67", "cum%": "66.67"}, + {"symbol": "d", "flat%": "33.33", "cum%": "33.33"}, + {"symbol": "a", "flat%": "0.00", "cum%": "100.00"}, + {"symbol": "b", "flat%": "0.00", "cum%": "100.00"}, + ], + "totalRows": 4, + "totalPages": 1, + "labels": [], + } + with open("./tests/sampled.speedscope.json", "r") as fd: + json_data = json.load(fd) + js = get_cpu_by_function(json_data, "lines") + assert js == { + "data": [ + {"symbol": "c:1", "flat%": "66.67", "cum%": "66.67"}, + {"symbol": "c:2", "flat%": "33.33", "cum%": "33.33"}, + {"symbol": "a:1", "flat%": "0.00", "cum%": "100.00"}, + {"symbol": "b:1", "flat%": "0.00", "cum%": "100.00"}, + ], + "totalRows": 4, + "totalPages": 1, + "labels": [], + } + + +def test_get_flamegraph(): + with open("./tests/sampled.speedscope.json", "r") as fd: + json_data = json.load(fd) + flame = get_flamegraph(json_data) + assert flame == { + "n": "root", + "f": "root", + "v": 0, + "c": [ + { + "n": "a:1", + "f": "a:1", + "v": 0, + "c": [ + { + "n": "b:1", + "f": "b:1", + "v": 0, + "c": [ + {"n": "c:1", "f": "c:1", "v": 2, "c": []}, + {"n": "c:2", "f": "c:2", "v": 1, "c": []}, + ], + } + ], + } + ], + } diff --git a/tests/test_speedscope_to_codeperf.py b/tests/test_speedscope_to_codeperf.py new file mode 100644 index 0000000..09cd3dc --- /dev/null +++ b/tests/test_speedscope_to_codeperf.py @@ -0,0 +1,5 @@ +from speedscope_to_codeperf import __version__ + + +def test_version(): + assert __version__ == "0.1.0" diff --git a/tests/two-sampled.speedscope.0.6.0.json b/tests/two-sampled.speedscope.0.6.0.json new file mode 100644 index 0000000..f94314e --- /dev/null +++ b/tests/two-sampled.speedscope.0.6.0.json @@ -0,0 +1,19 @@ +{ + "exporter": "speedscope@0.6.0", + "$schema": "https://www.speedscope.app/file-format-schema.json", + "name": "Two Samples", + "activeProfileIndex": 1, + "profiles": [ + { + "type": "sampled", + "name": "one", + "unit": "seconds", + "startValue": 0, + "endValue": 14, + "samples": [[0, 1, 2], [0, 1, 2], [0, 1, 3]] + } + ], + "shared": { + "frames": [{"name": "a"}, {"name": "b"}, {"name": "c"}, {"name": "d"}] + } +} diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..2580985 --- /dev/null +++ b/tox.ini @@ -0,0 +1,15 @@ + +[tox] +isolated_build = True + +[testenv:integration-tests] +deps = -r{toxinidir}/tests/test-requirements.txt + +stoponfail = True + +commands = + black --check speedscope_to_codeperf + flake8 speedscope_to_codeperf + coverage erase + coverage run --include=speedscope_to_codeperf/* -m pytest -ra + coverage report -m