├── .github ├── screenshot.png └── workflows │ ├── container-build.yaml │ └── pylint.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .pylintrc ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── packaging ├── aur │ ├── Dockerfile │ └── build.sh └── make-release.sh ├── poetry.lock ├── pyproject.toml ├── sugarjazy ├── __init__.py └── cli.py ├── tests ├── __init__.py └── test_cli.py └── walkthrough.md /.github/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chmouel/sugarjazy/2a2f2cf0a7b580b22fd3bec35869fd430c3a0971/.github/screenshot.png -------------------------------------------------------------------------------- /.github/workflows/container-build.yaml: -------------------------------------------------------------------------------- 1 | name: Create and publish a Docker image to ghcr 2 | 3 | on: ["push"] 4 | 5 | env: 6 | REGISTRY: ghcr.io 7 | IMAGE_NAME: ${{ github.repository }} 8 | 9 | jobs: 10 | build-and-push-image: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | packages: write 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v3 19 | 20 | - name: Log in to the Container registry 21 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 22 | with: 23 | registry: ${{ env.REGISTRY }} 24 | username: ${{ github.actor }} 25 | password: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | - name: Extract metadata (tags, labels) for Docker 28 | id: meta 29 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 30 | with: 31 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 32 | 33 | - name: Build and push Docker image 34 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 35 | with: 36 | context: . 37 | push: true 38 | tags: ${{ steps.meta.outputs.tags }} 39 | labels: ${{ steps.meta.outputs.labels }} 40 | -------------------------------------------------------------------------------- /.github/workflows/pylint.yaml: -------------------------------------------------------------------------------- 1 | name: Python quality 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | pre-commit: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-python@v2 11 | - name: Install poetry pylint 12 | run: | 13 | pip3 install --upgrade poetry 14 | poetry install -v 15 | - uses: pre-commit/action@v2.0.3 16 | with: 17 | extra_args: --show-diff-on-failure --color=always --all-files --hook-stage=push 18 | - name: Generate code coverage 19 | run: | 20 | poetry run pytest --cov ./ --cov-report=xml --cov-fail-under 75 21 | - name: Upload coverage to Codecov 22 | uses: codecov/codecov-action@v2 23 | with: 24 | verbose: true 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_install_hook_types: ["pre-push"] 2 | default_stages: ["push"] 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v4.2.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-added-large-files 11 | - repo: local 12 | hooks: 13 | - id: system 14 | name: Black 15 | entry: poetry run black . 16 | pass_filenames: false 17 | language: system 18 | stages: ["push"] 19 | - id: system 20 | name: isort 21 | entry: poetry run isort . 22 | pass_filenames: false 23 | language: system 24 | stages: ["push"] 25 | - id: system 26 | name: MyPy 27 | entry: poetry run mypy sugarjazy tests 28 | pass_filenames: false 29 | language: system 30 | stages: ["push"] 31 | - id: system 32 | name: Pylint 33 | entry: poetry run pylint -j 0 sugarjazy tests 34 | pass_filenames: false 35 | language: system 36 | stages: ["push"] 37 | - id: system 38 | name: Pytest 39 | entry: poetry run pytest 40 | pass_filenames: false 41 | language: system 42 | stages: ["push"] 43 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | disable = C0413, 3 | E0401, 4 | E402, 5 | R0902, 6 | R0903, 7 | R0912, 8 | R0913, 9 | R0914, 10 | R0915, 11 | W0511, 12 | C0301, 13 | C0103, 14 | no-self-use, 15 | chained-comparison, 16 | unspecified-encoding, 17 | missing-docstring, 18 | 19 | # vim: ft=toml 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This Dockerfile uses multi-stage build to customize DEV and PROD images: 2 | # https://docs.docker.com/develop/develop-images/multistage-build/ 3 | 4 | FROM python:3.10-alpine 5 | 6 | COPY sugarjazy /code 7 | WORKDIR /code 8 | 9 | ENV PYTHONFAULTHANDLER=1 \ 10 | PYTHONUNBUFFERED=1 \ 11 | PYTHONHASHSEED=random \ 12 | PYTHONDONTWRITEBYTECODE=1 \ 13 | # pip: 14 | PIP_NO_CACHE_DIR=1 \ 15 | PIP_DISABLE_PIP_VERSION_CHECK=1 \ 16 | PIP_DEFAULT_TIMEOUT=100 17 | 18 | 19 | RUN pip3 install -U python-dateutil 20 | 21 | ENTRYPOINT ["python", "cli.py"] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test 2 | 3 | test: 4 | poetry run pytest --cov sugarjazy --cov-fail-under 75 5 | poetry run pylint ./sugarjazy ./tests 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Now archived and moved onto a rust implementation of this tool called: [snazy](https://github.com/chmouel/snazy) 2 | 3 | [![codecov](https://codecov.io/gh/chmouel/sugarjazy/branch/main/graph/badge.svg)](https://codecov.io/gh/chmouel/sugarjazy) [![PyPI](https://badge.fury.io/py/sugarjazy.svg)](https://badge.fury.io/py/sugarjazy) [![AUR](https://img.shields.io/aur/version/sugarjazy)](https://aur.archlinux.org/packages/sugarjazy) [![github workflow](https://github.com/chmouel/sugarjazy/actions/workflows/pylint.yaml/badge.svg)](https://github.com/chmouel/sugarjazy/actions/workflows/pylint.yaml) [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) 4 | 5 | # sugarjazy - parse json logs nicely 6 | 7 | sugarjazy is a simple tool to parse json logs and output them in a nice format with nice colors. 8 | 9 | Usually play nicely with when using the 10 | ["Sugar"](https://pkg.go.dev/go.uber.org/zap#Logger.Sugar) logger output. 11 | 12 | As a [`tekton`](http://tekton.dev) developer this works pretty well with tekton controllers and webhooks pods but 13 | the shoudld work as well with most knative package and other pods using go-uber/zap. 14 | 15 | ## Screenshot 16 | 17 | ### Default 18 | 19 | ![screenshot](./.github/screenshot.png) 20 | 21 | ### Stream from kail with sugarjazy 22 | 23 | https://user-images.githubusercontent.com/98980/159916310-fabaa48e-b92a-4a41-a935-a1cb2a31e8fe.mp4 24 | 25 | 26 | ## Installation 27 | 28 | There is not many dependencies on this package but [`python-dateutil`](https://dateutil.readthedocs.io/en/stable/) is an optional dependency, if the package is not installed you will not be be able to show the log timestamps. 29 | 30 | ### Arch 31 | 32 | You can install it [from aur](https://aur.archlinux.org/packages/sugarjazy) with your aurhelper, like yay : 33 | 34 | ``` 35 | yay -S sugarjazy 36 | ``` 37 | 38 | ### pip 39 | 40 | With pip from pypi - 41 | 42 | ``` 43 | pip install --user sugarjazy 44 | ``` 45 | 46 | (make sure $HOME/.local/bin is in your PATH) 47 | 48 | ### Docker 49 | 50 | ``` 51 | docker run ghcr.io/chmouel/sugarjazy:main 52 | ``` 53 | 54 | ### git clone 55 | 56 | you will need [poetry](https://python-poetry.org/) : 57 | 58 | ``` 59 | git clone https://github.com/chmouel/sugarjazy 60 | cd sugarjazy 61 | poetry run sugarjazy 62 | ``` 63 | 64 | ## Usage 65 | 66 | You can use `sugarjazy` in multiple ways : 67 | 68 | - By piping your logs: `kubectl logs podname|sugarjazy` 69 | - By streamining your logs: `kubectl logs -f podname|sugarjazy -f` 70 | - Directly to a file (or multiples files): `sugarjazy /tmp/file1.log /tmp/file2.log` 71 | - Using kail from https://github.com/boz/kail piping the output to `sugarjazy` with the `--kail` flag. The advantage of `kail` is to be able to get the logs from multiple pods and watching new events as they appears. 72 | - By default the prefix of the pod/container will be printed unless you specify 73 | the option `--kail-no-prefix`. 74 | - The prefix can be customized with `--kail-prefix-format` flag, the default template is : 75 | `{namespace}/{pod}[{container}]` 76 | If you want to see only the pod name you can simply do : 77 | 78 | `--kail-prefix-format="[{pod}]"` 79 | 80 | - The `--kail` flags always assume `--follow` implicitely. 81 | 82 | See walkthrough documentation [here](./walkthrough.md) 83 | 84 | ### Options 85 | 86 | ```shell 87 | usage: sugarjazy [-h] [--timeformat TIMEFORMAT] 88 | [--regexp-highlight REGEXP_HIGHLIGHT] 89 | [--disable-event-colouring] [--filter-level FILTER_LEVEL] 90 | [--stream] [--kail] [--kail-no-prefix] 91 | [--kail-prefix-format KAIL_PREFIX_FORMAT] 92 | [--regexp-color REGEXP_COLOR] [--hide-timestamp] 93 | [files ...] 94 | 95 | positional arguments: 96 | files 97 | 98 | options: 99 | -h, --help show this help message and exit 100 | --timeformat TIMEFORMAT 101 | timeformat default only to the hour:minute:second. Use 102 | "%Y-%m-%d %H:%M:%S" if you want to add the year 103 | --regexp-highlight REGEXP_HIGHLIGHT, -r REGEXP_HIGHLIGHT 104 | Highlight a regexp in message, eg: "Failed:\s*\d+, 105 | Cancelled\s*\d+" 106 | --disable-event-colouring 107 | By default sugarjazy will try to add a ˃ char with a 108 | color to the eventid to easily identify which event 109 | belongs to which. Use this option to disable it. 110 | --filter-level FILTER_LEVEL, -F FILTER_LEVEL 111 | filter levels separated by commas, eg: info,debug 112 | --stream, -s, -f, --follow 113 | wait for input stream 114 | --kail, -k assume streaming logs from kail 115 | (https://github.com/boz/kail) 116 | --kail-no-prefix by default kail will print the prefix unless you 117 | specify this flag 118 | --kail-prefix-format KAIL_PREFIX_FORMAT 119 | the template of the kail prefix. 120 | --regexp-color REGEXP_COLOR 121 | Regexp highlight color 122 | --hide-timestamp, -H don't show timestamp 123 | ``` 124 | 125 | ## *`NOTE`* 126 | 127 | - Sugarjazy tries hard to identify the same event and add all events on the same colors to the chevron character (˃). 128 | - The json fields are not standardize. It works well with `knative` based 129 | controllers like `tekton` or others but that may be buggy for other ones. 130 | 131 | ## FAQ 132 | 133 | - sugarjazy is along word to type and why did the name anyway? 134 | - : I agree! and that's why I alias it with `alias sj=sugarjazy` or even better I have zsh [global alias](https://vonheikemen.github.io/devlog/tools/zsh-global-aliases/) `alias -g SJ=| sugarjazy -s` to avoid even more typing. 135 | 136 | ## Copyright 137 | 138 | [Apache-2.0](./LICENSE) 139 | 140 | ## Authors 141 | 142 | Chmouel Boudjnah <[@chmouel](https://twitter.com/chmouel)> 143 | -------------------------------------------------------------------------------- /packaging/aur/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM archlinux:latest 2 | RUN pacman -Sy python-setuptools python-dephell pacman-contrib openssh binutils git --noconfirm && pacman -Sc --noconfirm 3 | 4 | RUN useradd -ms /bin/bash builder 5 | USER builder 6 | WORKDIR /src 7 | -------------------------------------------------------------------------------- /packaging/aur/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | POETRY_NAME_VERSION="$(poetry version)" 6 | PKGNAME=${POETRY_NAME_VERSION% *} 7 | AUTHOR_EMAIL="$(git config --get user.email)" 8 | AUTHOR_NAME="$(git config --get user.name)" 9 | RELEASE=1 10 | image_name=${PKGNAME}-aur-builder 11 | finalaction="git push origin master" 12 | gitdir=$(git rev-parse --show-toplevel) 13 | cd "${gitdir}" 14 | 15 | while getopts "d" o; do 16 | case "${o}" in 17 | d) 18 | finalaction="echo done" 19 | ;; 20 | *) 21 | echo "Invalid option"; exit 1; 22 | ;; 23 | esac 24 | done 25 | shift $((OPTIND-1)) 26 | 27 | VERSION=${1:-${POETRY_NAME_VERSION#* }} 28 | 29 | SUDO=sudo 30 | [[ ${OSTYPE:-""} == darwin* ]] && SUDO= 31 | 32 | ${SUDO} docker build -f ./packaging/aur/Dockerfile -t ${image_name} . 33 | 34 | ${SUDO} docker run --rm \ 35 | -v ~/.config/copr:/home/builder/.config/copr \ 36 | -v "${gitdir}":/src \ 37 | -v $SSH_AUTH_SOCK:/ssh-agent --env SSH_AUTH_SOCK=/ssh-agent \ 38 | --name ${PKGNAME}-builder \ 39 | -it ${image_name} \ 40 | /bin/bash -c "set -x;mkdir -p ~/.ssh/;chmod 0700 ~/.ssh && \ 41 | ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts && \ 42 | git clone --depth=1 ssh://aur@aur.archlinux.org/${PKGNAME} /tmp/${PKGNAME} && \ 43 | cd /tmp/${PKGNAME} && \ 44 | git config --global user.email '${AUTHOR_EMAIL}' && \ 45 | git config --global user.name '${AUTHOR_NAME}' && \ 46 | sed -E 's#(pkgver=).*#\1$VERSION#' -i PKGBUILD && \ 47 | sed -E 's#(pkgrel=).*#\1$RELEASE#' -i PKGBUILD && \ 48 | updpkgsums && \ 49 | makepkg --printsrcinfo > .SRCINFO && \ 50 | git clean -f && \ 51 | git commit -v -m 'Update to ${VERSION}' .SRCINFO PKGBUILD && \ 52 | ${finalaction}" 53 | -------------------------------------------------------------------------------- /packaging/make-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euf 3 | VERSION=${1-""} 4 | POETRY_NAME_VERSION="$(poetry version)" 5 | PKGNAME=${POETRY_NAME_VERSION% *} 6 | 7 | docker ps -q >/dev/null || exit 1 8 | 9 | bumpversion() { 10 | current=$(git describe --tags $(git rev-list --tags --max-count=1)) 11 | echo "Current version is ${current}" 12 | 13 | major=$(python3 -c "import semver,sys;print(str(semver.VersionInfo.parse(sys.argv[1]).bump_major()))" ${current}) 14 | minor=$(python3 -c "import semver,sys;print(str(semver.VersionInfo.parse(sys.argv[1]).bump_minor()))" ${current}) 15 | patch=$(python3 -c "import semver,sys;print(str(semver.VersionInfo.parse(sys.argv[1]).bump_patch()))" ${current}) 16 | 17 | echo "If we bump we get, Major: ${major} Minor: ${minor} Patch: ${patch}" 18 | read -p "To which version you would like to bump [M]ajor, Mi[n]or, [P]atch or Manua[l]: " ANSWER 19 | if [[ ${ANSWER,,} == "m" ]];then 20 | mode="major" 21 | elif [[ ${ANSWER,,} == "n" ]];then 22 | mode="minor" 23 | elif [[ ${ANSWER,,} == "p" ]];then 24 | mode="patch" 25 | elif [[ ${ANSWER,,} == "l" ]];then 26 | read -p "Enter version: " -e VERSION 27 | return 28 | else 29 | print "no or bad reply??" 30 | exit 31 | fi 32 | VERSION=$(python3 -c "import semver,sys;print(str(semver.VersionInfo.parse(sys.argv[1]).bump_${mode}()))" ${current}) 33 | [[ -z ${VERSION} ]] && { 34 | echo "could not bump version automatically" 35 | exit 36 | } 37 | echo "Releasing ${VERSION}" 38 | } 39 | 40 | [[ $(git rev-parse --abbrev-ref HEAD) != main ]] && { 41 | echo "you need to be on the main branch" 42 | exit 1 43 | } 44 | [[ -z ${VERSION} ]] && bumpversion 45 | 46 | vfile=pyproject.toml 47 | sed -i "s/^version = .*/version = \"${VERSION}\"/" ${vfile} 48 | git commit -S -m "Release ${VERSION} 🥳" ${vfile} || true 49 | git tag -s ${VERSION} -m "Releasing version ${VERSION}" 50 | git push --tags origin ${VERSION} 51 | git push origin main --no-verify 52 | poetry build -f sdist 53 | gh release create ${VERSION} --notes "Release ${VERSION} 🥳" ./dist/${PKGNAME}-${VERSION}.tar.gz 54 | poetry publish -u __token__ -p $(pass show pypi/token) 55 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "astroid" 3 | version = "2.11.2" 4 | description = "An abstract syntax tree for Python with inference support." 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=3.6.2" 8 | 9 | [package.dependencies] 10 | lazy-object-proxy = ">=1.4.0" 11 | typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} 12 | typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} 13 | wrapt = ">=1.11,<2" 14 | 15 | [[package]] 16 | name = "atomicwrites" 17 | version = "1.4.0" 18 | description = "Atomic file writes." 19 | category = "dev" 20 | optional = false 21 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 22 | 23 | [[package]] 24 | name = "attrs" 25 | version = "21.4.0" 26 | description = "Classes Without Boilerplate" 27 | category = "dev" 28 | optional = false 29 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 30 | 31 | [package.extras] 32 | 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"] 33 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 34 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] 35 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] 36 | 37 | [[package]] 38 | name = "black" 39 | version = "22.3.0" 40 | description = "The uncompromising code formatter." 41 | category = "dev" 42 | optional = false 43 | python-versions = ">=3.6.2" 44 | 45 | [package.dependencies] 46 | click = ">=8.0.0" 47 | mypy-extensions = ">=0.4.3" 48 | pathspec = ">=0.9.0" 49 | platformdirs = ">=2" 50 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 51 | typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} 52 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} 53 | 54 | [package.extras] 55 | colorama = ["colorama (>=0.4.3)"] 56 | d = ["aiohttp (>=3.7.4)"] 57 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 58 | uvloop = ["uvloop (>=0.15.2)"] 59 | 60 | [[package]] 61 | name = "click" 62 | version = "8.1.2" 63 | description = "Composable command line interface toolkit" 64 | category = "dev" 65 | optional = false 66 | python-versions = ">=3.7" 67 | 68 | [package.dependencies] 69 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 70 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 71 | 72 | [[package]] 73 | name = "colorama" 74 | version = "0.4.4" 75 | description = "Cross-platform colored terminal text." 76 | category = "dev" 77 | optional = false 78 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 79 | 80 | [[package]] 81 | name = "coverage" 82 | version = "6.3.2" 83 | description = "Code coverage measurement for Python" 84 | category = "dev" 85 | optional = false 86 | python-versions = ">=3.7" 87 | 88 | [package.dependencies] 89 | tomli = {version = "*", optional = true, markers = "extra == \"toml\""} 90 | 91 | [package.extras] 92 | toml = ["tomli"] 93 | 94 | [[package]] 95 | name = "dill" 96 | version = "0.3.4" 97 | description = "serialize all of python" 98 | category = "dev" 99 | optional = false 100 | python-versions = ">=2.7, !=3.0.*" 101 | 102 | [package.extras] 103 | graph = ["objgraph (>=1.7.2)"] 104 | 105 | [[package]] 106 | name = "importlib-metadata" 107 | version = "4.11.3" 108 | description = "Read metadata from Python packages" 109 | category = "dev" 110 | optional = false 111 | python-versions = ">=3.7" 112 | 113 | [package.dependencies] 114 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 115 | zipp = ">=0.5" 116 | 117 | [package.extras] 118 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 119 | perf = ["ipython"] 120 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] 121 | 122 | [[package]] 123 | name = "iniconfig" 124 | version = "1.1.1" 125 | description = "iniconfig: brain-dead simple config-ini parsing" 126 | category = "dev" 127 | optional = false 128 | python-versions = "*" 129 | 130 | [[package]] 131 | name = "isort" 132 | version = "5.10.1" 133 | description = "A Python utility / library to sort Python imports." 134 | category = "dev" 135 | optional = false 136 | python-versions = ">=3.6.1,<4.0" 137 | 138 | [package.extras] 139 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 140 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 141 | colors = ["colorama (>=0.4.3,<0.5.0)"] 142 | plugins = ["setuptools"] 143 | 144 | [[package]] 145 | name = "lazy-object-proxy" 146 | version = "1.7.1" 147 | description = "A fast and thorough lazy object proxy." 148 | category = "dev" 149 | optional = false 150 | python-versions = ">=3.6" 151 | 152 | [[package]] 153 | name = "mccabe" 154 | version = "0.7.0" 155 | description = "McCabe checker, plugin for flake8" 156 | category = "dev" 157 | optional = false 158 | python-versions = ">=3.6" 159 | 160 | [[package]] 161 | name = "mypy" 162 | version = "0.942" 163 | description = "Optional static typing for Python" 164 | category = "dev" 165 | optional = false 166 | python-versions = ">=3.6" 167 | 168 | [package.dependencies] 169 | mypy-extensions = ">=0.4.3" 170 | tomli = ">=1.1.0" 171 | typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} 172 | typing-extensions = ">=3.10" 173 | 174 | [package.extras] 175 | dmypy = ["psutil (>=4.0)"] 176 | python2 = ["typed-ast (>=1.4.0,<2)"] 177 | reports = ["lxml"] 178 | 179 | [[package]] 180 | name = "mypy-extensions" 181 | version = "0.4.3" 182 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 183 | category = "dev" 184 | optional = false 185 | python-versions = "*" 186 | 187 | [[package]] 188 | name = "packaging" 189 | version = "21.3" 190 | description = "Core utilities for Python packages" 191 | category = "dev" 192 | optional = false 193 | python-versions = ">=3.6" 194 | 195 | [package.dependencies] 196 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 197 | 198 | [[package]] 199 | name = "pathspec" 200 | version = "0.9.0" 201 | description = "Utility library for gitignore style pattern matching of file paths." 202 | category = "dev" 203 | optional = false 204 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 205 | 206 | [[package]] 207 | name = "platformdirs" 208 | version = "2.5.1" 209 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 210 | category = "dev" 211 | optional = false 212 | python-versions = ">=3.7" 213 | 214 | [package.extras] 215 | docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] 216 | test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] 217 | 218 | [[package]] 219 | name = "pluggy" 220 | version = "1.0.0" 221 | description = "plugin and hook calling mechanisms for python" 222 | category = "dev" 223 | optional = false 224 | python-versions = ">=3.6" 225 | 226 | [package.dependencies] 227 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 228 | 229 | [package.extras] 230 | dev = ["pre-commit", "tox"] 231 | testing = ["pytest", "pytest-benchmark"] 232 | 233 | [[package]] 234 | name = "py" 235 | version = "1.11.0" 236 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 237 | category = "dev" 238 | optional = false 239 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 240 | 241 | [[package]] 242 | name = "pylint" 243 | version = "2.13.5" 244 | description = "python code static checker" 245 | category = "dev" 246 | optional = false 247 | python-versions = ">=3.6.2" 248 | 249 | [package.dependencies] 250 | astroid = ">=2.11.2,<=2.12.0-dev0" 251 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 252 | dill = ">=0.2" 253 | isort = ">=4.2.5,<6" 254 | mccabe = ">=0.6,<0.8" 255 | platformdirs = ">=2.2.0" 256 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 257 | typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} 258 | 259 | [package.extras] 260 | testutil = ["gitpython (>3)"] 261 | 262 | [[package]] 263 | name = "pyparsing" 264 | version = "3.0.8" 265 | description = "pyparsing module - Classes and methods to define and execute parsing grammars" 266 | category = "dev" 267 | optional = false 268 | python-versions = ">=3.6.8" 269 | 270 | [package.extras] 271 | diagrams = ["railroad-diagrams", "jinja2"] 272 | 273 | [[package]] 274 | name = "pytest" 275 | version = "7.1.1" 276 | description = "pytest: simple powerful testing with Python" 277 | category = "dev" 278 | optional = false 279 | python-versions = ">=3.7" 280 | 281 | [package.dependencies] 282 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 283 | attrs = ">=19.2.0" 284 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 285 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 286 | iniconfig = "*" 287 | packaging = "*" 288 | pluggy = ">=0.12,<2.0" 289 | py = ">=1.8.2" 290 | tomli = ">=1.0.0" 291 | 292 | [package.extras] 293 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 294 | 295 | [[package]] 296 | name = "pytest-cov" 297 | version = "3.0.0" 298 | description = "Pytest plugin for measuring coverage." 299 | category = "dev" 300 | optional = false 301 | python-versions = ">=3.6" 302 | 303 | [package.dependencies] 304 | coverage = {version = ">=5.2.1", extras = ["toml"]} 305 | pytest = ">=4.6" 306 | 307 | [package.extras] 308 | testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] 309 | 310 | [[package]] 311 | name = "python-dateutil" 312 | version = "2.8.2" 313 | description = "Extensions to the standard Python datetime module" 314 | category = "dev" 315 | optional = false 316 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 317 | 318 | [package.dependencies] 319 | six = ">=1.5" 320 | 321 | [[package]] 322 | name = "six" 323 | version = "1.16.0" 324 | description = "Python 2 and 3 compatibility utilities" 325 | category = "dev" 326 | optional = false 327 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 328 | 329 | [[package]] 330 | name = "tomli" 331 | version = "2.0.1" 332 | description = "A lil' TOML parser" 333 | category = "dev" 334 | optional = false 335 | python-versions = ">=3.7" 336 | 337 | [[package]] 338 | name = "typed-ast" 339 | version = "1.5.2" 340 | description = "a fork of Python 2 and 3 ast modules with type comment support" 341 | category = "dev" 342 | optional = false 343 | python-versions = ">=3.6" 344 | 345 | [[package]] 346 | name = "types-python-dateutil" 347 | version = "2.8.10" 348 | description = "Typing stubs for python-dateutil" 349 | category = "dev" 350 | optional = false 351 | python-versions = "*" 352 | 353 | [[package]] 354 | name = "typing-extensions" 355 | version = "4.1.1" 356 | description = "Backported and Experimental Type Hints for Python 3.6+" 357 | category = "dev" 358 | optional = false 359 | python-versions = ">=3.6" 360 | 361 | [[package]] 362 | name = "wrapt" 363 | version = "1.14.0" 364 | description = "Module for decorators, wrappers and monkey patching." 365 | category = "dev" 366 | optional = false 367 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 368 | 369 | [[package]] 370 | name = "zipp" 371 | version = "3.8.0" 372 | description = "Backport of pathlib-compatible object wrapper for zip files" 373 | category = "dev" 374 | optional = false 375 | python-versions = ">=3.7" 376 | 377 | [package.extras] 378 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 379 | testing = ["pytest (>=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 (>=0.9.1)"] 380 | 381 | [metadata] 382 | lock-version = "1.1" 383 | python-versions = "^3.7" 384 | content-hash = "ae5c6bfd7034302f7f7f860e887d74e13f31bd442144653281a91a1431ad7ad9" 385 | 386 | [metadata.files] 387 | astroid = [ 388 | {file = "astroid-2.11.2-py3-none-any.whl", hash = "sha256:cc8cc0d2d916c42d0a7c476c57550a4557a083081976bf42a73414322a6411d9"}, 389 | {file = "astroid-2.11.2.tar.gz", hash = "sha256:8d0a30fe6481ce919f56690076eafbb2fb649142a89dc874f1ec0e7a011492d0"}, 390 | ] 391 | atomicwrites = [ 392 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 393 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 394 | ] 395 | attrs = [ 396 | {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, 397 | {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, 398 | ] 399 | black = [ 400 | {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, 401 | {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, 402 | {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, 403 | {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, 404 | {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, 405 | {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, 406 | {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, 407 | {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, 408 | {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, 409 | {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, 410 | {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, 411 | {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, 412 | {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, 413 | {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, 414 | {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, 415 | {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, 416 | {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, 417 | {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, 418 | {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, 419 | {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, 420 | {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, 421 | {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, 422 | {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, 423 | ] 424 | click = [ 425 | {file = "click-8.1.2-py3-none-any.whl", hash = "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e"}, 426 | {file = "click-8.1.2.tar.gz", hash = "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"}, 427 | ] 428 | colorama = [ 429 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 430 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 431 | ] 432 | coverage = [ 433 | {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, 434 | {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, 435 | {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, 436 | {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, 437 | {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"}, 438 | {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, 439 | {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, 440 | {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, 441 | {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, 442 | {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, 443 | {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, 444 | {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, 445 | {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, 446 | {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"}, 447 | {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, 448 | {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, 449 | {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, 450 | {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, 451 | {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, 452 | {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, 453 | {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, 454 | {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, 455 | {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, 456 | {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"}, 457 | {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, 458 | {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, 459 | {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, 460 | {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, 461 | {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, 462 | {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, 463 | {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, 464 | {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, 465 | {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, 466 | {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"}, 467 | {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, 468 | {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, 469 | {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, 470 | {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, 471 | {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, 472 | {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, 473 | {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, 474 | ] 475 | dill = [ 476 | {file = "dill-0.3.4-py2.py3-none-any.whl", hash = "sha256:7e40e4a70304fd9ceab3535d36e58791d9c4a776b38ec7f7ec9afc8d3dca4d4f"}, 477 | {file = "dill-0.3.4.zip", hash = "sha256:9f9734205146b2b353ab3fec9af0070237b6ddae78452af83d2fca84d739e675"}, 478 | ] 479 | importlib-metadata = [ 480 | {file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"}, 481 | {file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"}, 482 | ] 483 | iniconfig = [ 484 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 485 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 486 | ] 487 | isort = [ 488 | {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, 489 | {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, 490 | ] 491 | lazy-object-proxy = [ 492 | {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, 493 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, 494 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"}, 495 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"}, 496 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"}, 497 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"}, 498 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"}, 499 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"}, 500 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"}, 501 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"}, 502 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"}, 503 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"}, 504 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"}, 505 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"}, 506 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"}, 507 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"}, 508 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"}, 509 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"}, 510 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"}, 511 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"}, 512 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"}, 513 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"}, 514 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"}, 515 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"}, 516 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"}, 517 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"}, 518 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"}, 519 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"}, 520 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"}, 521 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"}, 522 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"}, 523 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"}, 524 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"}, 525 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"}, 526 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"}, 527 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, 528 | {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, 529 | ] 530 | mccabe = [ 531 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 532 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 533 | ] 534 | mypy = [ 535 | {file = "mypy-0.942-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5bf44840fb43ac4074636fd47ee476d73f0039f4f54e86d7265077dc199be24d"}, 536 | {file = "mypy-0.942-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dcd955f36e0180258a96f880348fbca54ce092b40fbb4b37372ae3b25a0b0a46"}, 537 | {file = "mypy-0.942-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6776e5fa22381cc761df53e7496a805801c1a751b27b99a9ff2f0ca848c7eca0"}, 538 | {file = "mypy-0.942-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:edf7237137a1a9330046dbb14796963d734dd740a98d5e144a3eb1d267f5f9ee"}, 539 | {file = "mypy-0.942-cp310-cp310-win_amd64.whl", hash = "sha256:64235137edc16bee6f095aba73be5334677d6f6bdb7fa03cfab90164fa294a17"}, 540 | {file = "mypy-0.942-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b840cfe89c4ab6386c40300689cd8645fc8d2d5f20101c7f8bd23d15fca14904"}, 541 | {file = "mypy-0.942-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b184db8c618c43c3a31b32ff00cd28195d39e9c24e7c3b401f3db7f6e5767f5"}, 542 | {file = "mypy-0.942-cp36-cp36m-win_amd64.whl", hash = "sha256:1a0459c333f00e6a11cbf6b468b870c2b99a906cb72d6eadf3d1d95d38c9352c"}, 543 | {file = "mypy-0.942-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c3e497588afccfa4334a9986b56f703e75793133c4be3a02d06a3df16b67a58"}, 544 | {file = "mypy-0.942-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f6ad963172152e112b87cc7ec103ba0f2db2f1cd8997237827c052a3903eaa6"}, 545 | {file = "mypy-0.942-cp37-cp37m-win_amd64.whl", hash = "sha256:0e2dd88410937423fba18e57147dd07cd8381291b93d5b1984626f173a26543e"}, 546 | {file = "mypy-0.942-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:246e1aa127d5b78488a4a0594bd95f6d6fb9d63cf08a66dafbff8595d8891f67"}, 547 | {file = "mypy-0.942-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8d3ba77e56b84cd47a8ee45b62c84b6d80d32383928fe2548c9a124ea0a725c"}, 548 | {file = "mypy-0.942-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2bc249409a7168d37c658e062e1ab5173300984a2dada2589638568ddc1db02b"}, 549 | {file = "mypy-0.942-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9521c1265ccaaa1791d2c13582f06facf815f426cd8b07c3a485f486a8ffc1f3"}, 550 | {file = "mypy-0.942-cp38-cp38-win_amd64.whl", hash = "sha256:e865fec858d75b78b4d63266c9aff770ecb6a39dfb6d6b56c47f7f8aba6baba8"}, 551 | {file = "mypy-0.942-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ce34a118d1a898f47def970a2042b8af6bdcc01546454726c7dd2171aa6dfca"}, 552 | {file = "mypy-0.942-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:10daab80bc40f84e3f087d896cdb53dc811a9f04eae4b3f95779c26edee89d16"}, 553 | {file = "mypy-0.942-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3841b5433ff936bff2f4dc8d54cf2cdbfea5d8e88cedfac45c161368e5770ba6"}, 554 | {file = "mypy-0.942-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f7106cbf9cc2f403693bf50ed7c9fa5bb3dfa9007b240db3c910929abe2a322"}, 555 | {file = "mypy-0.942-cp39-cp39-win_amd64.whl", hash = "sha256:7742d2c4e46bb5017b51c810283a6a389296cda03df805a4f7869a6f41246534"}, 556 | {file = "mypy-0.942-py3-none-any.whl", hash = "sha256:a1b383fe99678d7402754fe90448d4037f9512ce70c21f8aee3b8bf48ffc51db"}, 557 | {file = "mypy-0.942.tar.gz", hash = "sha256:17e44649fec92e9f82102b48a3bf7b4a5510ad0cd22fa21a104826b5db4903e2"}, 558 | ] 559 | mypy-extensions = [ 560 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 561 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 562 | ] 563 | packaging = [ 564 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 565 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 566 | ] 567 | pathspec = [ 568 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, 569 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, 570 | ] 571 | platformdirs = [ 572 | {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, 573 | {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, 574 | ] 575 | pluggy = [ 576 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 577 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 578 | ] 579 | py = [ 580 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 581 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 582 | ] 583 | pylint = [ 584 | {file = "pylint-2.13.5-py3-none-any.whl", hash = "sha256:c149694cfdeaee1aa2465e6eaab84c87a881a7d55e6e93e09466be7164764d1e"}, 585 | {file = "pylint-2.13.5.tar.gz", hash = "sha256:dab221658368c7a05242e673c275c488670144123f4bd262b2777249c1c0de9b"}, 586 | ] 587 | pyparsing = [ 588 | {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, 589 | {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, 590 | ] 591 | pytest = [ 592 | {file = "pytest-7.1.1-py3-none-any.whl", hash = "sha256:92f723789a8fdd7180b6b06483874feca4c48a5c76968e03bb3e7f806a1869ea"}, 593 | {file = "pytest-7.1.1.tar.gz", hash = "sha256:841132caef6b1ad17a9afde46dc4f6cfa59a05f9555aae5151f73bdf2820ca63"}, 594 | ] 595 | pytest-cov = [ 596 | {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, 597 | {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, 598 | ] 599 | python-dateutil = [ 600 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 601 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 602 | ] 603 | six = [ 604 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 605 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 606 | ] 607 | tomli = [ 608 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 609 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 610 | ] 611 | typed-ast = [ 612 | {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, 613 | {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, 614 | {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, 615 | {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, 616 | {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, 617 | {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, 618 | {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, 619 | {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, 620 | {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, 621 | {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, 622 | {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, 623 | {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, 624 | {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, 625 | {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, 626 | {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, 627 | {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, 628 | {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, 629 | {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, 630 | {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, 631 | {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, 632 | {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, 633 | {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, 634 | {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, 635 | {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, 636 | ] 637 | types-python-dateutil = [ 638 | {file = "types-python-dateutil-2.8.10.tar.gz", hash = "sha256:6bcf3aae7242e5793bafd7b2bcfb4e255eb7b2b3144acd0df0e182dce58ccad3"}, 639 | {file = "types_python_dateutil-2.8.10-py3-none-any.whl", hash = "sha256:1f6d2305513d54da353a9dde7ed8a9ef46e8987377291612a0e2b9aac2d2b875"}, 640 | ] 641 | typing-extensions = [ 642 | {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, 643 | {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, 644 | ] 645 | wrapt = [ 646 | {file = "wrapt-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7"}, 647 | {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9a3ff5fb015f6feb78340143584d9f8a0b91b6293d6b5cf4295b3e95d179b88c"}, 648 | {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4b847029e2d5e11fd536c9ac3136ddc3f54bc9488a75ef7d040a3900406a91eb"}, 649 | {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:9a5a544861b21e0e7575b6023adebe7a8c6321127bb1d238eb40d99803a0e8bd"}, 650 | {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:88236b90dda77f0394f878324cfbae05ae6fde8a84d548cfe73a75278d760291"}, 651 | {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f0408e2dbad9e82b4c960274214af533f856a199c9274bd4aff55d4634dedc33"}, 652 | {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9d8c68c4145041b4eeae96239802cfdfd9ef927754a5be3f50505f09f309d8c6"}, 653 | {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:22626dca56fd7f55a0733e604f1027277eb0f4f3d95ff28f15d27ac25a45f71b"}, 654 | {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:65bf3eb34721bf18b5a021a1ad7aa05947a1767d1aa272b725728014475ea7d5"}, 655 | {file = "wrapt-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09d16ae7a13cff43660155383a2372b4aa09109c7127aa3f24c3cf99b891c330"}, 656 | {file = "wrapt-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:debaf04f813ada978d7d16c7dfa16f3c9c2ec9adf4656efdc4defdf841fc2f0c"}, 657 | {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748df39ed634851350efa87690c2237a678ed794fe9ede3f0d79f071ee042561"}, 658 | {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1807054aa7b61ad8d8103b3b30c9764de2e9d0c0978e9d3fc337e4e74bf25faa"}, 659 | {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763a73ab377390e2af26042f685a26787c402390f682443727b847e9496e4a2a"}, 660 | {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8529b07b49b2d89d6917cfa157d3ea1dfb4d319d51e23030664a827fe5fd2131"}, 661 | {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:68aeefac31c1f73949662ba8affaf9950b9938b712fb9d428fa2a07e40ee57f8"}, 662 | {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59d7d92cee84a547d91267f0fea381c363121d70fe90b12cd88241bd9b0e1763"}, 663 | {file = "wrapt-1.14.0-cp310-cp310-win32.whl", hash = "sha256:3a88254881e8a8c4784ecc9cb2249ff757fd94b911d5df9a5984961b96113fff"}, 664 | {file = "wrapt-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a242871b3d8eecc56d350e5e03ea1854de47b17f040446da0e47dc3e0b9ad4d"}, 665 | {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a65bffd24409454b889af33b6c49d0d9bcd1a219b972fba975ac935f17bdf627"}, 666 | {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9d9fcd06c952efa4b6b95f3d788a819b7f33d11bea377be6b8980c95e7d10775"}, 667 | {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:db6a0ddc1282ceb9032e41853e659c9b638789be38e5b8ad7498caac00231c23"}, 668 | {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:14e7e2c5f5fca67e9a6d5f753d21f138398cad2b1159913ec9e9a67745f09ba3"}, 669 | {file = "wrapt-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:6d9810d4f697d58fd66039ab959e6d37e63ab377008ef1d63904df25956c7db0"}, 670 | {file = "wrapt-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:d808a5a5411982a09fef6b49aac62986274ab050e9d3e9817ad65b2791ed1425"}, 671 | {file = "wrapt-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b77159d9862374da213f741af0c361720200ab7ad21b9f12556e0eb95912cd48"}, 672 | {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36a76a7527df8583112b24adc01748cd51a2d14e905b337a6fefa8b96fc708fb"}, 673 | {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0057b5435a65b933cbf5d859cd4956624df37b8bf0917c71756e4b3d9958b9e"}, 674 | {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0a4ca02752ced5f37498827e49c414d694ad7cf451ee850e3ff160f2bee9d3"}, 675 | {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8c6be72eac3c14baa473620e04f74186c5d8f45d80f8f2b4eda6e1d18af808e8"}, 676 | {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:21b1106bff6ece8cb203ef45b4f5778d7226c941c83aaaa1e1f0f4f32cc148cd"}, 677 | {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:493da1f8b1bb8a623c16552fb4a1e164c0200447eb83d3f68b44315ead3f9036"}, 678 | {file = "wrapt-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:89ba3d548ee1e6291a20f3c7380c92f71e358ce8b9e48161401e087e0bc740f8"}, 679 | {file = "wrapt-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:729d5e96566f44fccac6c4447ec2332636b4fe273f03da128fff8d5559782b06"}, 680 | {file = "wrapt-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:891c353e95bb11abb548ca95c8b98050f3620a7378332eb90d6acdef35b401d4"}, 681 | {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23f96134a3aa24cc50614920cc087e22f87439053d886e474638c68c8d15dc80"}, 682 | {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6807bcee549a8cb2f38f73f469703a1d8d5d990815c3004f21ddb68a567385ce"}, 683 | {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6915682f9a9bc4cf2908e83caf5895a685da1fbd20b6d485dafb8e218a338279"}, 684 | {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f2f3bc7cd9c9fcd39143f11342eb5963317bd54ecc98e3650ca22704b69d9653"}, 685 | {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3a71dbd792cc7a3d772ef8cd08d3048593f13d6f40a11f3427c000cf0a5b36a0"}, 686 | {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5a0898a640559dec00f3614ffb11d97a2666ee9a2a6bad1259c9facd01a1d4d9"}, 687 | {file = "wrapt-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:167e4793dc987f77fd476862d32fa404d42b71f6a85d3b38cbce711dba5e6b68"}, 688 | {file = "wrapt-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d066ffc5ed0be00cd0352c95800a519cf9e4b5dd34a028d301bdc7177c72daf3"}, 689 | {file = "wrapt-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d9bdfa74d369256e4218000a629978590fd7cb6cf6893251dad13d051090436d"}, 690 | {file = "wrapt-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2498762814dd7dd2a1d0248eda2afbc3dd9c11537bc8200a4b21789b6df6cd38"}, 691 | {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f24ca7953f2643d59a9c87d6e272d8adddd4a53bb62b9208f36db408d7aafc7"}, 692 | {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b835b86bd5a1bdbe257d610eecab07bf685b1af2a7563093e0e69180c1d4af1"}, 693 | {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b21650fa6907e523869e0396c5bd591cc326e5c1dd594dcdccac089561cacfb8"}, 694 | {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:354d9fc6b1e44750e2a67b4b108841f5f5ea08853453ecbf44c81fdc2e0d50bd"}, 695 | {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f83e9c21cd5275991076b2ba1cd35418af3504667affb4745b48937e214bafe"}, 696 | {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61e1a064906ccba038aa3c4a5a82f6199749efbbb3cef0804ae5c37f550eded0"}, 697 | {file = "wrapt-1.14.0-cp38-cp38-win32.whl", hash = "sha256:28c659878f684365d53cf59dc9a1929ea2eecd7ac65da762be8b1ba193f7e84f"}, 698 | {file = "wrapt-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:b0ed6ad6c9640671689c2dbe6244680fe8b897c08fd1fab2228429b66c518e5e"}, 699 | {file = "wrapt-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3f7e671fb19734c872566e57ce7fc235fa953d7c181bb4ef138e17d607dc8a1"}, 700 | {file = "wrapt-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87fa943e8bbe40c8c1ba4086971a6fefbf75e9991217c55ed1bcb2f1985bd3d4"}, 701 | {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4775a574e9d84e0212f5b18886cace049a42e13e12009bb0491562a48bb2b758"}, 702 | {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d57677238a0c5411c76097b8b93bdebb02eb845814c90f0b01727527a179e4d"}, 703 | {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00108411e0f34c52ce16f81f1d308a571df7784932cc7491d1e94be2ee93374b"}, 704 | {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d332eecf307fca852d02b63f35a7872de32d5ba8b4ec32da82f45df986b39ff6"}, 705 | {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:01f799def9b96a8ec1ef6b9c1bbaf2bbc859b87545efbecc4a78faea13d0e3a0"}, 706 | {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47045ed35481e857918ae78b54891fac0c1d197f22c95778e66302668309336c"}, 707 | {file = "wrapt-1.14.0-cp39-cp39-win32.whl", hash = "sha256:2eca15d6b947cfff51ed76b2d60fd172c6ecd418ddab1c5126032d27f74bc350"}, 708 | {file = "wrapt-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:bb36fbb48b22985d13a6b496ea5fb9bb2a076fea943831643836c9f6febbcfdc"}, 709 | {file = "wrapt-1.14.0.tar.gz", hash = "sha256:8323a43bd9c91f62bb7d4be74cc9ff10090e7ef820e27bfe8815c57e68261311"}, 710 | ] 711 | zipp = [ 712 | {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, 713 | {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, 714 | ] 715 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "sugarjazy" 3 | version = "0.7.1" 4 | description = "Parse json logs output from uber-go/zap library nicely" 5 | authors = ["Chmouel Boudjnah "] 6 | maintainers = ["Chmouel Boudjnah "] 7 | readme = "README.md" 8 | license = "Apache-2.0" 9 | keywords = ["logs", "tekton", "kubernetes"] 10 | homepage = "https://github.com/chmouel/sugarjazy" 11 | repository = "https://github.com/chmouel/sugarjazy" 12 | classifiers = [ 13 | "License :: OSI Approved :: Apache Software License", 14 | ] 15 | 16 | include = [ 17 | "LICENSE" 18 | ] 19 | 20 | [tool.poetry.dependencies] 21 | python = "^3.7" 22 | 23 | [tool.poetry.dev-dependencies] 24 | pytest = "^7.1" 25 | python-dateutil = "^2.8.0" 26 | pylint = "*" 27 | mypy = "^0.942" 28 | pytest-cov = "^3.0.0" 29 | black = "^22.3.0" 30 | isort = "^5.10.1" 31 | types-python-dateutil = "^2.8.10" 32 | 33 | [tool.poetry.scripts] 34 | sugarjazy = "sugarjazy.cli:main" 35 | 36 | [tool.poetry.urls] 37 | issues = "https://github.com/chmouel/sugarjazy/issues" 38 | 39 | [build-system] 40 | requires = ["poetry-core>=1.0.0"] 41 | build-backend = "poetry.core.masonry.api" 42 | -------------------------------------------------------------------------------- /sugarjazy/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.0" 2 | -------------------------------------------------------------------------------- /sugarjazy/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import datetime 5 | import json 6 | import random 7 | import re 8 | import sys 9 | import typing 10 | 11 | DEFAULT_TIMEFORMAT = "%H:%M:%S" 12 | CURRENT_EVENT_CHAR = "˃" 13 | 14 | KAIL_PREFIX_REGEXP = re.compile( 15 | r"^(?P[^/]*)/(?P[^\[]*)\[(?P[^]]*)]: (?P.*)" 16 | ) 17 | 18 | 19 | class bcolors: 20 | MAGENTA = "\033[95m" 21 | BLUE = "\033[94m" 22 | CYAN = "\033[96m" 23 | GREEN = "\033[92m" 24 | YELLOW = "\033[93m" 25 | RED = "\033[91m" 26 | ENDC = "\033[0m" 27 | BOLD = "\033[1m" 28 | UNDERLINE = "\033[4m" 29 | 30 | @classmethod 31 | def as_string(cls, s: str) -> str: 32 | return getattr(cls, s.upper()) 33 | 34 | @staticmethod 35 | def random256() -> str: 36 | color = random.randint(0o22, 0o231) 37 | return f"\033[38;5;{color}m" 38 | 39 | 40 | DTPARSEB = "store_false" 41 | try: 42 | import dateutil.parser as dtparse 43 | 44 | DTPARSEB = "store_true" 45 | except ImportError: 46 | DTPARSEB = "store_false" 47 | 48 | 49 | class SugarJazyBadArgumentExc(Exception): 50 | pass 51 | 52 | 53 | class Sugarjazy: 54 | sysargs: list 55 | argp: argparse.Namespace 56 | 57 | def __init__(self, sysargs: typing.Union[None, list]): 58 | if sysargs is None: 59 | self.sysargs = sys.argv[1:] 60 | else: 61 | self.sysargs = sysargs 62 | self.make_args() 63 | 64 | def do_fp(self, fp: typing.IO): 65 | for line in fp.read().split("\n"): 66 | if line.strip(): 67 | sys.stdout.write(self.parse(line) + "\n") 68 | 69 | def do_stdin(self): 70 | buff = "" 71 | try: 72 | while True: 73 | buff += sys.stdin.read(1) 74 | if buff.endswith("\n"): 75 | sys.stdout.write(self.parse(buff[:-1]) + "\n") 76 | buff = "" 77 | elif buff == "EOF": 78 | break 79 | except KeyboardInterrupt: 80 | sys.stdout.flush() 81 | 82 | def make_args(self) -> None: 83 | parser = argparse.ArgumentParser() 84 | parser.add_argument( 85 | "--timeformat", 86 | default=DEFAULT_TIMEFORMAT, 87 | help='timeformat default only to the hour:minute:second. Use "%%Y-%%m-%%d %%H:%%M:%%S" if you want to add the year', 88 | ) 89 | parser.add_argument( 90 | "--regexp-highlight", 91 | "-r", 92 | help=r'Highlight a regexp in message, eg: "Failed:\s*\d+, Cancelled\s*\d+"', 93 | ) 94 | parser.add_argument( 95 | "--disable-event-colouring", 96 | action="store_true", 97 | help=f"By default sugarjazy will try to add a {CURRENT_EVENT_CHAR} char with a color to the eventid to easily identify which event belongs to which. Use this option to disable it.", 98 | ) 99 | 100 | parser.add_argument( 101 | "--filter-level", 102 | "-F", 103 | help="filter levels separated by commas, eg: info,debug", 104 | ) 105 | 106 | parser.add_argument( 107 | "--stream", 108 | "-s", 109 | "-f", 110 | "--follow", 111 | action="store_true", 112 | help="wait for input stream", 113 | ) 114 | 115 | parser.add_argument( 116 | "--kail", 117 | "-k", 118 | action="store_true", 119 | help="assume streaming logs from kail (https://github.com/boz/kail)", 120 | ) 121 | parser.add_argument( 122 | "--kail-no-prefix", 123 | action="store_true", 124 | help="by default kail will print the prefix unless you specify this flag", 125 | ) 126 | parser.add_argument( 127 | "--kail-prefix-format", 128 | default="{namespace}/{pod}[{container}]", 129 | help="the template of the kail prefix.", 130 | ) 131 | 132 | parser.add_argument( 133 | "--regexp-color", default="CYAN", help=r"Regexp highlight color" 134 | ) 135 | parser.add_argument( 136 | "--hide-timestamp", "-H", action=DTPARSEB, help="don't show timestamp" 137 | ) 138 | parser.add_argument("files", nargs="*", default="") 139 | self.argp = parser.parse_args(self.sysargs) 140 | 141 | if self.argp.kail and self.argp.files: 142 | raise SugarJazyBadArgumentExc("kail mode only work on stream") 143 | if self.argp.kail: 144 | self.argp.stream = True 145 | 146 | def main(self): 147 | if self.argp.files: 148 | for f in self.argp.files: 149 | with open(f, encoding="utf-8") as ff: 150 | self.do_fp(ff) 151 | return 152 | 153 | if self.argp.stream: 154 | self.do_stdin() 155 | return 156 | 157 | self.do_fp(sys.stdin) 158 | 159 | # pylint: disable=too-many-return-statements 160 | def parse(self, line: str) -> str: 161 | colors = {} 162 | kail_prefix = "" 163 | if not line.strip(): 164 | return "" 165 | 166 | if self.argp.kail: 167 | kail_re = KAIL_PREFIX_REGEXP.match(line) 168 | if kail_re: 169 | line = kail_re.group("line") 170 | kail_prefix = self.argp.kail_prefix_format.format( 171 | pod=kail_re.group("pod"), 172 | container=kail_re.group("container"), 173 | namespace=kail_re.group("namespace"), 174 | ) 175 | try: 176 | jeez = json.loads(line) 177 | except json.decoder.JSONDecodeError: 178 | if not self.argp.filter_level: 179 | return line 180 | return "" 181 | 182 | getkey = lambda x: jeez.get(x) and x 183 | key_level = getkey("severity") or getkey("level") 184 | if not key_level: 185 | if not self.argp.filter_level: 186 | return line 187 | return "" 188 | 189 | if self.argp.filter_level: 190 | if not key_level: 191 | return "" 192 | if jeez[key_level].lower() not in [ 193 | x.lower() for x in self.argp.filter_level.split(",") 194 | ]: 195 | return "" 196 | key_message = getkey("msg") or getkey("message") 197 | key_event = getkey("event") or getkey("knative.dev/key") or getkey("caller") 198 | chevent = "" 199 | 200 | if not key_message: 201 | return "" 202 | 203 | if not self.argp.disable_event_colouring and key_event: 204 | if not jeez[key_event] in colors: 205 | colors[jeez[key_event]] = bcolors.random256() 206 | eventcolor = colors[jeez[key_event]] 207 | chevent = f"{eventcolor}{CURRENT_EVENT_CHAR}{bcolors.ENDC}" 208 | # highlight string in jeez[km] with a regexp 209 | if key_message and self.argp.regexp_highlight: 210 | jeez[key_message] = re.sub( 211 | "(" + self.argp.regexp_highlight + ")", 212 | bcolors.as_string(self.argp.regexp_color) + r"\1" + bcolors.ENDC, 213 | jeez[key_message], 214 | ) 215 | 216 | kt = getkey("ts") or getkey("timeformat") or getkey("timestamp") 217 | if key_level and jeez[key_level].lower() == "info": 218 | color = bcolors.GREEN 219 | elif key_level and ( 220 | jeez[key_level].lower() == "warning" or jeez[key_level].lower() == "warn" 221 | ): 222 | color = bcolors.YELLOW 223 | elif key_level and jeez[key_level].lower() == "error": 224 | color = bcolors.RED 225 | else: 226 | color = bcolors.CYAN 227 | 228 | dts = "" 229 | if kt and not self.argp.hide_timestamp: 230 | if isinstance(jeez[kt], float): 231 | dt = datetime.datetime.fromtimestamp(jeez[kt]) 232 | else: 233 | dt = dtparse.parse(jeez[kt]) 234 | dts = f"{bcolors.MAGENTA}{dt.strftime(self.argp.timeformat)}{bcolors.ENDC} " 235 | kails = "" 236 | if self.argp.kail and kail_prefix and not self.argp.kail_no_prefix: 237 | kails = f" {bcolors.BLUE}{kail_prefix: <20}{bcolors.ENDC}" 238 | return f"{color}{jeez[key_level].upper(): <7}{bcolors.ENDC} {chevent}{kails} {dts}{jeez[key_message]}" 239 | 240 | 241 | def main(sysargs: typing.Union[None, list] = None): 242 | SJ = Sugarjazy(sysargs) 243 | SJ.main() 244 | 245 | 246 | if __name__ == "__main__": 247 | main(sys.argv[1:]) 248 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chmouel/sugarjazy/2a2f2cf0a7b580b22fd3bec35869fd430c3a0971/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import io 3 | 4 | import pytest 5 | 6 | from sugarjazy import cli 7 | 8 | sampleline = """{"level":"info","ts":"2022-03-24T13:44:02.851Z","logger":"tekton-pipelines-webhook", "msg":"bar FOO hello", "event": "firstev"}""" 9 | 10 | 11 | def test_good(): 12 | sj = cli.Sugarjazy(sysargs=[]) 13 | f = sj.parse(sampleline) 14 | assert "hello" in f 15 | assert f"{cli.bcolors.GREEN}INFO {cli.bcolors.ENDC}" in f 16 | assert "13:44:02" in f 17 | assert cli.CURRENT_EVENT_CHAR in f 18 | 19 | f = sj.parse(sampleline.replace("info", "warn")) 20 | assert f"{cli.bcolors.YELLOW}WARN {cli.bcolors.ENDC}" in f 21 | 22 | f = sj.parse(sampleline.replace("info", "error")) 23 | assert f"{cli.bcolors.RED}ERROR {cli.bcolors.ENDC}" in f 24 | 25 | f = sj.parse(sampleline.replace("info", "other")) 26 | assert f"{cli.bcolors.CYAN}OTHER {cli.bcolors.ENDC}" in f 27 | 28 | 29 | def test_float(): 30 | ts = 1648151733.950114 31 | # pylint: disable=C0209 32 | tsfloat = ( 33 | """{"level":"info","ts":%f,"logger":"tekton-pipelines-webhook", "msg":"bar FOO hello", "event": "firstev"}""" 34 | % ts 35 | ) 36 | sj = cli.Sugarjazy(sysargs=[]) 37 | f = sj.parse(tsfloat) 38 | assert str(datetime.datetime.fromtimestamp(ts).strftime(sj.argp.timeformat)) in f 39 | 40 | 41 | def test_not_json(): 42 | sj = cli.Sugarjazy(sysargs=[]) 43 | f = sj.parse("NOT JSON") 44 | assert f == "NOT JSON" 45 | 46 | 47 | def test_skip_when_filtered(): 48 | sj = cli.Sugarjazy(sysargs=["-F=donotfilter"]) 49 | f = sj.parse(sampleline) 50 | assert f == "" 51 | 52 | sj = cli.Sugarjazy(sysargs=["-F=info"]) 53 | samplelinenolvel = """{"foo":"info","ts":"2022-03-24T13:44:02.851Z","logger":"tekton-pipelines-webhook", "msg":"bar FOO hello"}""" 54 | f = sj.parse(samplelinenolvel) 55 | assert f == "" 56 | 57 | sj = cli.Sugarjazy(sysargs=["-F=info"]) 58 | f = sj.parse("FOOBAR") 59 | assert f == "" 60 | 61 | 62 | def test_no_keys_no_parsing(): 63 | sample = """{"foo":"info","ts":"2022-03-24T13:44:02.851Z","logger":"tekton-pipelines-webhook", "msg":"bar FOO hello"}""" 64 | sj = cli.Sugarjazy(sysargs=[]) 65 | f = sj.parse(sample) 66 | assert f == sample 67 | 68 | 69 | def test_kail(): 70 | sj = cli.Sugarjazy(sysargs=["--kail", "--kail-prefix-format=<<{container}>>"]) 71 | line = """ns/pod[HELLOMOTO]: {"level":"info","ts":"2022-03-24T13:44:02.851Z","logger":"tekton-pipelines-webhook", "msg":"hello"}""" 72 | assert "<>" in sj.parse(line) 73 | 74 | 75 | def test_kail_only_on_stream(): 76 | with pytest.raises(cli.SugarJazyBadArgumentExc): 77 | cli.Sugarjazy(sysargs=["--kail", "/tmp/foo"]) 78 | 79 | 80 | def test_regexp_hl(): 81 | sj = cli.Sugarjazy(sysargs=["-r", "FOO"]) 82 | assert f"{cli.bcolors.CYAN}FOO{cli.bcolors.ENDC}" in sj.parse(sampleline) 83 | 84 | 85 | def test_hide_ts(): 86 | sj = cli.Sugarjazy(sysargs=["-H"]) 87 | f = sj.parse(sampleline) 88 | assert "13:44:02" not in f 89 | 90 | 91 | def test_stream(monkeypatch, capsys): 92 | def f(): 93 | return io.StringIO(sampleline + "\r\n" + sampleline + "\nEOF") 94 | 95 | monkeypatch.setattr("sys.stdin", f()) 96 | cli.main(["-s"]) 97 | captured = capsys.readouterr() 98 | assert "bar FOO hello" in captured.out 99 | 100 | 101 | def test_parse(capsys): 102 | fp = io.StringIO(sampleline + "\r\n" + sampleline + "\n") 103 | sj = cli.Sugarjazy(sysargs=["-H"]) 104 | sj.do_fp(fp) 105 | captured = capsys.readouterr() 106 | assert "bar FOO hello" in captured.out 107 | 108 | 109 | def test_parse_files(capsys, tmp_path): 110 | log_file = tmp_path / "log" 111 | log_file.write_text(sampleline + "\n" + sampleline) 112 | cli.main([str(log_file)]) 113 | captured = capsys.readouterr() 114 | assert "bar FOO hello" in captured.out 115 | -------------------------------------------------------------------------------- /walkthrough.md: -------------------------------------------------------------------------------- 1 | # Sugarjazy walkthrough 2 | 3 | 4 | ## Default log view (beurk) 5 | 6 | ```bash 7 | kubectl logs deployment/openshift-pipelines-operator -n openshift-operators 8 | ``` 9 | 10 | ## Log viewing with sugarjazy (yay) 11 | 12 | ```bash 13 | kubectl logs deployment/openshift-pipelines-operator -n openshift-operators | sugarjazy 14 | ``` 15 | 16 | ## Stream log vieweing 17 | 18 | ```bash 19 | kubectl logs deployment/openshift-pipelines-operator -n openshift-operators | sugarjazy -s 20 | ``` 21 | 22 | 23 | ## Filter levels 24 | 25 | ```bash 26 | kubectl logs deployment/openshift-pipelines-operator -n openshift-operators | sugarjazy --filter-level error 27 | ``` 28 | 29 | ## Filter multiple levels 30 | 31 | ```bash 32 | kubectl logs deployment/openshift-pipelines-operator -n openshift-operators | sugarjazy --filter-level debug,error 33 | ``` 34 | 35 | 36 | ## Syntax highlighting 37 | 38 | ```bash 39 | kubectl logs deployment/openshift-pipelines-operator -n openshift-operators | sugarjazy -r "TektonConfig" 40 | ``` 41 | 42 | ## Kail 43 | 44 | 45 | 46 | 47 | ```bash 48 | kail --since=1h -n openshift-operators|sugarjazy -s --kail 49 | ``` 50 | 51 | 52 | ## Kail no prefix container 53 | 54 | 55 | ```bash 56 | kail --since=1h -n openshift-operators|sugarjazy -s --kail --kail-no-prefix 57 | ``` 58 | --------------------------------------------------------------------------------