diff --git a/.circleci/config.yml b/.circleci/config.yml index f7d8f91..f3388a1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ jobs: build: machine: - image: ubuntu-2004:current + image: ubuntu-2204:current steps: - checkout - run: @@ -50,15 +50,15 @@ workflows: filters: branches: only: - - circleci - develop + - pre-release - main - /.*-ci/ - deploy: filters: branches: only: - - circleci + - pre-release - main requires: - build diff --git a/.eslintignore b/.eslintignore index 5b15327..2088da0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,13 @@ +!.circleci +**/__pycache__ +*test-results* *~ +.git +.mypy_cache +.pytest_cache +.ruff_cache +.venv +dist node_modules package-lock.json -test-results +typings diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 63c58eb..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,130 +0,0 @@ -module.exports = { - root: true, - env: { - node: true, - es2022: true, - browser: true, - }, - extends: [ - "eslint:recommended", - // CODE QUALITY - "plugin:sonarjs/recommended", - "plugin:unicorn/all", - // LANGS - "plugin:json/recommended", - "plugin:markdown/recommended", - //"plugin:md/recommended", - "plugin:yaml/recommended", - // PRACTICES - "plugin:array-func/recommended", - "plugin:eslint-comments/recommended", - "plugin:no-use-extend-native/recommended", - "plugin:optimize-regex/all", - "plugin:promise/recommended", - "plugin:import/recommended", - "plugin:switch-case/recommended", - // PRETTIER - "plugin:prettier/recommended", - // SECURITY - "plugin:no-unsanitized/DOM", - "plugin:security/recommended-legacy", - ], - parserOptions: { - ecmaFeatures: { - impliedStrict: true, - }, - ecmaVersion: 2022, - }, - overrides: [ - { - files: ["*.md"], - parser: "markdown-eslint-parser", - rules: { - "prettier/prettier": ["error", { parser: "markdown" }], - }, - }, - { - files: ["*.md.js"], // Will match js code inside *.md files - rules: { - // disable 2 core eslint rules 'no-unused-vars' and 'no-undef' - "no-unused-vars": "off", - "no-undef": "off", - }, - }, - ], - plugins: [ - "array-func", - "eslint-comments", - "json", - "import", - "markdown", - //"md", - "no-constructor-bind", - "no-secrets", - "no-unsanitized", - "no-use-extend-native", - "optimize-regex", - "prettier", - "promise", - "simple-import-sort", - "switch-case", - "security", - "sonarjs", - "unicorn", - "yaml", - ], - rules: { - "import/no-unresolved": [ - "error", - { - ignore: ["^[@]"], - }, - ], - "max-params": ["warn", 4], - /* - md/remark plugins can't be read by eslint - https://github.com/standard-things/esm/issues/855 - "md/remark": [ "error", - { - plugins: [ - "gfm", - "preset-lint-consistent", - "preset-lint-markdown-style-guide", - "preset-lint-recommended", - "preset-prettier" - ], - } - ], - */ - "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", - "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", - "no-constructor-bind/no-constructor-bind": "error", - "no-constructor-bind/no-constructor-state": "error", - "no-secrets/no-secrets": "error", - "prettier/prettier": "warn", - "security/detect-object-injection": "off", - "simple-import-sort/exports": "warn", - "simple-import-sort/imports": "warn", - "space-before-function-paren": "off", - "switch-case/newline-between-switch-case": "off", // Malfunctioning - "unicorn/prevent-abbreviations": "off", - "unicorn/filename-case": [ - "error", - { case: "kebabCase", ignore: [".*.md"] }, - ], - }, - ignorePatterns: [ - "*~", - "**/__pycache__", - ".git", - "!.circleci", - ".mypy_cache", - ".pytest_cache", - ".ruff_cache", - ".venv", - "dist", - "node_modules", - "test_results", - "typings", - ], -}; diff --git a/Dockerfile b/Dockerfile index 37ea1b2..1cc9749 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,13 +8,15 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends \ gifsicle \ libjpeg-progs \ - optipng \ python3-pip \ unrar \ webp \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# hadolint ignore=DL3016 +RUN npm install svgo + WORKDIR / COPY --chown=circleci:circleci in bin COPY --chown=circleci:circleci packages packages diff --git a/NEWS.md b/NEWS.md index ddecb7e..6f10b8e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,12 +1,36 @@ # 📰 Picopt News +## v4.0.0 + +- Alert! + - Timestamps options changed, invalidating old timestamps. On your first run + in a path with picopt 4.0 use the `-N` option to ignoring the old timestamp. +- Features + - Support optimizing SVG with svgo. + - Support converting more lossless formats to PNG & WEBP: CUR, DIB, FITS, IMT, + PCX, PIXAR, PSD, QOI, SGI, SPIDER, SUN, TGA, XBM, XPM. + - Support converting animated formats to animated PNG. + - Support converting losslessly converting MPO to JPEG (choose primary image) + - Internal oxipng replaces external optipng for png compression. + - Better support for preserving EXIF, ICC_PROFILE, & XMP data across + optimization and conversion. + - `--destroy-metadata` option becomes `--strip-metadata` + - `--near-lossless` option for lossless WebP. + - `--preserve` file attributes after optimization. + - `--disable-programs` option. + - ~25% faster due to avoiding disk io. +- Fixes + - wal file would write illegal key names and fail to load for some files. +- Dev + - walk.run now returns totals instead of running report on them. + ## v3.3.7 - Fix - Remove Lossy WebP optimization. Wasn't optimal. - Fix non zero exit code on success. - Features - - Improve webp optimization. + - Improve WebP optimization. ## v3.3.6 @@ -45,7 +69,8 @@ ## v3.2.5 -- Fix occasionally removing both final and originals on case insensitive filesystem. +- Fix occasionally removing both final and originals on case insensitive + filesystem. ## v3.2.4 @@ -95,7 +120,8 @@ ## v3.1.0 -- Change walk algorithm to complete directories and containers before opening new ones. +- Change walk algorithm to complete directories and containers before opening + new ones. - Case insensitive format config. - Ignore symlinks when asked to in timestamps. @@ -131,9 +157,10 @@ - Timestamps - Timestamps are now kept in .picopt_treestamps.yaml files. - Picopt will convert and clean up old style timestamps. - - Timestamps are now recorded after optimizing every image for - each image individually instead of directories, preserving progress. - - Timestamps record configuration. Running with a new config invalidates non-matching timestamps. + - Timestamps are now recorded after optimizing every image for each image + individually instead of directories, preserving progress. + - Timestamps record configuration. Running with a new config invalidates + non-matching timestamps. - Configuration - .picoptrc.yaml files can configure options - Changed cli option names. @@ -210,7 +237,8 @@ ## v1.4.5 -- When setting new timestamps, don't remove timestamps above the root paths specified as input +- When setting new timestamps, don't remove timestamps above the root paths + specified as input ## v1.4.4 @@ -256,7 +284,8 @@ ## v1.2 -- picopt learned the -j option for specifying number of subprocesses (thanks @DarwinAwardWinner) +- picopt learned the -j option for specifying number of subprocesses (thanks + @DarwinAwardWinner) ## v1.1.3 @@ -311,7 +340,8 @@ ## v0.12.0 -- Added multithreaded jpegrescan operation when picopt isn't using up all the cores at the suggestion of Alex Roe. +- Added multithreaded jpegrescan operation when picopt isn't using up all the + cores at the suggestion of Alex Roe. - Added destroy metadata option at the suggestion of Alex Roe. ## v0.11.4 diff --git a/README.md b/README.md index a7b71a0..26a3d74 100644 --- a/README.md +++ b/README.md @@ -1,166 +1,352 @@ # picopt -A multi-format, recursive, multiprocessor aware, command line lossless image optimizer utility that uses external tools to do the optimizing. +A multi-format, recursive, multiprocessor aware, command line, lossless image +optimizer utility that can use external tools for even better optimizing. -Picopt depends on Python [PIL](http://www.pythonware.com/products/pil/) to identify files and Python [rarfile](https://pypi.python.org/pypi/rarfile) to open CBRs. +Picopt will optionally drop hidden timestamps at the root of your image +directories to avoid reoptimizing images picopt has already optimized. -Picopt will optionally drop hidden timestamps at the root of your image directories to avoid reoptimizing images picopt has already optimized. +## 💭 Conversion Philosophy -The actual image optimization is best accomplished by external programs. +### Warning -## Conversion Philosophy +Picopt transforms images in place and throws out the old image. Always have a +backup of images before running picopt in case you are not satisfied with the +results. ### Lossy Images -JPEG images are likely the best and most practical lossy image formats. Converting lossy images rarely makes sense and so picopt only optimizes them in their current format. -WEBP Lossy images are not optimized. There is no current way to preserve information without running it through a lossy process again. +Converting lossy images rarely makes sense and so picopt only optimizes them in +their current format. + +- JPEG images are optimized with MozJpeg's jpegtran. +- WEBP Lossy images are not optimized. There is no current way to preserve + information without running it through a lossy process again. ### Lossless Images -Lossless WebP images are smaller than PNG, much smaller than GIF and, of course, a great deal smaller thein uncompressed bitmaps like BMP. As such the best practice is probably to convert all lossless images to WebP Lossless as now all major browsers support it. The only downside is that decoding WebP Lossless takes on average 50% more CPU than PNG. +Lossless WebP images are smaller than PNG, much smaller than GIF and, of course, +a great deal smaller thein uncompressed bitmaps like BMP. As such the best +practice is probably to convert all lossless images to WebP Lossless as now all +major browsers support it. The only downside is that decoding WebP Lossless +takes on average 50% more CPU than PNG. All major desktop and mobile browsers +support WEBP. WEBP is the lossless format of choice. Until perhaps JPEG XL +support arrives for browsers. ### Sequenced Images -Sequenced Images, like animated GIFs and WebP, most of the time, should be converted to a compressed video format like HEVC or VP9. There are several situations where this is impractical and so Animated WebP is now a good substitute. +Sequenced Images, like animated GIFs and WebP, most of the time, should be +converted to a compressed video format like HEVC, VVC, VP9 or VP10. There are +several situations where this is impractical and so Animated WebP is now a good +substitute. ### Conversion -By default picopt does not convert images between formats. You must turn on conversion to PNG or WebP explicitly. - -## Formats - -- By default picopt will optimize GIF, JPEG, PNG and WEBP images. -- Picopt can optionally optimize ZIP, ePub, and CBZ containers. -- Picopt can be told to convert lossless images such as BPM, PPM, GIF, TIFF into PNG, and all of the mentioned lossless formats into WebP. -- Picopt can convert Animated GIFs into Animated WebP files. -- Picopt can convert Animated PNGs (APNG) into Animated WebP files, but does not optimize APNG as APNG. +By default picopt does not convert images between formats. You must turn on +conversion to PNG or WebP explicitly. + +## 🖼️ Formats + +- By default picopt will optimize GIF, JPEG, PNG, and WEBP images. +- Picopt can optionally optimize SVG images, ZIP, ePub, and CBZ containers. +- Picopt can convert many lossless images such as BMP, CBR, CUR, DIB, FITS, GIF, + IMT, PCX, PIXAR, PNG, PPM, PSD, QOI, SGI, SPIDER, SUN, TGA, TIFF, XBM, and XPM + into PNG and WEBP. +- Picopt can convert Animated GIF, TIFF, and FLI into Animated PNG or WebP + files. +- Picopt can convert Animated GIF, TIFF, FLI, and PNG into Animated WebP files. +- Picopt can convert MPO to JPEG by stripping secondary images if a primary + image exists. (Experimental) - Picopt can convert RAR files into Zipfiles and CBR files into CBZ files. -## External Programs +Because picopt supports so many lossless image formats, to avoid surprises if +you specify a conversion target, picopt will only convert GIF and PNG images to +the target by default. To convert another format, like BMP, to WEBP you must +specify that you want to read the BMP format _and_ that you want t:qo convert it +to WEBP: -Picopt will perform some minor optimization on most formats natively without using external programs, but this is not very good compared to the optimizations external programs can provide. + + +```sh +picopt -x BMP -c WEBP big_old.bmp +``` ### JPEG -To optimize JPEG images. Picopt needs one of [mozjpeg](https://github.com/mozilla/mozjpeg) or [jpegtran](http://jpegclub.org/jpegtran/) on the path. in order of preference. +To optimize JPEG images at all picopt needs one of +[mozjpeg](https://github.com/mozilla/mozjpeg) or +[jpegtran](http://jpegclub.org/jpegtran/) on the path. in order of preference. + +### PNG & APNG -### PNG +Picopt uses an internal oxipng python module to to optimize PNG images and +convert other lossless formats to PNG picopt. The external +[pngout](http://advsys.net/ken/utils.htm) tool can provide a small extra bit of +compression. -To optimize PNG images or convert other lossless formats to PNG picopt requires either [optipng](http://optipng.sourceforge.net/) or [pngout](http://advsys.net/ken/utils.htm) be on the path. Optipng provides the most advantage, but best results will be had by using pngout as well. +Animated PNGs are optimized with the internal optimizer. ### Animated GIF -Animated GIFs are optimized with [gifsicle](http://www.lcdf.org/gifsicle/) if it is available. +Gifs and Animated GIFs are optimized with +[gifsicle](http://www.lcdf.org/gifsicle/) if available. or interaallly if is +not. Gifsicle only provides a small advantage over the internal optimizer. ### WebP -WebP lossless formats are optimized with [cwebp](https://developers.google.com/speed/webp/docs/cwebp). +WebP lossless formats are optimized with +[cwebp](https://developers.google.com/speed/webp/docs/cwebp) if available and +with the internal optimizer if not. cwebp provides significant improvements over +the internal optimizer. + +### SVG + +Picopt can only optimize SVGs if [svgo](https://github.com/svg/svgo) is on the +path. + +### MPO (Experimental) + +Picopt can extract the primary image from an multi JPEG MPO that also contains +thumbnails and convert the file to an ordinary JPEG. Picopt will also optimize +this image if it can. To enable this you must run with `-x MPO -c JPEG` +Steroscopic MPOs should have no primary image tagged in the MPO directory and be +unaffected. + +This feature has not been tested with a large variety of MPOs and should be +considered experimental. ### EPub -EPub Books are zip files that often contain images and picopt unpacks and repacks this format natively. Images within the epub are handled by other programs. EPub optimization is not turned on by default. -EPub contents are never converted to other formats because it would break internal references to them. +EPub Books are zip files that often contain images and picopt unpacks and +repacks this format natively. Images within the epub are handled by other +programs. EPub optimization is not turned on by default. EPub contents are never +converted to other formats because it would break internal references to them. ### CBZ & CBR -Picopt uncompresses, optimizes and rezips [comic book archive files](https://en.wikipedia.org/wiki/Comic_book_archive). Be aware that CBR rar archives may only be rezipped into CBZs instead of CBR. Comic book archive optimization is not turned on by default to prevent surprises. +Picopt uncompresses, optimizes and rezips +[comic book archive files](https://en.wikipedia.org/wiki/Comic_book_archive). Be +aware that CBR rar archives may only be rezipped into CBZs instead of CBR. Comic +book archive optimization is not turned on by default to prevent surprises. -## Install +## 📦 Install ### System Dependencies -picopt requires several external system dependencies to run. We must install these first +picopt is most effective with ependencies to run. We must install these first #### macOS - brew install gifsicle jonof/kenutils/pngout mozjpeg optipng webp + - ln -s $(brew --prefix)/opt/mozjpeg/bin/jpegtran /usr/local/bin/mozjpeg +```sh +brew install gifsicle mozjpeg svgo webp -Unfortunately hombrew's `webp` formula does not yet install the gif2webp tool that picopt uses for converting animated gifs to animated webps. -You may manually download it and put it in your path at [Google's WebP developer website](https://developers.google.com/speed/webp/download) +ln -s $(brew --prefix)/opt/mozjpeg/bin/jpegtran /usr/local/bin/mozjpeg +``` #### Debian / Ubuntu - apt-get install optipng gifsicle python-imaging webp + -if you don't want to install mozjpeg using the instructions below then use jpegtran: +```sh +apt-get install gifsicle python-imaging webp +``` - apt-get install libjpeg-progs +if you don't want to install mozjpeg using the instructions below then use +jpegtran: -#### Redhat / Fedora + - yum install optipng gifsicle python-imaging libwebp-tools +```sh +apt-get install libjpeg-progs +``` -if you don't want to install mozjpeg using the instructions below then use jpegtran: +See mozjepg, pngout & svgo install instructions below - yum install libjpeg-progs +#### Redhat / Fedora -#### MozJPEG + -mozjpeg offers better compression than libjpeg-progs jpegtran. It may or -may not be packaged for your \*nix, but even when it is, picopt requires that its separately compiled version of jpegtran be symlinked to 'mozjpeg' somewhere in the path. +```sh +yum install gifsicle python-imaging libwebp-tools +``` -Instructions for installing on macOS are given above. -Some near recent binaries for Windows and Debian x86 [can be found here](https://mozjpeg.codelove.de/binaries.html). -Most Linux distributions still require a more manual install as elucidated here on [Casey Hoffer's blog](https://www.caseyhofford.com/2019/05/01/improved-image-compression-install-mozjpeg-on-ubuntu-server/) +if you don't want to install mozjpeg using the instructions below then use +jpegtran: -#### pngout + -pngout is a useful compression to use after optipng. It is not packaged for linux, but you may find the latest binary version [on JonoF's site](http://www.jonof.id.au/kenutils). Picopt looks for the binary to be called `pngout` +```sh +yum install libjpeg-progs +``` + +See mozjepg, pngout & svgo install instructions below ### Picopt python package - pip install picopt + + +```sh +pip install picopt +``` + +## ⚙️ External Programs + +Picopt will perform optimization on most lossless formats without using external +programs, but much more compression is possible if these external programs are +on your path. + +### mozjpeg + +mozjpeg offers better compression than libjpeg-progs jpegtran. It may or may not +be packaged for your \*nix, but even when it is, picopt requires that its +separately compiled version of jpegtran be symlinked to 'mozjpeg' somewhere in +the path. + +Instructions for installing on macOS are given above. Some near recent binaries +for Windows and Debian x86 +[can be found here](https://mozjpeg.codelove.de/binaries.html). Most Linux +distributions still require a more manual install as elucidated here on +[Casey Hoffer's blog](https://www.caseyhofford.com/2019/05/01/improved-image-compression-install-mozjpeg-on-ubuntu-server/) + +### pngout + +pngout is a compression tool that can be used for small extra compression. It +does not run on 16 bit PNGs. + +It can be installed on macOS with: + + -## Usage Examples +```sh +brew install jonof/kenutils/pngout +``` + +It is not packaged for linux, but you may find the latest binary version +[on JonoF's site](http://www.jonof.id.au/kenutils). Picopt looks for the binary +to be called `pngout` + +### svgo + +svgo compresses SVGs. Svgo is packaged for homebrew, but picopt can also use it +if it's installed with npm. + +#### On Linux + +To install svgo on Linux you can use the snap tool: + + + +```sh +snap install svgo +``` + +Or you can install svgo with npm: + + + +```sh +npm install -G svgo +``` + +## ⌨️ Usage Examples Optimize all JPEG files in a directory: - picopt *.jpg + + +```sh +picopt *.jpg +``` Optimize all files and recurse directories: - picopt -r * + + +```sh +picopt -r * +``` + +Optimize files, recurse directories, also optimize ePub & CBZ containers, +convert lossless images into WEBP, convert CBR into CBZ. -Optimize files, recurse directories, also optimize ePub & CBZ containers, convert lossless images into WEBP, convert CBR into CBZ. + - picopt -rx EPUB,CBR,CBZ -c WEBP,CBZ * +```sh +picopt -rx EPUB,CBR,CBZ -c WEBP,CBZ * +``` Optimize files and recurse directories AND optimize comic book archives: - picopt -rx CBZ * + -Optimize comic directory recursively. Convert CBRs to CBZ. Convert lossless images, including TIFF, to lossless WEBP. Do not follow symlinks. Set timestamps. +```sh +picopt -rx CBZ * +``` - picopt -rStc CBZ,WEBP -x TIFF,CBR,CBZ /Volumes/Media/Comics +Optimize comic directory recursively. Convert CBRs to CBZ. Convert lossless +images, including TIFF, to lossless WEBP. Do not follow symlinks. Set +timestamps. + + + +```sh +picopt -rStc CBZ,WEBP -x TIFF,CBR,CBZ /Volumes/Media/Comics +``` Optimize all files, but only JPEG format files: - picopt -f JPEG * + + +```sh +picopt -f JPEG * +``` Optimize files and containers, but not JPEGS: - picopt -f GIF,PNG,WEBP,ZIP,CBZ,EPUB * + + +```sh +picopt -f GIF,PNG,WEBP,ZIP,CBZ,EPUB * +``` Optimize files, but not animated gifs: - picopt -f PNG,WEBP,ZIP,CBZ,EPUB * + + +```sh +picopt -f PNG,WEBP,ZIP,CBZ,EPUB * +``` Just list files picopt.py would try to optimize: - picopt -L * + + +```sh +picopt -L * +``` -Optimize pictures in my iPhoto library, but only after the last time I did this, skipping symlinks to avoid duplicate work. Also drop a timestamp file so I don't have to remember the last time I did this: +Optimize pictures in my iPhoto library, but only after the last time I did this, +skipping symlinks to avoid duplicate work. Also drop a timestamp file so I don't +have to remember the last time I did this: - picopt -rSt -D '2013 June 1 14:00' 'Pictures/iPhoto Library' + -## Packages +```sh +picopt -rSt -D '2013 June 1 14:00' 'Pictures/iPhoto Library' +``` + +## 📦 Packages - [PyPI](https://pypi.python.org/pypi/picopt/) - [Arch Linux](https://aur.archlinux.org/packages/picopt/) -## Alternatives +## 👀 Alternatives + +- [imagemin](https://github.com/imagemin/imagemin-cli) looks to be an all in one + cli and gui solution with bundled libraries, so no awkward dependencies. -[imagemin](https://github.com/imagemin/imagemin-cli) looks to be an all in one cli and gui solution with bundled libraries, so no awkward dependencies. -[Imageoptim](http://imageoptim.com/) is an all-in-one OS X GUI image optimizer. Imageoptim command line usage is possible with [an external program](https://code.google.com/p/imageoptim/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Priority%20Milestone%20Owner%20Summary%20Stars&groupby=&sort=&id=39). +- [Imageoptim](http://imageoptim.com/) is an all-in-one OS X GUI image + optimizer. Imageoptim command line usage is possible with + [an external program](https://code.google.com/p/imageoptim/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Priority%20Milestone%20Owner%20Summary%20Stars&groupby=&sort=&id=39). diff --git a/build.Dockerfile b/build.Dockerfile index 0bb23b5..b4c8d38 100644 --- a/build.Dockerfile +++ b/build.Dockerfile @@ -1,4 +1,4 @@ -FROM cimg/python:3.11-node +FROM cimg/python:3.12-node ENV DEBIAN_FRONTEND noninteractive @@ -10,13 +10,15 @@ RUN apt-get update \ gifsicle \ git \ libjpeg-progs \ - optipng \ shellcheck \ unrar \ webp \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# hadolint ignore=DL3016 +RUN npm install --global svgo + WORKDIR /app RUN chown circleci:circleci /app COPY --chown=circleci:circleci bin bin diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..1948553 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,177 @@ +import { FlatCompat } from "@eslint/eslintrc"; +import js from "@eslint/js"; +import arrayFunc from "eslint-plugin-array-func"; +// import plugin broken for flag config +// https://github.com/import-js/eslint-plugin-import/issues/2556 +//import importPlugin from "eslint-plugin-import"; +import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; +import pluginSecurity from "eslint-plugin-security"; +import eslintPluginUnicorn from "eslint-plugin-unicorn"; +import globals from "globals"; + +const compat = new FlatCompat(); + +export default [ + { + languageOptions: { + globals: { + ...globals.node, + }, + }, + linterOptions: { + reportUnusedDisableDirectives: "warn", + }, + plugins: { + // import: importPlugin, + unicorn: eslintPluginUnicorn, + }, + rules: { + "max-params": ["warn", 4], + "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", + "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", + "prettier/prettier": "warn", + "security/detect-object-injection": "off", + "space-before-function-paren": "off", + "unicorn/prevent-abbreviations": "off", + "unicorn/filename-case": [ + "error", + { case: "kebabCase", ignore: [".*.md"] }, + ], + /* + ...importPlugin.configs["recommended"].rules, + "import/no-unresolved": [ + "error", + { + ignore: ["^[@]"], + }, + ], + */ + }, + /* + settings: { + "import/parsers": { + espree: [".js", ".cjs", ".mjs", ".jsx"], + "@typescript-eslint/parser": [".ts"], + }, + "import/resolver": { + typescript: true, + node: true, + }, + }, + */ + }, + js.configs.recommended, + arrayFunc.configs.all, + pluginSecurity.configs.recommended, + eslintPluginPrettierRecommended, + ...compat.config({ + ignorePatterns: [ + "*~", + "**/__pycache__", + ".git", + "!.circleci", + ".mypy_cache", + ".pytest_cache", + ".ruff_cache", + ".venv", + "dist", + "node_modules", + "package-lock.json", + "test-results", + "typings", + ], + root: true, + env: { + node: true, + es2024: true, + }, + parserOptions: { + ecmaFeatures: { + impliedStrict: true, + }, + ecmaVersion: "latest", + }, + plugins: [ + "eslint-comments", + //"import", + "markdown", + "no-constructor-bind", + "no-secrets", + "no-unsanitized", + "no-use-extend-native", + "optimize-regex", + "promise", + "simple-import-sort", + "sonarjs", + "switch-case", + ], + extends: [ + // CODE QUALITY + "plugin:sonarjs/recommended", + // LANGS + "plugin:jsonc/recommended-with-jsonc", + "plugin:markdown/recommended", + "plugin:toml/recommended", + "plugin:yml/standard", + "plugin:yml/prettier", + // PRACTICES + "plugin:eslint-comments/recommended", + //"plugin:import/recommended", + "plugin:no-use-extend-native/recommended", + "plugin:optimize-regex/all", + "plugin:promise/recommended", + "plugin:switch-case/recommended", + // SECURITY + "plugin:no-unsanitized/DOM", + ], + rules: { + "eslint-comments/no-unused-disable": 1, + "no-constructor-bind/no-constructor-bind": "error", + "no-constructor-bind/no-constructor-state": "error", + "no-secrets/no-secrets": "error", + "simple-import-sort/exports": "warn", + "simple-import-sort/imports": "warn", + "switch-case/newline-between-switch-case": "off", // Malfunctioning + }, + overrides: [ + { + files: ["**/*.md"], + processor: "markdown/markdown", + rules: { + "prettier/prettier": ["error", { parser: "markdown" }], + }, + }, + { + files: ["**/*.md/*.js"], // Will match js code inside *.md files + rules: { + "no-unused-vars": "off", + "no-undef": "off", + }, + }, + { + files: ["**/*.md/*.sh"], + rules: { + "prettier/prettier": ["error", { parser: "sh" }], + }, + }, + { + files: ["*.yaml", "*.yml"], + //parser: "yaml-eslint-parser", + rules: { + "unicorn/filename-case": "off", + }, + }, + { + files: ["*.toml"], + //parser: "toml-eslint-parser", + rules: { + "prettier/prettier": ["error", { parser: "toml" }], + }, + }, + { + files: ["*.json", "*.json5", "*.jsonc"], + //parser: "jsonc-eslint-parser", + }, + ], + }), +]; diff --git a/package-lock.json b/package-lock.json index 0a2241a..d8bf9ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,14 +5,16 @@ "packages": { "": { "devDependencies": { + "@eslint/eslintrc": "^3.0.1", "@fsouza/prettierd": "^0.25.2", "@prettier/plugin-xml": "^3.1.0", + "eslint": "^8.56.0", "eslint_d": "^13.1.2", "eslint-config-prettier": "^9.0.0", - "eslint-plugin-array-func": "^4.0.0", + "eslint-plugin-array-func": "^5.0.1", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.27.5", - "eslint-plugin-json": "^3.1.0", + "eslint-plugin-jsonc": "^2.13.0", "eslint-plugin-markdown": "^3.0.0", "eslint-plugin-no-constructor-bind": "^2.0.4", "eslint-plugin-no-secrets": "^0.8.9", @@ -22,15 +24,16 @@ "eslint-plugin-prettier": "^5.0.0-alpha.2", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-security": "^2.1.0", - "eslint-plugin-simple-import-sort": "^10.0.0", - "eslint-plugin-sonarjs": "^0.23.0", + "eslint-plugin-simple-import-sort": "^12.0.0", + "eslint-plugin-sonarjs": "^0.24.0", "eslint-plugin-switch-case": "^1.1.2", - "eslint-plugin-unicorn": "^50.0.1", - "eslint-plugin-yaml": "^0.5.0", - "markdown-eslint-parser": "^1.2.1", + "eslint-plugin-toml": "^0.9.2", + "eslint-plugin-unicorn": "^51.0.1", + "eslint-plugin-yml": "^1.12.2", "prettier": "^3.0.0", "prettier-plugin-packagejson": "^2.4.4", "prettier-plugin-sh": "^0.14.0", + "prettier-plugin-toml": "^2.0.1", "remark": "^15.0.1", "remark-cli": "^12.0.0", "remark-gfm": "^4.0.0", @@ -228,9 +231,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", "dev": true, "optional": true, "bin": { @@ -265,14 +268,14 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.0.1.tgz", + "integrity": "sha512-xXm39r1RgOSmPCqlhn+E10KPJ7JKrpuBwsAVw/++5dS/Sa4GAi0smby0r0wfTN4gNpkk9iij2hssJMXHSmQ89w==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", + "espree": "^10.0.1", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -281,34 +284,12 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/js": { "version": "8.56.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", @@ -349,28 +330,6 @@ "node": ">=10.10.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -526,6 +485,15 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@npmcli/map-workspaces/node_modules/glob": { "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", @@ -548,6 +516,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@npmcli/name-from-folder": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", @@ -580,9 +563,9 @@ } }, "node_modules/@prettier/plugin-xml": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@prettier/plugin-xml/-/plugin-xml-3.2.2.tgz", - "integrity": "sha512-SoE70SQF1AKIvK7LVK80JcdAe6wrDcbodFFjcoqb1FkOqV0G0oSlgAFDwoRXPqkUE5p/YF2nGsnUbnfm6471sw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@prettier/plugin-xml/-/plugin-xml-3.3.1.tgz", + "integrity": "sha512-kllNJk6n2pXJjGWdj+HAr1GhOoOTrlmeWkDYCGBzkyZS2l0K6h2gsUQcVif2cNqAE1MNC+nUrzN6QwEsCukPnQ==", "dev": true, "dependencies": { "@xml-tools/parser": "^1.0.11" @@ -591,6 +574,21 @@ "prettier": "^3.0.0" } }, + "node_modules/@taplo/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@taplo/core/-/core-0.1.1.tgz", + "integrity": "sha512-BG/zLGf5wiNXGEVPvUAAX/4ilB3PwDUY2o0MV0y47mZbDZ9ad9UK/cIQsILat3bqbPJsALVbU6k3cskNZ3vAQg==", + "dev": true + }, + "node_modules/@taplo/lib": { + "version": "0.4.0-alpha.2", + "resolved": "https://registry.npmjs.org/@taplo/lib/-/lib-0.4.0-alpha.2.tgz", + "integrity": "sha512-DV/Re3DPVY+BhBtLZ3dmP4mP6YMLSsgq9qGLXwOV38lvNF/fBlgvQswzlXmzCEefL/3q2eMoefZpOI/+GLuCNA==", + "dev": true, + "dependencies": { + "@taplo/core": "^0.1.0" + } + }, "node_modules/@types/concat-stream": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-2.0.3.tgz", @@ -616,18 +614,18 @@ "dev": true }, "node_modules/@types/estree-jsx": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.3.tgz", - "integrity": "sha512-pvQ+TKeRHeiUGRhvYwRrQ/ISnohKkSJR14fT2yqyZ4e9K5vqc7hrtY2Y1Dw0ZwAzQ6DQsxsaCUuSIIi8v0Cq6w==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.4.tgz", + "integrity": "sha512-5idy3hvI9lAMqsyilBM+N+boaCf1MgoefbDxN6KEO5aK17TOHwFAYT9sjxzeKAiIWRUBgLxmZ9mPcnzZXtTcRQ==", "dev": true, "dependencies": { "@types/estree": "*" } }, "node_modules/@types/hast": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.9.tgz", - "integrity": "sha512-pTHyNlaMD/oKJmS+ZZUyFUcsZeBZpC0lmGquw98CqRVNgAdJZJeD7GoeLiT6Xbx5rU9VCjSt0RwEvDgzh4obFw==", + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", "dev": true, "dependencies": { "@types/unist": "^2" @@ -661,9 +659,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.6.tgz", - "integrity": "sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q==", + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -694,9 +692,9 @@ "dev": true }, "node_modules/@typescript-eslint/types": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.1.tgz", - "integrity": "sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "optional": true, "engines": { @@ -708,14 +706,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz", - "integrity": "sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "optional": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", - "@typescript-eslint/visitor-keys": "6.19.1", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -736,14 +734,40 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.19.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz", - "integrity": "sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "optional": true, "dependencies": { - "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -815,33 +839,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -886,13 +883,16 @@ "dev": true }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -927,17 +927,36 @@ "node": ">=8" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", - "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "node_modules/array.prototype.filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", + "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", + "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -983,17 +1002,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -1003,19 +1023,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz", + "integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==", "dev": true, "engines": { "node": ">= 0.4" @@ -1050,12 +1061,13 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/braces": { @@ -1071,9 +1083,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -1090,8 +1102,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -1121,14 +1133,19 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1144,9 +1161,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001579", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", - "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==", + "version": "1.0.30001588", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz", + "integrity": "sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==", "dev": true, "funding": [ { @@ -1231,12 +1248,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, "node_modules/chevrotain": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz", @@ -1247,16 +1258,10 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1269,6 +1274,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -1321,40 +1329,6 @@ "node": ">=0.8.0" } }, - "node_modules/cli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", - "integrity": "sha512-41U72MB56TfUMGndAKK8vJ78eooOD4Z5NOL4xEfjc0c23s+6EYKXlXsmACBVclLP1yOfWCgEganVzddVrSNoTg==", - "dev": true, - "dependencies": { - "exit": "0.1.2", - "glob": "^7.1.1" - }, - "engines": { - "node": ">=0.2.5" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1404,38 +1378,6 @@ "typedarray": "^0.0.6" } }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha512-duS7VP5pvfsNLDvL1O4VOEbw37AI3A4ZUQYemvDlnpGrNu9tprR7BYWpDYwC0Xia0Zxz5ZupdiIrUp0GH1aXfg==", - "dev": true, - "dependencies": { - "date-now": "^0.1.4" - } - }, "node_modules/core_d": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/core_d/-/core_d-6.1.0.tgz", @@ -1446,24 +1388,18 @@ } }, "node_modules/core-js-compat": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz", - "integrity": "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==", + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz", + "integrity": "sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==", "dev": true, "dependencies": { - "browserslist": "^4.22.2" + "browserslist": "^4.22.3" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1478,12 +1414,6 @@ "node": ">= 8" } }, - "node_modules/date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw==", - "dev": true - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1531,17 +1461,20 @@ "dev": true }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -1605,9 +1538,9 @@ } }, "node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, "engines": { "node": ">=0.3.1" @@ -1637,72 +1570,16 @@ "node": ">=6.0.0" } }, - "node_modules/dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "node_modules/dom-serializer/node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "node_modules/domhandler": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", - "integrity": "sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ==", - "dev": true, - "dependencies": { - "domelementtype": "1" - } - }, - "node_modules/domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", - "dev": true, - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.645", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.645.tgz", - "integrity": "sha512-EeS1oQDCmnYsRDRy2zTeC336a/4LZ6WKqvSaM1jLocEk5ZuyszkQtCpsqvuvaIXGOUjwtvF6LTcS8WueibXvSw==", + "version": "1.4.673", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.673.tgz", + "integrity": "sha512-zjqzx4N7xGdl5468G+vcgzDhaHkaYgVcf9MqgexcTqsl2UHSCmOj/Bi3HAprg4BZCpC7HyD8a6nZl6QAZf72gw==", "dev": true }, "node_modules/emoji-regex": { @@ -1711,12 +1588,6 @@ "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", "dev": true }, - "node_modules/entities": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", - "integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ==", - "dev": true - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -1727,50 +1598,52 @@ } }, "node_modules/es-abstract": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", - "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz", + "integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.8", "string.prototype.trimend": "^1.0.7", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", + "typed-array-buffer": "^1.0.1", "typed-array-byte-length": "^1.0.0", "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -1779,6 +1652,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", @@ -1820,9 +1720,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -1910,6 +1810,21 @@ "eslint_d": "bin/eslint_d.js" } }, + "node_modules/eslint-compat-utils": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.4.1.tgz", + "integrity": "sha512-5N7ZaJG5pZxUeNNJfUchurLVrunD1xJvyg5kYOIVF8kg1f3ajTikmAu/5fZ9w100omNPOoMjngRszh/Q/uFGMg==", + "dev": true, + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/eslint-config-prettier": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", @@ -1969,15 +1884,15 @@ } }, "node_modules/eslint-plugin-array-func": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-array-func/-/eslint-plugin-array-func-4.0.0.tgz", - "integrity": "sha512-p3NY2idNIvgmQLF2/62ZskYt8gOuUgQ51smRc3Lh7FtSozpNc2sg+lniz9VaCagLZHEZTl8qGJKqE7xy8O/D/g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-array-func/-/eslint-plugin-array-func-5.0.1.tgz", + "integrity": "sha512-bRydL/TorX9B6HMMGzggkTzoaY0dM1iCIdA/SGM8VB2P8+38TH+dqYmDdfLCR5LOdDUHq0XBFgkvVnb7DB61cw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "peerDependencies": { - "eslint": ">=8.40.0" + "eslint": ">=8.51.0" } }, "node_modules/eslint-plugin-eslint-comments": { @@ -2039,16 +1954,6 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, - "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -2070,18 +1975,6 @@ "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -2091,17 +1984,45 @@ "semver": "bin/semver.js" } }, - "node_modules/eslint-plugin-json": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-json/-/eslint-plugin-json-3.1.0.tgz", - "integrity": "sha512-MrlG2ynFEHe7wDGwbUuFPsaT2b1uhuEFhJ+W1f1u+1C2EkXmTYJp4B1aAdQQ8M+CC3t//N/oRKiIVw14L2HR1g==", + "node_modules/eslint-plugin-jsonc": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.13.0.tgz", + "integrity": "sha512-2wWdJfpO/UbZzPDABuUVvlUQjfMJa2p2iQfYt/oWxOMpXCcjuiMUSaA02gtY/Dbu82vpaSqc+O7Xq6ECHwtIxA==", "dev": true, "dependencies": { - "lodash": "^4.17.21", - "vscode-json-languageservice": "^4.1.6" + "@eslint-community/eslint-utils": "^4.2.0", + "eslint-compat-utils": "^0.4.0", + "espree": "^9.6.1", + "graphemer": "^1.4.0", + "jsonc-eslint-parser": "^2.0.4", + "natural-compare": "^1.4.0", + "synckit": "^0.6.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-jsonc/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": ">=12.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-plugin-markdown": { @@ -2210,6 +2131,22 @@ } } }, + "node_modules/eslint-plugin-prettier/node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/eslint-plugin-promise": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", @@ -2223,30 +2160,30 @@ } }, "node_modules/eslint-plugin-security": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-2.1.0.tgz", - "integrity": "sha512-ywxclP954bf8d3gr6KOQ/AFc+PRvWuhOxtPOEtiHmVYiZr/mcgQtmSJq6+hTEXC5ylTjHnPPG+PEnzlDiWMXbQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-2.1.1.tgz", + "integrity": "sha512-7cspIGj7WTfR3EhaILzAPcfCo5R9FbeWvbgsPYWivSurTBKW88VQxtP3c4aWMG9Hz/GfJlJVdXEJ3c8LqS+u2w==", "dev": true, "dependencies": { "safe-regex": "^2.1.1" } }, "node_modules/eslint-plugin-simple-import-sort": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-10.0.0.tgz", - "integrity": "sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.0.0.tgz", + "integrity": "sha512-8o0dVEdAkYap0Cn5kNeklaKcT1nUsa3LITWEuFk3nJifOoD+5JQGoyDUW2W/iPWwBsNBJpyJS9y4je/BgxLcyQ==", "dev": true, "peerDependencies": { "eslint": ">=5.0.0" } }, "node_modules/eslint-plugin-sonarjs": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.23.0.tgz", - "integrity": "sha512-z44T3PBf9W7qQ/aR+NmofOTyg6HLhSEZOPD4zhStqBpLoMp8GYhFksuUBnCxbnf1nfISpKBVkQhiBLFI/F4Wlg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.24.0.tgz", + "integrity": "sha512-87zp50mbbNrSTuoEOebdRQBPa0mdejA5UEjyuScyIw8hEpEjfWP89Qhkq5xVZfVyVSRQKZc9alVm7yRKQvvUmg==", "dev": true, "engines": { - "node": ">=14" + "node": ">=16" }, "peerDependencies": { "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" @@ -2265,10 +2202,31 @@ "node": ">=4" } }, + "node_modules/eslint-plugin-toml": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-toml/-/eslint-plugin-toml-0.9.2.tgz", + "integrity": "sha512-ri0xf63PYf3pIq/WY9BIwrqxZmGTIwSkAO0bHddI0ajUwN4KGz6W8vOvdXFHOpRdRfzxlmXze/vfsY/aTEXESg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "eslint-compat-utils": "^0.4.0", + "lodash": "^4.17.19", + "toml-eslint-parser": "^0.9.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/eslint-plugin-unicorn": { - "version": "50.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-50.0.1.tgz", - "integrity": "sha512-KxenCZxqSYW0GWHH18okDlOQcpezcitm5aOSz6EnobyJ6BIByiPDviQRjJIUAjG/tMN11958MxaQ+qCoU6lfDA==", + "version": "51.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-51.0.1.tgz", + "integrity": "sha512-MuR/+9VuB0fydoI0nIn2RDA5WISRn4AsJyNSaNKLVwie9/ONvQhxOBbkfSICBPnzKrB77Fh6CZZXjgTt/4Latw==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", @@ -2298,27 +2256,38 @@ "eslint": ">=8.56.0" } }, - "node_modules/eslint-plugin-yaml": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-yaml/-/eslint-plugin-yaml-0.5.0.tgz", - "integrity": "sha512-Z6km4HEiRptSuvzc96nXBND1Vlg57b7pzRmIJOgb9+3PAE+XpaBaiMx+Dg+3Y15tSrEMKCIZ9WoZMwkwUbPI8A==", + "node_modules/eslint-plugin-unicorn/node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", - "jshint": "^2.13.0" + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": "*" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/eslint-plugin-unicorn/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2327,25 +2296,42 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "node_modules/eslint-plugin-yml": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-yml/-/eslint-plugin-yml-1.12.2.tgz", + "integrity": "sha512-hvS9p08FhPT7i/ynwl7/Wt7ke7Rf4P2D6fT8lZlL43peZDTsHtH2A0SIFQ7Kt7+mJ6if6P+FX3iJhMkdnxQwpg==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "debug": "^4.3.2", + "eslint-compat-utils": "^0.4.0", + "lodash": "^4.17.21", + "natural-compare": "^1.4.0", + "yaml-eslint-parser": "^1.2.1" }, "engines": { - "node": ">=6" + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=6.0.0" } }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { @@ -2360,29 +2346,30 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/eslint/node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": "*" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/espree": { + "node_modules/eslint/node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", @@ -2399,17 +2386,33 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/espree": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", + "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "dependencies": { + "acorn": "^8.11.3", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" }, "engines": { - "node": ">=4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { @@ -2454,35 +2457,12 @@ "node": ">=0.10.0" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2536,38 +2516,14 @@ "dev": true }, "node_modules/fastq": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", - "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" } }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -2700,12 +2656,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, "node_modules/functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -2716,16 +2666,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2752,13 +2706,14 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -2808,28 +2763,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -2918,12 +2851,12 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2954,12 +2887,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -2969,9 +2902,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -2986,35 +2919,10 @@ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, - "node_modules/htmlparser2": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", - "integrity": "sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q==", - "dev": true, - "dependencies": { - "domelementtype": "1", - "domhandler": "2.3", - "domutils": "1.5", - "entities": "1.0", - "readable-stream": "1.1" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" @@ -3089,57 +2997,13 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/inquirer/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/inquirer/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -3172,14 +3036,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3519,12 +3385,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -3546,9 +3412,9 @@ } }, "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true }, "node_modules/isexe": { @@ -3614,58 +3480,6 @@ "node": ">=6" } }, - "node_modules/jshint": { - "version": "2.13.6", - "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.6.tgz", - "integrity": "sha512-IVdB4G0NTTeQZrBoM8C5JFVLjV2KtZ9APgybDA1MK73xb09qFs0jCXyQLnCOp1cSZZZbvhq/6mfXHUTaDkffuQ==", - "dev": true, - "dependencies": { - "cli": "~1.0.0", - "console-browserify": "1.1.x", - "exit": "0.1.x", - "htmlparser2": "3.8.x", - "lodash": "~4.17.21", - "minimatch": "~3.0.2", - "strip-json-comments": "1.0.x" - }, - "bin": { - "jshint": "bin/jshint" - } - }, - "node_modules/jshint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/jshint/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/jshint/node_modules/strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg==", - "dev": true, - "bin": { - "strip-json-comments": "cli.js" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -3684,634 +3498,188 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", - "dev": true - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/load-plugin": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/load-plugin/-/load-plugin-6.0.2.tgz", - "integrity": "sha512-3KRkTvCOsyNrx4zvBl/+ZMqPdVyp26TIf6xkmfEGuGwCfNQ/HzhktwbJCxd1KJpzPbK42t/WVOL3cX+TDaMRuQ==", - "dev": true, - "dependencies": { - "@npmcli/config": "^8.0.0", - "import-meta-resolve": "^4.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/load-plugin/node_modules/import-meta-resolve": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", - "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.last": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash.last/-/lodash.last-3.0.0.tgz", - "integrity": "sha512-14mq7rSkCxG4XMy9lF2FbIOqqgF0aH0NfPuQ3LPR3vIh0kHnUvIYP70dqa1Hf47zyXfQ8FzAg0MYOQeSuE1R7A==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.zipobject": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lodash.zipobject/-/lodash.zipobject-4.1.3.tgz", - "integrity": "sha512-A9SzX4hMKWS25MyalwcOnNoplyHbkNVsjidhTp8ru0Sj23wY9GWBKS8gAIGDSAqeWjIjvE4KBEl24XXAs+v4wQ==", - "dev": true - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/markdown-eslint-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/markdown-eslint-parser/-/markdown-eslint-parser-1.2.1.tgz", - "integrity": "sha512-ImxZH4YUT1BsYrusLPL8tWSZYUN4EZSjaSNL7KC8nsAYWavUgcK/Y1CuufbbkoSlqzv/tjFYLpyxcsaxo97dEA==", - "dev": true, - "dependencies": { - "eslint": "^6.8.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/markdown-eslint-parser/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/markdown-eslint-parser/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/markdown-eslint-parser/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/markdown-eslint-parser/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/markdown-eslint-parser/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/markdown-eslint-parser/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/markdown-eslint-parser/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/markdown-eslint-parser/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/markdown-eslint-parser/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/markdown-eslint-parser/node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/markdown-eslint-parser/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/markdown-eslint-parser/node_modules/eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.3", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/markdown-eslint-parser/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/markdown-eslint-parser/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/markdown-eslint-parser/node_modules/espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/markdown-eslint-parser/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/markdown-eslint-parser/node_modules/file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "dependencies": { - "flat-cache": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/markdown-eslint-parser/node_modules/flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "dependencies": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/markdown-eslint-parser/node_modules/flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "node_modules/markdown-eslint-parser/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/markdown-eslint-parser/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "dependencies": { - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markdown-eslint-parser/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/markdown-eslint-parser/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, - "node_modules/markdown-eslint-parser/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "minimist": "^1.2.0" }, "bin": { - "js-yaml": "bin/js-yaml.js" + "json5": "lib/cli.js" } }, - "node_modules/markdown-eslint-parser/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "node_modules/jsonc-eslint-parser": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.0.tgz", + "integrity": "sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==", "dev": true, "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "acorn": "^8.5.0", + "eslint-visitor-keys": "^3.0.0", + "espree": "^9.0.0", + "semver": "^7.3.5" }, "engines": { - "node": ">= 0.8.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" } }, - "node_modules/markdown-eslint-parser/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/jsonc-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "*" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/markdown-eslint-parser/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" + "json-buffer": "3.0.1" } }, - "node_modules/markdown-eslint-parser/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "dev": true, "engines": { - "node": ">=4" + "node": ">=6" } }, - "node_modules/markdown-eslint-parser/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/markdown-eslint-parser/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/load-plugin": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/load-plugin/-/load-plugin-6.0.2.tgz", + "integrity": "sha512-3KRkTvCOsyNrx4zvBl/+ZMqPdVyp26TIf6xkmfEGuGwCfNQ/HzhktwbJCxd1KJpzPbK42t/WVOL3cX+TDaMRuQ==", "dev": true, "dependencies": { - "glob": "^7.1.3" + "@npmcli/config": "^8.0.0", + "import-meta-resolve": "^4.0.0" }, - "bin": { - "rimraf": "bin.js" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/markdown-eslint-parser/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/load-plugin/node_modules/import-meta-resolve": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", + "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/markdown-eslint-parser/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "shebang-regex": "^1.0.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/markdown-eslint-parser/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, - "node_modules/markdown-eslint-parser/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } + "node_modules/lodash.last": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash.last/-/lodash.last-3.0.0.tgz", + "integrity": "sha512-14mq7rSkCxG4XMy9lF2FbIOqqgF0aH0NfPuQ3LPR3vIh0kHnUvIYP70dqa1Hf47zyXfQ8FzAg0MYOQeSuE1R7A==", + "dev": true }, - "node_modules/markdown-eslint-parser/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.zipobject": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lodash.zipobject/-/lodash.zipobject-4.1.3.tgz", + "integrity": "sha512-A9SzX4hMKWS25MyalwcOnNoplyHbkNVsjidhTp8ru0Sj23wY9GWBKS8gAIGDSAqeWjIjvE4KBEl24XXAs+v4wQ==", + "dev": true }, - "node_modules/markdown-eslint-parser/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/markdown-eslint-parser/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/markdown-eslint-parser/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { - "isexe": "^2.0.0" + "yallist": "^4.0.0" }, - "bin": { - "which": "bin/which" + "engines": { + "node": ">=10" } }, "node_modules/markdown-extensions": { @@ -5613,9 +4981,9 @@ } }, "node_modules/mdast-util-phrasing": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.0.0.tgz", - "integrity": "sha512-xadSsJayQIucJ9n053dfQwVu1kuXg7jCTdYsMK8rqzKZh52nLfSH/k0sAxE0u+pj/zKZX+o5wB+ML5mRayOxFA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "dev": true, "dependencies": { "@types/mdast": "^4.0.0", @@ -5985,9 +5353,9 @@ } }, "node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { @@ -6263,15 +5631,6 @@ "node": ">=8.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -6282,18 +5641,15 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, "node_modules/minimist": { @@ -6314,18 +5670,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -6341,12 +5685,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, "node_modules/mvdan-sh": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/mvdan-sh/-/mvdan-sh-0.10.1.tgz", @@ -6368,12 +5706,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -6497,15 +5829,16 @@ } }, "node_modules/object.groupby": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", - "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", + "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" } }, "node_modules/object.values": { @@ -6534,21 +5867,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -6566,15 +5884,6 @@ "node": ">= 0.8.0" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -6712,9 +6021,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", - "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -6766,9 +6075,9 @@ } }, "node_modules/prettier": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", - "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -6793,12 +6102,12 @@ } }, "node_modules/prettier-plugin-packagejson": { - "version": "2.4.9", - "resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.4.9.tgz", - "integrity": "sha512-b3Q7agXVqxK3UpYEJr0xLD51SxriYXESWUCjmxOBUGqnPFZOg9jZGZ+Ptzq252I6OqzXN2rj1tJIFq6KOGLLJw==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.4.11.tgz", + "integrity": "sha512-zmOmM96GkAjT2zUdHSQJnpyVpbisBkewDluo2NLHjI/JN7uOCZlEzWVaMhdqyZ8LVdQDfzamvbvSw4swd3Az1A==", "dev": true, "dependencies": { - "sort-package-json": "2.6.0", + "sort-package-json": "2.7.0", "synckit": "0.9.0" }, "peerDependencies": { @@ -6845,6 +6154,24 @@ "prettier": "^3.0.3" } }, + "node_modules/prettier-plugin-toml": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-toml/-/prettier-plugin-toml-2.0.1.tgz", + "integrity": "sha512-99z1YOkViECHtXQjGIigd3talI/ybUI1zB3yniAwUrlWBXupNXThB1hM6bwSMUEj2/+tomTlMtT98F5t4s8IWA==", + "dev": true, + "dependencies": { + "@taplo/lib": "^0.4.0-alpha.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + }, + "peerDependencies": { + "prettier": "^3.0.3" + } + }, "node_modules/proc-log": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", @@ -6854,15 +6181,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/proto-props": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/proto-props/-/proto-props-2.0.0.tgz", @@ -7026,15 +6344,17 @@ } }, "node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/readdirp": { @@ -7065,14 +6385,15 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -7081,15 +6402,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true, - "engines": { - "node": ">=6.5.0" - } - }, "node_modules/regjsparser": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz", @@ -11382,25 +10694,6 @@ "node": ">=4" } }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -11426,15 +10719,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -11458,24 +10742,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -11506,12 +10772,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -11542,13 +10802,13 @@ } }, "node_modules/safe-regex-test": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz", - "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", - "get-intrinsic": "^1.2.2", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, "engines": { @@ -11558,16 +10818,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -11580,14 +10834,15 @@ } }, "node_modules/set-function-length": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", - "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.1" }, @@ -11646,14 +10901,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11681,56 +10940,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/sort-object-keys": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-1.1.3.tgz", @@ -11738,9 +10947,9 @@ "dev": true }, "node_modules/sort-package-json": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.6.0.tgz", - "integrity": "sha512-XSQ+lY9bAYA8ZsoChcEoPlgcSMaheziEp1beox1JVxy1SV4F2jSq9+h2rJ+3mC/Dhu9Ius1DLnInD5AWcsDXZw==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.7.0.tgz", + "integrity": "sha512-6AayF8bp6L+WROgpbhTMUtB9JSFmpGHjmW7DyaNPS1HwlTw2oSVlUUtlkHSEZmg5o89F3zvLBZNvMeZ1T4fjQg==", "dev": true, "dependencies": { "detect-indent": "^7.0.1", @@ -11797,9 +11006,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.4.0.tgz", - "integrity": "sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true }, "node_modules/spdx-expression-parse": { @@ -11813,22 +11022,19 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", - "dev": true - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", "dev": true }, "node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } }, "node_modules/string-width": { "version": "6.1.0", @@ -12026,84 +11232,15 @@ } }, "node_modules/synckit": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", - "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", - "dev": true, - "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "dependencies": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/table/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/table/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/table/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.6.2.tgz", + "integrity": "sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==", "dev": true, "dependencies": { - "ansi-regex": "^4.1.0" + "tslib": "^2.3.1" }, "engines": { - "node": ">=6" + "node": ">=12.20" } }, "node_modules/text-table": { @@ -12112,24 +11249,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -12142,10 +11261,25 @@ "node": ">=8.0" } }, + "node_modules/toml-eslint-parser": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/toml-eslint-parser/-/toml-eslint-parser-0.9.3.tgz", + "integrity": "sha512-moYoCvkNUAPCxSW9jmHmRElhm4tVJpHL8ItC/+uYD0EpPSFXbck7yREz9tNdJVTSpHVod8+HoipcpbQ0oE6gsw==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, "node_modules/trough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", - "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "dev": true, "funding": { "type": "github", @@ -12153,13 +11287,13 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", "dev": true, "optional": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -12208,14 +11342,14 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz", + "integrity": "sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -12240,16 +11374,17 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.1.tgz", + "integrity": "sha512-tcqKMrTRXjqvHN9S3553NPCaGL0VPgFI92lXszmrE8DMhiDPLBYLlvo8Uu4WZAAX/aGqp/T1sbA4ph8EWjDF9Q==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", "for-each": "^0.3.3", + "gopd": "^1.0.1", "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -12444,6 +11579,15 @@ "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", "dev": true }, + "node_modules/unified-engine/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/unified-engine/node_modules/glob": { "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", @@ -12484,6 +11628,21 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/unified-engine/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/unified-engine/node_modules/parse-json": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", @@ -12877,12 +12036,6 @@ "node": ">=8" } }, - "node_modules/v8-compile-cache": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", - "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", - "dev": true - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -13145,43 +12298,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/vscode-json-languageservice": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.2.1.tgz", - "integrity": "sha512-xGmv9QIWs2H8obGbWg+sIPI/3/pFgj/5OWBhNzs00BkYQ9UaB2F6JJaGB/2/YOZJ3BvLXQTC4Q7muqU25QgAhA==", - "dev": true, - "dependencies": { - "jsonc-parser": "^3.0.0", - "vscode-languageserver-textdocument": "^1.0.3", - "vscode-languageserver-types": "^3.16.0", - "vscode-nls": "^5.0.0", - "vscode-uri": "^3.0.3" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz", - "integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==", - "dev": true - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "dev": true - }, - "node_modules/vscode-nls": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz", - "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==", - "dev": true - }, - "node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", - "dev": true - }, "node_modules/walk-up-path": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", @@ -13220,16 +12336,16 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -13238,15 +12354,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -13370,18 +12477,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "dependencies": { - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -13397,6 +12492,23 @@ "node": ">= 14" } }, + "node_modules/yaml-eslint-parser": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.2.2.tgz", + "integrity": "sha512-pEwzfsKbTrB8G3xc/sN7aw1v6A6c/pKxLAkjclnAyo5g5qOh6eL9WGu0o3cSDQZKrTNk4KL4lQSwZW+nBkANEg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.0.0", + "lodash": "^4.17.21", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index ed299d3..ceafa27 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "description": "linting for picopt", + "type": "module", "scripts": { "check": "remark . --quiet", - "fix": "eslint_d --cache --fix --ext .cjs,.mjs.js,.json,.yaml,.md . && prettier --write .", - "lint": "eslint_d --cache --ext .cjs,.mjs.js,.json,.yaml,.md . && prettier --check .", + "fix": "eslint --fix . && prettier --write .", + "lint": "eslint . && prettier --check .", "remark-check": "remark .", "remark-fix": "remark . --output" }, @@ -11,7 +12,18 @@ "plugins": [ "@prettier/plugin-xml", "prettier-plugin-packagejson", - "prettier-plugin-sh" + "prettier-plugin-sh", + "prettier-plugin-toml" + ], + "overrides": [ + { + "files": [ + "**/*.md" + ], + "options": { + "proseWrap": "always" + } + } ] }, "remarkConfig": { @@ -21,16 +33,21 @@ "preset-lint-markdown-style-guide", "preset-lint-recommended", "preset-prettier" - ] + ], + "settings": { + "ignorePath": ".gitignore" + } }, "devDependencies": { + "@eslint/eslintrc": "^3.0.1", "@fsouza/prettierd": "^0.25.2", "@prettier/plugin-xml": "^3.1.0", + "eslint": "^8.56.0", "eslint-config-prettier": "^9.0.0", - "eslint-plugin-array-func": "^4.0.0", + "eslint-plugin-array-func": "^5.0.1", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.27.5", - "eslint-plugin-json": "^3.1.0", + "eslint-plugin-jsonc": "^2.13.0", "eslint-plugin-markdown": "^3.0.0", "eslint-plugin-no-constructor-bind": "^2.0.4", "eslint-plugin-no-secrets": "^0.8.9", @@ -40,16 +57,17 @@ "eslint-plugin-prettier": "^5.0.0-alpha.2", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-security": "^2.1.0", - "eslint-plugin-simple-import-sort": "^10.0.0", - "eslint-plugin-sonarjs": "^0.23.0", + "eslint-plugin-simple-import-sort": "^12.0.0", + "eslint-plugin-sonarjs": "^0.24.0", "eslint-plugin-switch-case": "^1.1.2", - "eslint-plugin-unicorn": "^50.0.1", - "eslint-plugin-yaml": "^0.5.0", + "eslint-plugin-toml": "^0.9.2", + "eslint-plugin-unicorn": "^51.0.1", + "eslint-plugin-yml": "^1.12.2", "eslint_d": "^13.1.2", - "markdown-eslint-parser": "^1.2.1", "prettier": "^3.0.0", "prettier-plugin-packagejson": "^2.4.4", "prettier-plugin-sh": "^0.14.0", + "prettier-plugin-toml": "^2.0.1", "remark": "^15.0.1", "remark-cli": "^12.0.0", "remark-gfm": "^4.0.0", diff --git a/run.py b/picopt.py similarity index 100% rename from run.py rename to picopt.py diff --git a/run.sh b/picopt.sh similarity index 57% rename from run.sh rename to picopt.sh index 257b65f..e55eab2 100755 --- a/run.sh +++ b/picopt.sh @@ -1,3 +1,3 @@ #!/bin/sh # Run picopt in development -poetry run ./run.py "$@" +poetry run ./picopt.py "$@" diff --git a/picopt/cli.py b/picopt/cli.py index b3c5e36..d288c22 100644 --- a/picopt/cli.py +++ b/picopt/cli.py @@ -1,23 +1,22 @@ """Run pictures through image specific external optimizers.""" -import argparse import sys -from argparse import Action, Namespace, RawDescriptionHelpFormatter +from argparse import Action, ArgumentParser, Namespace, RawDescriptionHelpFormatter from importlib.metadata import PackageNotFoundError, version -from typing import Optional +from confuse.exceptions import ConfigError from termcolor import colored, cprint from picopt import PROGRAM_NAME, walk from picopt.config import ALL_FORMAT_STRS, DEFAULT_HANDLERS, get_config +from picopt.exceptions import PicoptError from picopt.handlers.png import Png -from picopt.handlers.webp import WebP -from picopt.handlers.zip import CBR, Rar +from picopt.handlers.webp import WebPLossless +from picopt.handlers.zip import Cbr, Rar -DEFAULT_FORMAT_STRS = frozenset( +_DEFAULT_FORMAT_STRS = frozenset( [handler_cls.OUTPUT_FORMAT_STR for handler_cls in DEFAULT_HANDLERS] ) -EXTRA_FORMAT_STRS = ALL_FORMAT_STRS - DEFAULT_FORMAT_STRS -FORMAT_DELIMETER = "," +_LIST_DELIMETER = "," try: VERSION = version(PROGRAM_NAME) except PackageNotFoundError: @@ -30,7 +29,7 @@ class SplitArgsAction(Action): def __call__(self, _parser, namespace, values, _option_string=None): """Split values string into list.""" if isinstance(values, str): - values = tuple(sorted(values.strip().split(FORMAT_DELIMETER))) + values = tuple(sorted(values.strip().split(_LIST_DELIMETER))) setattr(namespace, self.dest, values) @@ -39,7 +38,7 @@ def _comma_join(formats: frozenset[str]) -> str: return ", ".join(sorted(formats)) -def get_arguments(params: Optional[tuple[str, ...]] = None) -> Namespace: +def get_arguments(params: tuple[str, ...] | None = None) -> Namespace: """Parse the command line.""" description = "Losslessly optimizes and optionally converts images." epilog = ( @@ -53,7 +52,7 @@ def get_arguments(params: Optional[tuple[str, ...]] = None) -> Namespace: colored("error", "red"), ) epilog = "\n ".join(epilog) - parser = argparse.ArgumentParser( + parser = ArgumentParser( description=description, epilog=epilog, formatter_class=RawDescriptionHelpFormatter, @@ -90,8 +89,8 @@ def get_arguments(params: Optional[tuple[str, ...]] = None) -> Namespace: action=SplitArgsAction, dest="formats", help="Only optimize images of the specified " - f"'{FORMAT_DELIMETER}' delimited formats from: {_comma_join(ALL_FORMAT_STRS)}. " - f"Defaults to {_comma_join(DEFAULT_FORMAT_STRS)}", + f"comma delimited formats from: {_comma_join(ALL_FORMAT_STRS)}. " + f"Defaults to {_comma_join(_DEFAULT_FORMAT_STRS)}", ) parser.add_argument( "-x", @@ -106,11 +105,18 @@ def get_arguments(params: Optional[tuple[str, ...]] = None) -> Namespace: action=SplitArgsAction, dest="convert_to", help="A list of formats to convert to. Lossless images may convert to" - f" {Png.OUTPUT_FORMAT_STR} or {WebP.OUTPUT_FORMAT_STR}." + f" {Png.OUTPUT_FORMAT_STR} or {WebPLossless.OUTPUT_FORMAT_STR}." f" {Rar.INPUT_FORMAT_STR} archives" - f" may convert to {Rar.OUTPUT_FORMAT_STR} or {CBR.OUTPUT_FORMAT_STR}." + f" may convert to {Rar.OUTPUT_FORMAT_STR} or {Cbr.OUTPUT_FORMAT_STR}." " By default formats are not converted to other formats.", ) + parser.add_argument( + "-n", + "--near-lossless", + action="store_true", + dest="near_lossless", + help="Precompress lossless WebP images with near lossless pixel adjustments. Provides more compression for little to no visual quality loss especially for discrete tone images like drawings.", + ) parser.add_argument( "-S", "--no-symlinks", @@ -123,7 +129,7 @@ def get_arguments(params: Optional[tuple[str, ...]] = None) -> Namespace: "--ignore", action=SplitArgsAction, dest="ignore", - help="List of globs to ignore.", + help="Comma dilenated list of globs to ignore.", ) parser.add_argument( "-b", @@ -171,13 +177,12 @@ def get_arguments(params: Optional[tuple[str, ...]] = None) -> Namespace: dest="list_only", help="Only list files that would be optimized", ) - parser.add_argument( "-M", - "--destroy-metadata", + "--strip-metadata", action="store_false", dest="keep_metadata", - help="Destroy metadata like EXIF and ICC Profiles", + help="Strip metadata like EXIF, XMP and ICC Profiles", ) parser.add_argument( "-j", @@ -186,7 +191,7 @@ def get_arguments(params: Optional[tuple[str, ...]] = None) -> Namespace: action="store", dest="jobs", help="Number of parallel jobs to run simultaneously. Defaults " - "to maximum available.", + "to number of available cores.", ) parser.add_argument( "-C", @@ -195,6 +200,18 @@ def get_arguments(params: Optional[tuple[str, ...]] = None) -> Namespace: action="store", help="Path to a config file", ) + parser.add_argument( + "-p", + "--preserve", + action="store_true", + help="Preserve file attributes (uid, gid, mode, mtime) after optimization.", + ) + parser.add_argument( + "-D", + "--disable-programs", + action=SplitArgsAction, + help="Disable a comma delineated list of external programs from optimizing files.", + ) ########### # Actions # ########### @@ -220,10 +237,6 @@ def get_arguments(params: Optional[tuple[str, ...]] = None) -> Namespace: pns = parser.parse_args(params) - # Move extra_formats to computed namespace - pns.computed = Namespace(extra_formats=pns.extra_formats) - delattr(pns, "extra_formats") - # increment verbose if pns.verbose is not None and pns.verbose > 0: pns.verbose += 1 @@ -231,14 +244,21 @@ def get_arguments(params: Optional[tuple[str, ...]] = None) -> Namespace: return Namespace(picopt=pns) -def main(args: Optional[tuple[str, ...]] = None): +def main(args: tuple[str, ...] | None = None): """Process command line arguments and walk inputs.""" try: arguments = get_arguments(args) config = get_config(arguments) wob = walk.Walk(config) - wob.run() + totals = wob.run() + totals.report() + except ConfigError as err: + cprint(f"ERROR: {err}", "red") + sys.exit(78) + except PicoptError as err: + cprint(f"ERROR: {err}", "red") + sys.exit(1) except Exception as exc: cprint(f"ERROR: {exc}", "red") import traceback diff --git a/picopt/config.py b/picopt/config.py index 0e84485..533d93c 100644 --- a/picopt/config.py +++ b/picopt/config.py @@ -1,86 +1,87 @@ """Confuse config for picopt.""" -import pathlib -import stat +import shutil +import subprocess import time -import typing from argparse import Namespace -from copy import deepcopy +from collections.abc import ItemsView, Iterable from dataclasses import dataclass, fields -from typing import Any +from pathlib import Path +from types import MappingProxyType -from confuse import Configuration +from confuse import Configuration, Subview from confuse.templates import ( AttrDict, Choice, Integer, MappingTemplate, Optional, - Path, Sequence, ) +from confuse.templates import Path as ConfusePath from dateutil.parser import parse from termcolor import cprint from picopt import PROGRAM_NAME -from picopt.handlers.gif import AnimatedGif, Gif -from picopt.handlers.handler import FileFormat, Handler -from picopt.handlers.image import ( - BPM_FILE_FORMAT, - CONVERTABLE_FILE_FORMATS, - CONVERTABLE_FORMAT_STRS, - PNG_ANIMATED_FILE_FORMAT, - PPM_FILE_FORMAT, - TIFF_ANIMATED_FILE_FORMAT, - TIFF_FILE_FORMAT, - TIFF_FORMAT_STR, +from picopt.formats import ( + CONVERTIBLE_ANIMATED_FILE_FORMATS, + CONVERTIBLE_FILE_FORMATS, + LOSSLESS_FORMAT_STRS, + MODERN_CWEBP_FORMAT_STRS, + MPO_FILE_FORMAT, + FileFormat, ) +from picopt.handlers.gif import Gif, GifAnimated +from picopt.handlers.handler import Handler from picopt.handlers.jpeg import Jpeg from picopt.handlers.png import Png -from picopt.handlers.webp import Gif2WebP, WebPLossless +from picopt.handlers.png_animated import PngAnimated +from picopt.handlers.svg import Svg +from picopt.handlers.webp import WebPLossless from picopt.handlers.webp_animated import WebPAnimatedLossless -from picopt.handlers.zip import CBR, CBZ, EPub, Rar, Zip +from picopt.handlers.zip import Cbr, Cbz, EPub, Rar, Zip -_PNG_CONVERTABLE_FILE_FORMATS = CONVERTABLE_FILE_FORMATS | frozenset( - [Gif.OUTPUT_FILE_FORMAT] -) -_WEBP_CONVERTABLE_FILE_FORMATS = _PNG_CONVERTABLE_FILE_FORMATS | frozenset( - [Png.OUTPUT_FILE_FORMAT] +########################### +# Confuse Config Template # +########################### +_CONVERT_TO_FORMAT_STRS = frozenset( + { + cls.OUTPUT_FORMAT_STR + for cls in ( + Png, + PngAnimated, + WebPLossless, + WebPAnimatedLossless, + Zip, + Cbz, + Jpeg, + ) + } ) -PNG_CONVERTABLE_FORMAT_STRS = frozenset( - [img_format.format_str for img_format in _PNG_CONVERTABLE_FILE_FORMATS] +_CONTAINER_CONVERTIBLE_FORMAT_STRS = frozenset( + {cls.INPUT_FORMAT_STR for cls in (Rar, Cbr)} ) -WEBP_CONVERTABLE_FORMAT_STRS = frozenset( - [img_format.format_str for img_format in _WEBP_CONVERTABLE_FILE_FORMATS] + +DEFAULT_HANDLERS = frozenset( + {Gif, GifAnimated, Jpeg, Png, WebPLossless, WebPAnimatedLossless} ) -CONVERT_TO_FORMAT_STRS = frozenset( - ( - Png.OUTPUT_FORMAT_STR, - WebPLossless.OUTPUT_FORMAT_STR, - Zip.OUTPUT_FORMAT_STR, - CBZ.OUTPUT_FORMAT_STR, +_ALL_HANDLERS = frozenset( + DEFAULT_HANDLERS + | frozenset( + { + Zip, + Rar, + Svg, + Cbz, + Cbr, + EPub, + } ) ) -CONTAINER_CONVERTABLE_FORMAT_STRS = frozenset( - (Rar.INPUT_FORMAT_STR, CBR.INPUT_FORMAT_STR) -) -DEFAULT_HANDLERS = (Gif, AnimatedGif, Jpeg, Png, WebPLossless) -HANDLERS = frozenset( - [ - *DEFAULT_HANDLERS, - Gif2WebP, - WebPAnimatedLossless, - Zip, - Rar, - CBZ, - CBR, - EPub, - ] -) ALL_FORMAT_STRS: frozenset[str] = ( - frozenset([cls.OUTPUT_FORMAT_STR for cls in HANDLERS]) - | CONVERTABLE_FORMAT_STRS - | frozenset([TIFF_FORMAT_STR]) - | CONTAINER_CONVERTABLE_FORMAT_STRS + frozenset([cls.OUTPUT_FORMAT_STR for cls in _ALL_HANDLERS]) + | LOSSLESS_FORMAT_STRS + | _CONTAINER_CONVERTIBLE_FORMAT_STRS + | {MPO_FILE_FORMAT.format_str} ) TEMPLATE = MappingTemplate( { @@ -88,13 +89,17 @@ { "after": Optional(float), "bigger": bool, - "convert_to": Optional(Sequence(Choice(CONVERT_TO_FORMAT_STRS))), + "convert_to": Optional(Sequence(Choice(_CONVERT_TO_FORMAT_STRS))), + "disable_programs": Sequence(str), + "extra_formats": Optional(Sequence(Choice(ALL_FORMAT_STRS))), "formats": Sequence(Choice(ALL_FORMAT_STRS)), "ignore": Sequence(str), "jobs": Integer(), "keep_metadata": bool, "list_only": bool, - "paths": Sequence(Path()), + "near_lossless": bool, + "paths": Sequence(ConfusePath()), + "preserve": bool, "recurse": bool, "symlinks": bool, "test": bool, @@ -104,19 +109,10 @@ "computed": Optional( MappingTemplate( { - "available_programs": frozenset, - "convertable_formats": Optional( - MappingTemplate( - { - "webp": Optional(frozenset), - "png": Optional(frozenset), - } - ) - ), - "extra_formats": Optional( - Sequence(Choice(ALL_FORMAT_STRS)) - ), - "format_handlers": dict, + "native_handlers": dict, + "convert_handlers": dict, + "handler_stages": dict, + "is_modern_cwebp": bool, } ) ), @@ -130,263 +126,357 @@ "formats", "ignore", "keep_metadata", + "near_lossless", "recurse", "symlinks", } +# cwebp before this version only accepts PNG & WEBP +MIN_CWEBP_VERSION = (1, 2, 3) +_JPEG_PROGS = frozenset({"mozjpeg", "jpegtran"}) +######################## +# File Format Handlers # +######################## @dataclass class FileFormatHandlers: """FileFormat handlers for a File FileFormat.""" - # Pyright can't enforce a list of subclasses - # https://github.com/microsoft/pyright/issues/130 - # https://peps.python.org/pep-0483/#covariance-and-contravariance - convert: Any = None - native: Any = None - - def _field_to_tuple(self, field): - """Convert a handler to a tuple of handlers.""" - val = getattr(self, field.name) - if val is None: - setattr(self, field.name, ()) - elif not isinstance(val, tuple): - val = (val,) - setattr(self, field.name, val) - - def __post_init__(self): - """Convert raw handlers into tuples.""" - for field in fields(self): - self._field_to_tuple(field) - - def items(self): + convert: tuple[type[Handler], ...] = () + native: tuple[type[Handler], ...] = () + + def items(self) -> ItemsView[str, tuple[type[Handler], ...]]: """Return both fields.""" - return {"convert": self.convert, "native": self.native}.items() + return { + field.name: tuple(getattr(self, field.name)) for field in fields(self) + }.items() # Handlers for formats are listed in priority order -_FORMAT_HANDLERS = { - PPM_FILE_FORMAT: FileFormatHandlers(convert=(WebPLossless, Png)), - BPM_FILE_FORMAT: FileFormatHandlers(convert=(WebPLossless, Png)), - Gif.OUTPUT_FILE_FORMAT: FileFormatHandlers(convert=(Gif2WebP, Png), native=Gif), - AnimatedGif.OUTPUT_FILE_FORMAT: FileFormatHandlers( - convert=Gif2WebP, native=AnimatedGif - ), - Jpeg.OUTPUT_FILE_FORMAT: FileFormatHandlers(native=Jpeg), - Png.OUTPUT_FILE_FORMAT: FileFormatHandlers(convert=WebPLossless, native=Png), - WebPLossless.OUTPUT_FILE_FORMAT: FileFormatHandlers(native=WebPLossless), - WebPAnimatedLossless.OUTPUT_FILE_FORMAT: FileFormatHandlers( - native=WebPAnimatedLossless - ), - Zip.OUTPUT_FILE_FORMAT: FileFormatHandlers(native=Zip), - CBZ.OUTPUT_FILE_FORMAT: FileFormatHandlers(native=CBZ), - Rar.INPUT_FILE_FORMAT: FileFormatHandlers(convert=Rar), - CBR.INPUT_FILE_FORMAT: FileFormatHandlers(convert=CBR), - EPub.OUTPUT_FILE_FORMAT: FileFormatHandlers(native=EPub), - TIFF_FILE_FORMAT: FileFormatHandlers(convert=(WebPLossless, Png)), - TIFF_ANIMATED_FILE_FORMAT: FileFormatHandlers(convert=WebPAnimatedLossless), - PNG_ANIMATED_FILE_FORMAT: FileFormatHandlers(convert=WebPAnimatedLossless), -} -MODE_EXECUTABLE = stat.S_IXUSR ^ stat.S_IXGRP ^ stat.S_IXOTH +_LOSSLESS_CONVERTIBLE_FORMAT_HANDLERS = MappingProxyType( + { + ffmt: FileFormatHandlers(convert=(WebPLossless, Png)) + for ffmt in CONVERTIBLE_FILE_FORMATS + } +) +_LOSSLESS_CONVERTIBLE_ANIMATED_FORMAT_HANDLERS = MappingProxyType( + { + ffmt: FileFormatHandlers(convert=(WebPAnimatedLossless, PngAnimated)) + for ffmt in CONVERTIBLE_ANIMATED_FILE_FORMATS + } +) +_FORMAT_HANDLERS = MappingProxyType( + { + **_LOSSLESS_CONVERTIBLE_FORMAT_HANDLERS, + **_LOSSLESS_CONVERTIBLE_ANIMATED_FORMAT_HANDLERS, + Gif.OUTPUT_FILE_FORMAT: FileFormatHandlers( + convert=(WebPLossless, Png), + native=(Gif,), + ), + GifAnimated.OUTPUT_FILE_FORMAT: FileFormatHandlers( + convert=(WebPAnimatedLossless, PngAnimated), + native=(GifAnimated,), + ), + MPO_FILE_FORMAT: FileFormatHandlers(convert=(Jpeg,)), + Jpeg.OUTPUT_FILE_FORMAT: FileFormatHandlers(native=(Jpeg,)), + Png.OUTPUT_FILE_FORMAT: FileFormatHandlers( + convert=(WebPLossless,), native=(Png,) + ), + PngAnimated.OUTPUT_FILE_FORMAT: FileFormatHandlers( + convert=(WebPAnimatedLossless,), native=(PngAnimated,) + ), + WebPLossless.OUTPUT_FILE_FORMAT: FileFormatHandlers(native=(WebPLossless,)), + WebPAnimatedLossless.OUTPUT_FILE_FORMAT: FileFormatHandlers( + native=(WebPAnimatedLossless,) + ), + Svg.OUTPUT_FILE_FORMAT: FileFormatHandlers(native=(Svg,)), + Zip.OUTPUT_FILE_FORMAT: FileFormatHandlers(native=(Zip,)), + Cbz.OUTPUT_FILE_FORMAT: FileFormatHandlers(native=(Cbz,)), + Rar.INPUT_FILE_FORMAT: FileFormatHandlers(convert=(Rar,)), + Cbr.INPUT_FILE_FORMAT: FileFormatHandlers(convert=(Cbr,)), + EPub.OUTPUT_FILE_FORMAT: FileFormatHandlers(native=(EPub,)), + } +) -def _is_external_program_executable( - program: str, bin_path: typing.Optional[str], verbose: int -) -> bool: - """Test to see if the external programs can be run.""" - try: - if not bin_path: - return False - path = pathlib.Path(bin_path) - mode = path.stat().st_mode - result = bool(mode & MODE_EXECUTABLE) - except Exception: - if bin_path and verbose: +def _print_formats_config( + verbose: int, + handled_format_strs: set[str], + convert_format_strs: dict[str, set[str]], + is_modern_cwebp: bool, + cwebp_version: str, +) -> None: + """Print verbose init messages.""" + handled_format_list = ", ".join(sorted(handled_format_strs)) + cprint(f"Optimizing formats: {handled_format_list}") + for convert_to_format_str, format_strs in convert_format_strs.items(): + convert_from_list = sorted(format_strs) + if not convert_from_list: + return + convert_from = ", ".join(convert_from_list) + cprint(f"Converting {convert_from} to {convert_to_format_str}", "cyan") + if ( + verbose > 1 + and not is_modern_cwebp + and WebPAnimatedLossless.OUTPUT_FORMAT_STR in convert_format_strs + ): + to_webp_strs = MODERN_CWEBP_FORMAT_STRS & handled_format_strs + if to_webp_strs: + to_web_str = " & ".join(sorted(to_webp_strs)) cprint( - f"WARNING: Could not find executable program for '{program}'", "yellow" + f"Converting {to_web_str} with an extra step for older cwebp {cwebp_version}", + "cyan", ) - result = False - return result - -def _get_available_programs(config: Configuration) -> frozenset: - """Run the external program tester on the required binaries.""" - verbose = config[PROGRAM_NAME]["verbose"].get(int) - if not isinstance(verbose, int): - msg = f"wrong type for convert_to: {type(verbose)} {verbose}" - raise TypeError(msg) - programs = set() - for handler in HANDLERS: - for program, bin_path in handler.PROGRAMS.items(): - if ( - program == Handler.INTERNAL - or program.startswith("pil2") - or _is_external_program_executable(program, bin_path, verbose) - ): - programs.add(program) - if not programs: - msg = "No optimizers are available or all optimizers are disabled" - raise ValueError(msg) - return frozenset(programs) - - -def _config_formats_list_to_set(config, key, computed=False) -> frozenset[str]: - source = config[PROGRAM_NAME] - if computed: - source = source["computed"] - val_list = source[key].get() +def _config_formats_list_to_set(config: Subview, key: str) -> frozenset[str]: + val_list: Iterable = config[key].get(list) if key in config else [] # type: ignore val_set = set() for val in val_list: val_set.add(val.upper()) return frozenset(val_set) -def _update_formats_png(format_strs, convert_handlers, config): - """Update formats if converting to png.""" - convertable_formats = set(PNG_CONVERTABLE_FORMAT_STRS) - format_strs |= PNG_CONVERTABLE_FORMAT_STRS - file_formats = deepcopy(_PNG_CONVERTABLE_FILE_FORMATS) - if TIFF_FORMAT_STR in format_strs: - file_formats.add(TIFF_FILE_FORMAT) - convertable_formats.add(TIFF_FORMAT_STR) - convert_handlers[Png] = file_formats - config[PROGRAM_NAME]["computed"]["convertable_formats"]["png"] = frozenset( - convertable_formats - ) - return format_strs - - -def _update_formats_webp_lossless(format_strs, convert_handlers, config): - """Update formats if converting to webp lossless.""" - convertable_format_strs = set(WEBP_CONVERTABLE_FORMAT_STRS) - format_strs |= WEBP_CONVERTABLE_FORMAT_STRS - file_formats = deepcopy(_WEBP_CONVERTABLE_FILE_FORMATS) - if TIFF_FORMAT_STR in format_strs: - file_formats.add(TIFF_FILE_FORMAT) - convertable_format_strs.add(TIFF_FORMAT_STR) - convert_handlers[WebPLossless] = file_formats - - convert_handlers[Gif2WebP] = { - Gif.OUTPUT_FILE_FORMAT, - AnimatedGif.OUTPUT_FILE_FORMAT, - } - if TIFF_FORMAT_STR in format_strs: - if WebPAnimatedLossless not in convert_handlers: - convert_handlers[WebPAnimatedLossless] = set() - convert_handlers[WebPAnimatedLossless].add(TIFF_ANIMATED_FILE_FORMAT) - if Png.OUTPUT_FORMAT_STR in format_strs: - if WebPAnimatedLossless not in convert_handlers: - convert_handlers[WebPAnimatedLossless] = set() - convert_handlers[WebPAnimatedLossless].add(PNG_ANIMATED_FILE_FORMAT) - config[PROGRAM_NAME]["computed"]["convertable_formats"]["webp"] = frozenset( - convertable_format_strs +def _set_all_format_strs(config: Subview) -> frozenset[str]: + all_format_strs = _config_formats_list_to_set(config, "formats") + extra_format_strs = _config_formats_list_to_set(config, "extra_formats") + + all_format_strs |= extra_format_strs + + config["formats"] = tuple(sorted(all_format_strs)) + + return frozenset(all_format_strs) + + +def _get_handler_stages( + handler_class: type[Handler], disabled_programs: frozenset +) -> dict[str, tuple[str, ...]]: + """Get the program stages for each handler.""" + stages = {} + for program_priority_list in handler_class.PROGRAMS: + for program in program_priority_list: + if program in disabled_programs: + continue + if program.startswith(("pil2", Handler.INTERNAL)): + exec_args = None + elif program.startswith("npx_"): + bin_path = shutil.which("npx") + if not bin_path: + continue + exec_args = (bin_path, "--no", *program.split("_")[1:]) + # XXX sucks but easiest way to determine if an npx prog exists is + # running it. + try: + subprocess.run( + exec_args, # noqa S603 + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + except (subprocess.CalledProcessError, FileNotFoundError, OSError): + continue + else: + bin_path = shutil.which(program) + if not bin_path: + continue + exec_args = (bin_path,) + stages[program] = exec_args + break + return stages + + +def _set_format_handler_map_entry( # noqa: PLR0913 + handler_type: str, + handler_class: type[Handler], + convert_to: frozenset[str], + handler_stages: dict[type[Handler], dict[str, tuple[str, ...]]], + convert_format_strs: dict, + file_format: FileFormat, + convert_handlers: dict[FileFormat, type[Handler]], + native_handlers: dict[FileFormat, type[Handler]], + handled_format_strs: set[str], + disabled_programs: frozenset, +) -> bool: + """Create an entry for the format handler maps.""" + if ( + handler_type == "convert" + and handler_class.OUTPUT_FILE_FORMAT.format_str not in convert_to + ): + return False + + # Get handler stages by class with caching + if handler_class not in handler_stages: + handler_stages[handler_class] = _get_handler_stages( + handler_class, disabled_programs + ) + stages = handler_stages.get(handler_class) + if not stages: + return False + + if handler_type == "convert": + if handler_class.OUTPUT_FORMAT_STR not in convert_format_strs: + convert_format_strs[handler_class.OUTPUT_FORMAT_STR] = set() + convert_format_strs[handler_class.OUTPUT_FORMAT_STR].add(file_format.format_str) + convert_handlers[file_format] = handler_class + else: + native_handlers[file_format] = handler_class + + handled_format_strs.add(file_format.format_str) + return True + + +def _get_cwebp_version(handler_stages: dict): + """Get the version of cwebp.""" + cwebp_version = "" + bin_path = handler_stages.get(WebPLossless, {}).get("cwebp") + if not bin_path: + return cwebp_version + args = (*bin_path, "-version") + result = subprocess.run( + args, # noqa S603 + capture_output=True, + text=True, + check=True, ) - return format_strs - + if result.returncode == 0: + cwebp_version = result.stdout.splitlines()[0].strip() + return cwebp_version -def _update_formats_zip(format_strs, convert_handlers): - """Update formats if converting to zip.""" - format_strs |= {Rar.INPUT_FORMAT_STR} - convert_handlers[Rar] = {Rar.INPUT_FILE_FORMAT} - return format_strs +def _is_cwebp_modern(handler_stages: dict) -> tuple[bool, str]: + cwebp_version = "Unknown" + try: + cwebp_version = _get_cwebp_version(handler_stages) + if not cwebp_version: + return False, cwebp_version + parts = cwebp_version.split(".") + for index in range(len(MIN_CWEBP_VERSION)): + test_part = int(parts[index]) + ref_part = MIN_CWEBP_VERSION[index] + if test_part > ref_part: + return True, cwebp_version + if test_part < ref_part: + return False, cwebp_version + except Exception: + return False, cwebp_version + return True, cwebp_version -def _update_formats_cbz(format_strs, convert_handlers): - """Update formats if converting to cbz.""" - format_strs |= {CBR.INPUT_FORMAT_STR} - convert_handlers[CBR] = {CBR.INPUT_FILE_FORMAT} - return format_strs +def _set_format_handler_map(config: Subview) -> None: + """Create a format to handler map from config.""" + all_format_strs = _set_all_format_strs(config) + convert_to = _config_formats_list_to_set(config, "convert_to") -def _update_formats(config: Configuration) -> dict: - formats = _config_formats_list_to_set(config, "formats") - if "extra_formats" in config[PROGRAM_NAME]["computed"]: - extra_formats = _config_formats_list_to_set( - config, "extra_formats", computed=True - ) - config[PROGRAM_NAME]["computed"]["extra_formats"].set(sorted(extra_formats)) - formats |= extra_formats + native_handlers: dict[FileFormat, type[Handler]] = {} + convert_handlers: dict[FileFormat, type[Handler]] = {} + handler_stages: dict[type[Handler], dict[str, tuple[str, ...]]] = {} - convert_to = _config_formats_list_to_set(config, "convert_to") - config[PROGRAM_NAME]["convert_to"].set(sorted(convert_to)) - convert_handlers: dict[type[Handler], set[FileFormat]] = {} - if Png.OUTPUT_FORMAT_STR in convert_to: - formats = _update_formats_png(formats, convert_handlers, config) - if WebPLossless.OUTPUT_FORMAT_STR in convert_to: - formats = _update_formats_webp_lossless(formats, convert_handlers, config) - if Rar.OUTPUT_FORMAT_STR in convert_to: - formats = _update_formats_zip(formats, convert_handlers) - if CBR.OUTPUT_FORMAT_STR in convert_to: - formats = _update_formats_cbz(formats, convert_handlers) - config[PROGRAM_NAME]["formats"].set(sorted(formats)) - - return convert_handlers - - -def _create_format_handler_map(config: Configuration, convert_handlers: dict) -> None: - """Create a format to handler map from config.""" - available_programs = _get_available_programs(config) - format_handlers = {} - format_strs = config[PROGRAM_NAME]["formats"].get(list) - if not isinstance(format_strs, list): - msg = f"wrong type for formats: {type(format_strs)}{format_strs}" - raise TypeError(msg) - format_strs = set(format_strs) + handled_format_strs = set() + convert_format_strs = {} + disabled_programs: list | frozenset = config["disable_programs"].get(list) # type: ignore + disabled_programs = ( + frozenset(disabled_programs) if disabled_programs else frozenset() + ) for file_format, possible_file_handlers in _FORMAT_HANDLERS.items(): - if file_format.format_str not in format_strs: + if file_format.format_str not in all_format_strs: continue for ( handler_type, possible_handler_classes, - ) in possible_file_handlers.items(): + ) in sorted(possible_file_handlers.items()): for handler_class in possible_handler_classes: - available = handler_class.is_handler_available( - convert_handlers, available_programs, file_format - ) - if available: - if file_format not in format_handlers: - format_handlers[file_format] = {} - format_handlers[file_format][handler_type] = handler_class + if _set_format_handler_map_entry( + handler_type, + handler_class, + convert_to, + handler_stages, + convert_format_strs, + file_format, + convert_handlers, + native_handlers, + handled_format_strs, + disabled_programs, + ): break - config[PROGRAM_NAME]["computed"]["format_handlers"].set(format_handlers) - config[PROGRAM_NAME]["computed"]["available_programs"].set(available_programs) + + is_modern_cwebp, cwebp_version = _is_cwebp_modern(handler_stages) + + convert_handlers = native_handlers | convert_handlers + + config["computed"]["native_handlers"].set(native_handlers) + config["computed"]["convert_handlers"].set(convert_handlers) + config["computed"]["handler_stages"].set(handler_stages) + config["computed"]["is_modern_cwebp"].set(is_modern_cwebp) + verbose: int = config["verbose"].get(int) # type: ignore + _print_formats_config( + verbose, + handled_format_strs, + convert_format_strs, + is_modern_cwebp, + cwebp_version, + ) -def _set_after(config) -> None: - after = config[PROGRAM_NAME]["after"].get() +######################### +# Other Computed Config # +######################### +def _set_after(config: Subview) -> None: + after = config["after"].get() if after is None: return try: - timestamp = float(after) + timestamp = float(after) # type: ignore except ValueError: - after_dt = parse(after) + after_dt = parse(after) # type: ignore timestamp = time.mktime(after_dt.timetuple()) - config[PROGRAM_NAME]["after"].set(timestamp) + config["after"].set(timestamp) + if timestamp is not None: + after = time.ctime(timestamp) + cprint(f"Optimizing after {after}") -def _set_ignore(config) -> None: +def _set_ignore(config: Subview) -> None: """Remove duplicates from the ignore list.""" - ignore: list[str] = config[PROGRAM_NAME]["ignore"].get(list) - config[PROGRAM_NAME]["ignore"].set(tuple(sorted(set(ignore)))) + ignore: Iterable = config["ignore"].get(list) # type: ignore + ignore = tuple(sorted(ignore)) + config["ignore"].set(ignore) + if ignore: + verbose: int = config["verbose"].get(int) # type: ignore + if verbose > 1: + ignore_list = ",".join(ignore) + cprint(f"Ignoring: {ignore_list}", "cyan") -def _set_timestamps(config) -> None: +def _set_timestamps(config: Subview) -> None: """Set the timestamps attribute.""" timestamps = ( - config[PROGRAM_NAME]["timestamps"].get(bool) - and not config[PROGRAM_NAME]["test"].get(bool) - and not config[PROGRAM_NAME]["list_only"].get(bool) + config["timestamps"].get(bool) + and not config["test"].get(bool) + and not config["list_only"].get(bool) ) - config[PROGRAM_NAME]["timestamps"].set(timestamps) - - -def get_config( - args: typing.Optional[Namespace] = None, modname=PROGRAM_NAME -) -> AttrDict: + config["timestamps"].set(timestamps) + verbose: int = config["verbose"].get(int) # type: ignore + if verbose > 1: + if timestamps: + roots = set() + paths: Iterable = config["paths"].get(list) # type: ignore + for path_str in paths: + path = Path(path_str) + if path.is_dir(): + roots.add(str(path)) + else: + roots.add(str(path.parent)) + roots_str = ", ".join(sorted(roots)) + ts_str = f"Setting a timestamp file at the top of each directory tree: {roots_str}" + else: + ts_str = "Not setting timestamps." + cprint(ts_str, "cyan") + + +def get_config(args: Namespace | None = None, modname=PROGRAM_NAME) -> AttrDict: """Get the config dict, layering env and args over defaults.""" config = Configuration(PROGRAM_NAME, modname=modname, read=False) config.read() @@ -395,11 +485,11 @@ def get_config( config.set_env() if args: config.set_args(args) - _set_after(config) - convert_handlers = _update_formats(config) - _create_format_handler_map(config, convert_handlers) - _set_ignore(config) - _set_timestamps(config) + config_program = config[PROGRAM_NAME] + _set_format_handler_map(config_program) + _set_after(config_program) + _set_ignore(config_program) + _set_timestamps(config_program) ad = config.get(TEMPLATE) if not isinstance(ad, AttrDict): msg = "Not a valid config" diff --git a/picopt/config_default.yaml b/picopt/config_default.yaml index e92b475..b956eb1 100644 --- a/picopt/config_default.yaml +++ b/picopt/config_default.yaml @@ -2,12 +2,15 @@ picopt: after: null bigger: False convert_to: [] - formats: ["GIF", "JPEG", "PNG", "WEBP"] + disable_programs: [] + formats: [GIF, JPEG, PNG, WEBP] ignore: [] jobs: 0 keep_metadata: True list_only: False + near_lossless: False paths: [] + preserve: False recurse: False symlinks: True test: False diff --git a/picopt/configurable.py b/picopt/configurable.py deleted file mode 100644 index aa7ea5a..0000000 --- a/picopt/configurable.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Abstract class to hold config and match ignores.""" -from pathlib import Path - -from confuse import AttrDict - - -class Configurable: - """Match ignore paths.""" - - def __init__(self, config: AttrDict) -> None: - """Initialize.""" - self._config: AttrDict = config - - def is_path_ignored(self, path: Path): - """Match path against the ignore listg.""" - return any(path.match(ignore_glob) for ignore_glob in self._config.ignore) diff --git a/picopt/data.py b/picopt/data.py deleted file mode 100644 index ea78cbc..0000000 --- a/picopt/data.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Data classes.""" -from dataclasses import dataclass -from pathlib import Path -from typing import Optional - - -@dataclass -class PathInfo: - """Path Info object, mostly for passing down walk.""" - - path: Path - top_path: Path - container_mtime: Optional[float] - convert: bool - is_case_sensitive: bool - - -@dataclass -class ReportInfo: - """Info for Reports.""" - - path: Path - convert: bool - test: bool - bytes_in: int = 0 - bytes_out: int = 0 - exc: Optional[Exception] = None diff --git a/picopt/exceptions.py b/picopt/exceptions.py new file mode 100644 index 0000000..a9c418a --- /dev/null +++ b/picopt/exceptions.py @@ -0,0 +1,5 @@ +"""Picopt exceptions.""" + + +class PicoptError(Exception): + """An an exception sepecifically raised by picopt.""" diff --git a/picopt/formats.py b/picopt/formats.py new file mode 100644 index 0000000..f82e6bc --- /dev/null +++ b/picopt/formats.py @@ -0,0 +1,127 @@ +"""Convertible format definitions.""" +from dataclasses import dataclass + +from PIL.BmpImagePlugin import BmpImageFile, DibImageFile +from PIL.CurImagePlugin import CurImageFile +from PIL.FitsImagePlugin import FitsImageFile +from PIL.FliImagePlugin import FliImageFile +from PIL.GifImagePlugin import GifImageFile +from PIL.ImtImagePlugin import ImtImageFile +from PIL.MpoImagePlugin import MpoImageFile +from PIL.PcxImagePlugin import PcxImageFile +from PIL.PixarImagePlugin import PixarImageFile +from PIL.PngImagePlugin import PngImageFile +from PIL.PpmImagePlugin import PpmImageFile +from PIL.PsdImagePlugin import PsdImageFile +from PIL.QoiImagePlugin import QoiImageFile +from PIL.SgiImagePlugin import SgiImageFile +from PIL.SpiderImagePlugin import SpiderImageFile +from PIL.SunImagePlugin import SunImageFile +from PIL.TgaImagePlugin import TgaImageFile +from PIL.TiffImagePlugin import TiffImageFile +from PIL.XbmImagePlugin import XbmImageFile +from PIL.XpmImagePlugin import XpmImageFile + + +@dataclass(eq=True, frozen=True) +class FileFormat: + """A file format, with image attributes.""" + + format_str: str + lossless: bool = True + animated: bool = False + + +MPO_FILE_FORMAT = FileFormat(MpoImageFile.format, False, True) +SVG_FORMAT_STR = "SVG" +TIFF_FILE_FORMAT = FileFormat(TiffImageFile.format, True, False) +TIFF_LOSSLESS_COMPRESSION = frozenset( + { + None, + "group3", + "group4", + "lzma", + "packbits", + "tiff_adobe_deflate", + "tiff_ccitt", + "tiff_lzw", + "tiff_raw_16", + "tiff_sgilog", + "tiff_sgilog24", + "tiff_thunderscan", + "zstd", + } +) +# TIFF_LOSSY_COMPRESSION = frozenset({ +# "jpeg", "webp" +# }) +PNGINFO_XMP_KEY = "XML:com.adobe.xmp" +PPM_FILE_FORMAT = FileFormat(PpmImageFile.format, True, False) +MODERN_CWEBP_FORMATS = frozenset({PPM_FILE_FORMAT, TIFF_FILE_FORMAT}) +MODERN_CWEBP_FORMAT_STRS = frozenset( + sorted(fmt.format_str for fmt in MODERN_CWEBP_FORMATS) +) + + +_CONVERTABLE_PIL_IMAGE_FILES = ( + ################################### + # PIL Read/Write lossless formats # + ################################### + BmpImageFile, + DibImageFile, + # GifImageFile, + PcxImageFile, + PpmImageFile, + # PngImageFile, + SgiImageFile, + SpiderImageFile, + TgaImageFile, + TiffImageFile, + XbmImageFile, + ################################## + # PIL Read only lossless formats # + ################################## + CurImageFile, + FitsImageFile, + ImtImageFile, + PixarImageFile, + PsdImageFile, + SunImageFile, + XpmImageFile, + QoiImageFile, + ################### + # Animated to not # + ################### + MpoImageFile, +) +CONVERTIBLE_FORMAT_STRS = frozenset( + {img_file.format for img_file in _CONVERTABLE_PIL_IMAGE_FILES} +) + +CONVERTIBLE_FILE_FORMATS = frozenset( + {FileFormat(format_str, True, False) for format_str in CONVERTIBLE_FORMAT_STRS} +) +_CONVERTABLE_PIL_ANIMATED_IMAGE_FILES = ( + ################################# + # PIL lossless animated formats # + ################################# + FliImageFile, + # GifImageFile, + # PngImageFile, + TiffImageFile, +) +CONVERTIBLE_ANIMATED_FORMAT_STRS = frozenset( + {img_file.format for img_file in _CONVERTABLE_PIL_ANIMATED_IMAGE_FILES} +) +CONVERTIBLE_ANIMATED_FILE_FORMATS = frozenset( + { + FileFormat(format_str, True, True) + for format_str in CONVERTIBLE_ANIMATED_FORMAT_STRS + } +) + +LOSSLESS_FORMAT_STRS = frozenset( + CONVERTIBLE_FORMAT_STRS - {MpoImageFile.format} + | CONVERTIBLE_ANIMATED_FORMAT_STRS + | {GifImageFile.format, PngImageFile.format, SVG_FORMAT_STR} +) diff --git a/picopt/handlers/container.py b/picopt/handlers/container.py index 0d4c61e..758671a 100644 --- a/picopt/handlers/container.py +++ b/picopt/handlers/container.py @@ -1,14 +1,16 @@ """Optimize comic archives.""" -import shutil from abc import ABCMeta, abstractmethod -from pathlib import Path -from typing import Optional, Union +from collections.abc import Generator, Mapping +from io import BytesIO +from multiprocessing.pool import ApplyResult +from typing import BinaryIO from confuse.templates import AttrDict from termcolor import cprint -from picopt.data import PathInfo, ReportInfo -from picopt.handlers.handler import FileFormat, Handler, Metadata +from picopt.formats import FileFormat +from picopt.handlers.handler import Handler +from picopt.path import PathInfo from picopt.stats import ReportStats @@ -20,15 +22,15 @@ class ContainerHandler(Handler, metaclass=ABCMeta): @classmethod @abstractmethod - def identify_format(cls, path: Path) -> Optional[FileFormat]: + def identify_format(cls, path_info: PathInfo) -> FileFormat | None: """Return the format if this handler can handle this path.""" @abstractmethod - def unpack_into(self) -> None: + def unpack_into(self) -> Generator[PathInfo, None, None]: """Unpack a container into a tmp dir to work on it's contents.""" @abstractmethod - def pack_into(self, working_path: Path) -> None: + def pack_into(self) -> BytesIO: """Create a container from a tmp dir's contents.""" def __init__( @@ -36,75 +38,62 @@ def __init__( config: AttrDict, path_info: PathInfo, file_format: FileFormat, - metadata: Metadata, + info: Mapping, ): """Unpack a container with a subclass's unpacker.""" super().__init__( config, path_info, file_format, - metadata, - ) - self.comment: Optional[bytes] = None - self.tmp_container_dir: Path = Path( - str(self.get_working_path()) + self.CONTAINER_DIR_SUFFIX + info, ) + self.comment: bytes | None = None + self._tasks: dict[PathInfo, ApplyResult] = {} + self._optimized_contents: dict[PathInfo, bytes] = {} + + def get_container_paths(self) -> tuple[str, ...]: + """Create a container path for output.""" + return (*self.path_info.container_paths, str(self.original_path)) - def unpack(self) -> Union[Handler, ReportStats]: + def unpack(self) -> Generator[PathInfo, None, None]: """Create directory and unpack container.""" - try: - if self.config.verbose: - cprint(f"Unpacking {self.original_path}...", end="") - - # create a clean tmpdir - if self.tmp_container_dir.exists(): - shutil.rmtree(self.tmp_container_dir) - self.tmp_container_dir.mkdir(parents=True) - - # extract archive into the tmpdir - self.unpack_into() - - if self.config.verbose: - cprint("done") - except Exception as exc: - return self.error(exc) - return self - - def cleanup_after_optimize(self, last_working_path: Path) -> tuple[int, int]: - """Clean up the temp dir as well as the old container.""" if self.config.verbose: - cprint(".", end="") - shutil.rmtree(self.tmp_container_dir) + cprint(f"Unpacking {self.original_path}...", end="") - if self.config.verbose: - cprint(".", end="") - bytes_count = super().cleanup_after_optimize(last_working_path) + yield from self.unpack_into() if self.config.verbose: - cprint("done.") - return bytes_count + cprint("done") + + def set_task(self, path_info: PathInfo, mp_result: ApplyResult | None) -> None: + """Store the mutiprocessing task.""" + if mp_result is None: + # if not handled by picopt, place it in the results. + self._optimized_contents[path_info] = path_info.data() + else: + self._tasks[path_info] = mp_result + + def optimize_contents(self) -> None: + """Store results from mutiprocessing task.""" + for path_info in tuple(self._tasks): + mp_results = self._tasks.pop(path_info) + report = mp_results.get() + data = report.data if report.data else path_info.data() + # Clearing has to happen AFTER mp_results.get() or we risk not passing the data + path_info.data_clear() + path_info.container_filename = str(report.path) + self._optimized_contents[path_info] = data + + def optimize(self) -> BinaryIO: + """Run pack_into.""" + return self.pack_into() def repack(self) -> ReportStats: """Create a new container and clean up the tmp dir.""" - new_path = self.get_working_path() - try: - # archive into new filename - if self.config.verbose: - cprint(f"Repacking {self.final_path}", end="") - self.pack_into(new_path) - - bytes_count = self.cleanup_after_optimize(new_path) - info = ReportInfo( - self.final_path, - self.convert, - self.config.test, - bytes_count[0], - bytes_count[1], - ) - report_stats = ReportStats(info) - if self.config.verbose: - report_stats.report() - except Exception as exc: - shutil.rmtree(self.tmp_container_dir, ignore_errors=True) - return self.error(exc) + if self.config.verbose: + cprint(f"Repacking {self.final_path}...", end="") + + report_stats = self.optimize_wrapper() + if self.config.verbose: + cprint("done") return report_stats diff --git a/picopt/handlers/factory.py b/picopt/handlers/factory.py index ca5b7bb..5246aed 100644 --- a/picopt/handlers/factory.py +++ b/picopt/handlers/factory.py @@ -1,121 +1,184 @@ """Return a handler for a path.""" -from pathlib import Path -from typing import Optional +from collections.abc import Mapping +from contextlib import suppress +from typing import Any from confuse.templates import AttrDict from PIL import Image, UnidentifiedImageError +from PIL.JpegImagePlugin import JpegImageFile +from PIL.PngImagePlugin import PngImageFile +from PIL.TiffImagePlugin import XMP as TIFF_XMP_TAG +from PIL.TiffImagePlugin import TiffImageFile from termcolor import cprint -from picopt.config import WEBP_CONVERTABLE_FORMAT_STRS -from picopt.data import PathInfo -from picopt.handlers.container import ContainerHandler -from picopt.handlers.handler import FileFormat, Handler, Metadata -from picopt.handlers.image import TIFF_FORMAT_STR +from picopt.formats import ( + LOSSLESS_FORMAT_STRS, + PNGINFO_XMP_KEY, + TIFF_LOSSLESS_COMPRESSION, + FileFormat, +) +from picopt.handlers.handler import Handler +from picopt.handlers.non_pil import NonPILIdentifier +from picopt.handlers.svg import Svg from picopt.handlers.webp import WebPLossless -from picopt.handlers.zip import CBR, CBZ, EPub, Rar, Zip +from picopt.handlers.zip import Cbr, Cbz, EPub, Rar, Zip +from picopt.path import PathInfo +from picopt.pillow.jpeg_xmp import get_jpeg_xmp from picopt.pillow.webp_lossless import is_lossless -_ALWAYS_LOSSLESS_FORMAT_STRS = WEBP_CONVERTABLE_FORMAT_STRS - {TIFF_FORMAT_STR} -_CONTAINER_HANDLERS: tuple[type[ContainerHandler], ...] = (CBZ, Zip, CBR, Rar, EPub) - - -def _is_lossless(image_format: str, path: Path, info: dict) -> bool: - """Determine if image format is lossless.""" - if image_format in _ALWAYS_LOSSLESS_FORMAT_STRS: - lossless = True - elif image_format == WebPLossless.OUTPUT_FORMAT_STR: - lossless = is_lossless(str(path)) - elif image_format == TIFF_FORMAT_STR: - lossless = info.get("compression") != "jpeg" - else: - lossless = False - return lossless - - -def _extract_image_info(path, keep_metadata): +################### +# Get File Format # +################### +_NON_PIL_HANDLERS: tuple[type[NonPILIdentifier], ...] = ( + Svg, + Cbz, + Zip, + Cbr, + Rar, + EPub, +) + + +def _set_xmp(keep_metadata: bool, image: Image.Image, info: dict) -> None: + """Extract and set XMP info in the info dict.""" + # Pillow's only extracts raw xmp data into the info dict sometimes for some formats. + # Pillow's support for writing xmp data is very different between PNG & WEBP. + if not keep_metadata or "xmp" in info: + return + try: + xmp = None + if image.format == JpegImageFile.format: + xmp = get_jpeg_xmp(image) # type: ignore + elif image.format == PngImageFile.format: + xmp = info.get(PNGINFO_XMP_KEY) + if isinstance(image, TiffImageFile): + # elif image.format == TiffImageFile.format: + xmp = image.tag_v2.get(TIFF_XMP_TAG) + if xmp: + info["xmp"] = xmp + except Exception: + cprint("Failed to extract xmp data:") + + +def _extract_image_info( + path_info: PathInfo, keep_metadata: bool +) -> tuple[str | None, dict[str, Any]]: """Get image format and info from a file.""" image_format_str = None info = {} n_frames = 1 animated = False try: - with Image.open(path, mode="r") as image: + fp = path_info.path_or_buffer() + with Image.open(fp) as image: image.verify() image.close() # for animated images - with Image.open(path, mode="r") as image: + with suppress(AttributeError): + fp.close() # type: ignore + fp = path_info.path_or_buffer() + with Image.open(fp) as image: image_format_str = image.format if image_format_str: + info = image.info if keep_metadata else {} animated = getattr(image, "is_animated", False) - info = image.info - if keep_metadata and animated: - n_frames = getattr(image, "n_frames", 1) + info["animated"] = animated + if animated: + n_frames = image.n_frames + if n_frames is not None: + info["n_frames"] = n_frames + try: + _set_xmp(keep_metadata, image, info) + except Exception as exc: + cprint( + f"WARNING: Failed to extract xmp data for {path_info.full_name()}, {exc}", + "yellow", + ) + with suppress(AttributeError): + info["mpinfo"] = image.mpinfo # type: ignore image.close() # for animated images + with suppress(AttributeError): + fp.close() # type: ignore except UnidentifiedImageError: pass - return image_format_str, info, n_frames, animated + return image_format_str, info + + +def _is_lossless( + image_format_str: str, path_info: PathInfo, info: Mapping[str, Any] +) -> bool: + """Determine if image format is lossless.""" + if image_format_str == WebPLossless.OUTPUT_FORMAT_STR: + lossless = is_lossless(path_info.fp_or_buffer()) + elif image_format_str == TiffImageFile.format: + lossless = info.get("compression") in TIFF_LOSSLESS_COMPRESSION + else: + lossless = image_format_str in LOSSLESS_FORMAT_STRS + return lossless def _get_image_format( - path: Path, keep_metadata: bool -) -> tuple[Optional[FileFormat], Metadata]: + path_info: PathInfo, keep_metadata: bool +) -> tuple[FileFormat | None, Mapping[str, Any]]: """Construct the image format with PIL.""" - image_format_str, info, n_frames, animated = _extract_image_info( - path, keep_metadata - ) + image_format_str, info = _extract_image_info(path_info, keep_metadata) file_format = None - metadata = Metadata() if image_format_str: - if keep_metadata: - exif = info.get("exif", b"") - icc = info.get("icc_profile", "") - metadata = Metadata(exif, icc, n_frames) - lossless = _is_lossless(image_format_str, path, info) - file_format = FileFormat(image_format_str, lossless, animated) - return file_format, metadata + lossless = _is_lossless(image_format_str, path_info, info) + file_format = FileFormat( + image_format_str, lossless, info.get("animated", False) + ) + return file_format, info -def _get_container_format(path: Path) -> Optional[FileFormat]: +def _get_non_pil_format(path_info: PathInfo) -> FileFormat | None: """Get the container format by querying each handler.""" file_format = None - for container_handler in _CONTAINER_HANDLERS: - file_format = container_handler.identify_format(path) + for handler in _NON_PIL_HANDLERS: + file_format = handler.identify_format(path_info) if file_format is not None: break + else: + file_format = None return file_format -def _get_handler_class( - config: AttrDict, key: str, file_format: FileFormat -) -> Optional[type[Handler]]: - handler_classes = config.computed.format_handlers.get(file_format) - return handler_classes.get(key) if handler_classes else None - - def _create_handler_get_format( - config: AttrDict, path: Path -) -> tuple[Optional[FileFormat], Metadata]: - file_format, metadata = _get_image_format(path, config.keep_metadata) + config: AttrDict, path_info: PathInfo +) -> tuple[FileFormat | None, Mapping[str, Any]]: + file_format, info = _get_image_format(path_info, config.keep_metadata) if not file_format: - file_format = _get_container_format(path) - return file_format, metadata + file_format = _get_non_pil_format(path_info) + return file_format, info + + +##################### +# Get Handler Class # +##################### +def _get_handler_class( + config: AttrDict, file_format: FileFormat, key: str +) -> type[Handler] | None: + format_handlers = config.computed.get(key) + return format_handlers.get(file_format) def _create_handler_get_handler_class( - config: AttrDict, convert: bool, file_format: Optional[FileFormat] -) -> Optional[type[Handler]]: - handler_cls: Optional[type[Handler]] = None - if not file_format: - return handler_cls - if convert: - handler_cls = _get_handler_class(config, "convert", file_format) - if not handler_cls: - handler_cls = _get_handler_class(config, "native", file_format) + config: AttrDict, convert: bool, file_format: FileFormat | None +) -> type[Handler] | None: + handler_cls: type[Handler] | None = None + if file_format and file_format.format_str in config.formats: + if convert: + handler_cls = _get_handler_class(config, file_format, "convert_handlers") + if not handler_cls: + handler_cls = _get_handler_class(config, file_format, "native_handlers") return handler_cls +#################### +# No Handler Class # +#################### def _create_handler_no_handler_class( - config: AttrDict, path: Path, file_format: Optional[FileFormat] + config: AttrDict, path_info: PathInfo, file_format: FileFormat | None ) -> None: if config.verbose > 1 and not config.list_only: if file_format: @@ -129,7 +192,7 @@ def _create_handler_no_handler_class( else: fmt = "unknown" cprint( - f"Skipped {path}: ({fmt}) is not an enabled image or container.", + f"Skipped {path_info.full_name()}: ({fmt}) is not an enabled image or container.", "white", attrs=["dark"], ) @@ -137,22 +200,25 @@ def _create_handler_no_handler_class( cprint(".", "white", attrs=["dark"], end="") -def create_handler(config: AttrDict, path_info: PathInfo) -> Optional[Handler]: +def create_handler(config: AttrDict, path_info: PathInfo) -> Handler | None: """Get the image format.""" # This is the consumer of config._format_handlers - file_format: Optional[FileFormat] = None - metadata: Metadata = Metadata() - handler_cls: Optional[type[Handler]] = None + handler_cls: type[Handler] | None = None try: - file_format, metadata = _create_handler_get_format(config, path_info.path) + file_format, info = _create_handler_get_format(config, path_info) handler_cls = _create_handler_get_handler_class( config, path_info.convert, file_format ) except OSError as exc: cprint(f"WARNING: getting handler {exc}", "yellow") + from traceback import print_exc + + print_exc() + file_format = None + info = {} if handler_cls and file_format is not None: - handler = handler_cls(config, path_info, file_format, metadata) + handler = handler_cls(config, path_info, file_format, info) else: - handler = _create_handler_no_handler_class(config, path_info.path, file_format) + handler = _create_handler_no_handler_class(config, path_info, file_format) return handler diff --git a/picopt/handlers/gif.py b/picopt/handlers/gif.py index 7ddc916..fae2c80 100644 --- a/picopt/handlers/gif.py +++ b/picopt/handlers/gif.py @@ -1,13 +1,11 @@ """Gif format.""" -import shutil -from pathlib import Path +from io import BytesIO from types import MappingProxyType -from typing import Optional +from typing import BinaryIO -from PIL import Image from PIL.GifImagePlugin import GifImageFile -from picopt.handlers.handler import FileFormat +from picopt.formats import FileFormat from picopt.handlers.image import ImageHandler @@ -16,34 +14,25 @@ class Gif(ImageHandler): OUTPUT_FORMAT_STR = GifImageFile.format OUTPUT_FILE_FORMAT = FileFormat(OUTPUT_FORMAT_STR, True, False) - PROGRAMS: MappingProxyType[str, Optional[str]] = ImageHandler.init_programs( - ("gifsicle", "pil2gif") - ) - _ARGS_PREFIX: tuple[Optional[str], ...] = ( - PROGRAMS.get("gifsicle", ""), + INPUT_FILE_FORMATS = frozenset({OUTPUT_FILE_FORMAT}) + PROGRAMS = (("gifsicle", "pil2native"),) + PIL2_KWARGS = MappingProxyType({"optimize": True}) + _GIFSICLE_ARGS_PREFIX: tuple[str, ...] = ( "--optimize=3", - "--batch", + "--threads", + "--output", + "-", + "-", ) - def gifsicle(self, old_path: Path, new_path: Path) -> Path: + def gifsicle(self, exec_args: tuple[str, ...], input_buffer: BinaryIO) -> BytesIO: """Return gifsicle args.""" - if not self._ARGS_PREFIX[0]: - return old_path - - shutil.copy2(old_path, new_path) - args = (*self._ARGS_PREFIX, str(new_path)) - self.run_ext(args) - return new_path - - def pil2gif(self, old_path: Path, new_path: Path) -> Path: - """Pillow gif optimization.""" - with Image.open(old_path) as image: - image.save(new_path, self.OUTPUT_FORMAT_STR, optimize=True, save_all=True) - image.close() # for animated images - return new_path + args = (*exec_args, *self._GIFSICLE_ARGS_PREFIX) + return self.run_ext(args, input_buffer) -class AnimatedGif(Gif): +class GifAnimated(Gif): """Animated GIF handler.""" OUTPUT_FILE_FORMAT = FileFormat(Gif.OUTPUT_FORMAT_STR, True, True) + INPUT_FILE_FORMATS = frozenset({OUTPUT_FILE_FORMAT}) diff --git a/picopt/handlers/handler.py b/picopt/handlers/handler.py index 61a730f..b684ece 100644 --- a/picopt/handlers/handler.py +++ b/picopt/handlers/handler.py @@ -1,66 +1,59 @@ """FileType abstract class for image and container formats.""" -import shutil +import os import subprocess -from abc import ABC -from dataclasses import dataclass +from abc import ABC, abstractmethod +from collections.abc import Mapping +from io import BufferedReader, BytesIO from pathlib import Path from types import MappingProxyType -from typing import Optional +from typing import Any, BinaryIO from confuse.templates import AttrDict +from PIL.PngImagePlugin import PngImageFile, PngInfo +from PIL.WebPImagePlugin import WebPImageFile from termcolor import cprint from picopt import PROGRAM_NAME -from picopt.data import PathInfo, ReportInfo +from picopt.formats import PNGINFO_XMP_KEY, FileFormat +from picopt.path import PathInfo from picopt.stats import ReportStats +SAVE_INFO_KEYS: frozenset[str] = frozenset( + {"n_frames", "loop", "duration", "background"} +) +WORKING_PATH_TRANS_TABLE = str.maketrans({c: "_" for c in " /"}) -@dataclass(eq=True, frozen=True) -class FileFormat: - """A file format, with image attributes.""" - format_str: str - lossless: bool = True - animated: bool = False +def _gif_palette_index_to_rgb( + palette_index: int, +) -> tuple[int, int, int]: + """Convert an 8-bit color palette index to an RGB tuple.""" + # Extract the individual color components from the palette index. + red = (palette_index >> 5) & 0x7 + green = (palette_index >> 2) & 0x7 + blue = palette_index & 0x3 + # Scale the color components to the range 0-255. + red = red * 36 + green = green * 36 + blue = blue * 36 -@dataclass(eq=True, frozen=True) -class Metadata: - """Image metadata class.""" - - exif: bytes = b"" - icc_profile: str = "" - n_frames: int = 1 + return (red, green, blue) class Handler(ABC): """FileType superclass for image and container formats.""" - BEST_ONLY: bool = True OUTPUT_FORMAT_STR: str = "unimplemented" OUTPUT_FILE_FORMAT: FileFormat = FileFormat(OUTPUT_FORMAT_STR, False, False) - INTERNAL: str = "python_internal" - PROGRAMS: MappingProxyType[str, Optional[str]] = MappingProxyType({}) - WORKING_SUFFIX: str = f"{PROGRAM_NAME}__tmp" + INPUT_FILE_FORMATS: frozenset[FileFormat] = frozenset({OUTPUT_FILE_FORMAT}) + CONVERT_FROM_FORMAT_STRS: frozenset[str] = frozenset() + INTERNAL: str = "internal" + PROGRAMS: tuple[tuple[str, ...], ...] = () + WORKING_SUFFIX: str = f"{PROGRAM_NAME}-tmp" @classmethod - def init_programs( - cls, programs: tuple[str, ...] - ) -> MappingProxyType[str, Optional[str]]: - """Initialize the PROGRAM map.""" - program_dict = {} - for program in programs: - if program.startswith("pil2") or program == cls.INTERNAL: - bin_path = None - else: - bin_path = shutil.which(program) - if not bin_path: - continue - program_dict[program] = bin_path - return MappingProxyType(program_dict) - - @staticmethod - def run_ext(args: tuple[Optional[str], ...]) -> None: + def run_ext(cls, args: tuple[str, ...], input_buffer: BinaryIO) -> BytesIO: """Run EXTERNAL program.""" for arg in args: # Guarantee tuple[str] @@ -68,33 +61,31 @@ def run_ext(args: tuple[Optional[str], ...]) -> None: reason = f"{args}" raise ValueError(reason) - subprocess.run( - args, # noqa S603 # type: ignore + input_buffer.seek(0) + result = subprocess.run( + args, # noqa: S603 check=True, - text=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.PIPE, + input=input_buffer.read(), + stdout=subprocess.PIPE, ) + return BytesIO(result.stdout) - @classmethod - def native_input_file_formats(cls) -> frozenset[FileFormat]: - """Return input formats handled without conversion.""" - return frozenset({cls.OUTPUT_FILE_FORMAT}) + def get_working_path(self, identifier: str) -> Path: + """Return a working path with a custom suffix.""" + # Used by cwebp + if cps := self.path_info.container_paths: + path_tail = "__".join((*cps[1:], str(self.original_path))) + path_tail = path_tail.translate(WORKING_PATH_TRANS_TABLE) + path = Path(cps[0] + "__" + path_tail) + else: + path = self.original_path - @classmethod - def is_handler_available( - cls, - convert_handlers: dict, - available_programs: set, - file_format: FileFormat, - ): - """Can this handler run with available programs.""" - handled_file_formats = cls.native_input_file_formats() | convert_handlers.get( - cls, set() - ) - return file_format in handled_file_formats and bool( - available_programs & set(cls.PROGRAMS.keys()) - ) + suffixes = [self.original_path.suffix, self.WORKING_SUFFIX] + if identifier: + suffixes += [identifier] + suffix = ".".join(suffixes) + suffix += self.output_suffix + return path.with_suffix(suffix) @classmethod def get_default_suffix(cls): @@ -113,70 +104,199 @@ def __init__( config: AttrDict, path_info: PathInfo, input_file_format: FileFormat, - metadata: Metadata, + info: Mapping[str, Any], ): """Initialize handler.""" self.config: AttrDict = config - self.original_path: Path = path_info.path - self.working_paths: set[Path] = set() + self.path_info: PathInfo = path_info + self.original_path: Path = ( + path_info.path if path_info.path else Path(path_info.name()) + ) + self.working_path = self.original_path default_suffix = self.get_default_suffix() self._suffixes = self.get_suffixes(default_suffix) + suffix = path_info.suffix() self.output_suffix: str = ( - self.original_path.suffix - if self.original_path - and self.original_path.suffix.lower() in self._suffixes - else default_suffix + suffix if (suffix.lower() in self._suffixes) else default_suffix ) self.final_path: Path = self.original_path.with_suffix(self.output_suffix) self.input_file_format: FileFormat = input_file_format - self.metadata = metadata - self.convert = input_file_format != self.OUTPUT_FILE_FORMAT - self.is_case_sensitive = path_info.is_case_sensitive + self.info: dict[str, Any] = dict(info) + if self.config.preserve: + self.path_info.stat() + self._input_file_formats = self.INPUT_FILE_FORMATS - def get_working_path(self, identifier: str = "") -> Path: - """Return a working path with a custom suffix.""" - suffixes = [self.original_path.suffix, self.WORKING_SUFFIX] - if identifier: - suffixes += [identifier] - suffix = ".".join(suffixes) - suffix += self.output_suffix - return self.original_path.with_suffix(suffix) + def prepare_info(self, format_str) -> MappingProxyType[str, Any]: + """Prepare an info dict for saving.""" + if format_str == WebPImageFile.format: + self.info.pop("background", None) + background = self.info.get("background") + if isinstance(background, int): + # GIF background is an int. + rgb = _gif_palette_index_to_rgb(background) + self.info["background"] = (*rgb, 0) + if format_str == PngImageFile.format: + transparency = self.info.get("transparency") + if isinstance(transparency, int): + self.info.pop("transparency", None) + if xmp := self.info.get("xmp", None): + pnginfo = self.info.get("pnginfo", PngInfo()) + pnginfo.add_text(PNGINFO_XMP_KEY, xmp, zip=True) + self.info["pnginfo"] = pnginfo + if self.config.keep_metadata: + info = self.info + else: + info = {} + for key, val in self.info: + if key in SAVE_INFO_KEYS: + info[key] = val + return MappingProxyType(info) + + def run_ext_fs( # noqa: PLR0913 + self, + args: tuple[str | None, ...], + input_buffer: BinaryIO, + input_path: Path, + output_path: Path, + input_path_tmp: bool, + output_path_tmp: bool, + ) -> BinaryIO: + """Run EXTERNAL program that lacks stdin/stdout streaming.""" + if input_path_tmp: + with input_path.open("wb") as input_tmp_file, input_buffer: + input_buffer.seek(0) + input_tmp_file.write(input_buffer.read()) + + subprocess.run( + args, # noqa S603 # type: ignore + check=True, + text=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + ) - def cleanup_after_optimize(self, last_working_path: Path) -> tuple[int, int]: + if input_path_tmp: + input_path.unlink(missing_ok=True) + + if output_path_tmp: + with output_path.open("rb") as output_tmp_file: + output_buffer = BytesIO(output_tmp_file.read()) + output_path.unlink(missing_ok=True) + else: + self.working_path = output_path + output_buffer = output_path.open("rb") + return output_buffer + + def _cleanup_filesystem(self, final_data_buffer: BinaryIO) -> None: + """Write file to filesystem and clean up.""" + if not self.final_path: + reason = "This should not happen. no buffer and no final path." + raise ValueError(reason) + + if isinstance(final_data_buffer, BytesIO): + with self.final_path.open("wb") as final_file, final_data_buffer: + final_data_buffer.seek(0) + final_file.write(final_data_buffer.read()) + else: + final_data_buffer.close() + self.working_path.replace(self.final_path) + + ########### + # CLEANUP # + ########### + # Remove original path if the file has + # a new name. But be careful of case sensitive fs. + compare_final_str = str(self.final_path) + compare_original_str = str(self.original_path) + if not self.path_info.is_case_sensitive: + compare_final_str = compare_final_str.lower() + compare_original_str = compare_original_str.lower() + if compare_final_str != compare_original_str: + self.original_path.unlink(missing_ok=True) + + ############################### + # RESTORE STATS TO FINAL PATH # + ############################### + if self.config.preserve: + stat = self.path_info.stat() + if stat and stat is not True: + os.chown(self.final_path, stat.st_uid, stat.st_gid) + self.final_path.chmod(stat.st_mode) + os.utime( + self.final_path, + ns=(stat.st_atime_ns, stat.st_mtime_ns), + ) + + def get_buffer_len(self, buffer: BinaryIO) -> int: + """Return buffer size.""" + if isinstance(buffer, BufferedReader): + size = self.working_path.stat().st_size + elif isinstance(buffer, BytesIO): + size = buffer.getbuffer().nbytes + else: + reason = f"Unknown type for input_buffer: {type(buffer)}" + raise TypeError(reason) + return size + + def _cleanup_after_optimize_save_new(self, final_data_buffer: BinaryIO) -> bytes: + """Save new data.""" + return_data = b"" + if ( + isinstance(final_data_buffer, BytesIO) + or self.path_info.is_container_child() + ): + # only return the data in the report for containers. + final_data_buffer.seek(0) + return_data = final_data_buffer.read() + if self.path_info.path: + self._cleanup_filesystem(final_data_buffer) + return return_data + + def _cleanup_after_optimize(self, final_data_buffer: BinaryIO) -> ReportStats: """Replace old file with better one or discard new wasteful file.""" - bytes_in = 0 - bytes_out = 0 try: - bytes_in = self.original_path.stat().st_size - bytes_out = last_working_path.stat().st_size + bytes_in = self.path_info.bytes_in() + bytes_out = self.get_buffer_len(final_data_buffer) if not self.config.test and ( (bytes_out > 0) and ((bytes_out < bytes_in) or self.config.bigger) ): - last_working_path.replace(self.final_path) - - # Add original path to working_paths if the file has - # a new name. But be careful of case sensitive fs. - compare_final_str = str(self.final_path) - compare_original_str = str(self.original_path) - if not self.is_case_sensitive: - compare_final_str = compare_final_str.lower() - compare_original_str = compare_original_str.lower() - if compare_final_str != compare_original_str: - self.working_paths.add(self.original_path) + return_data = self._cleanup_after_optimize_save_new(final_data_buffer) else: - self.working_paths.add(last_working_path) - bytes_out = bytes_in - if self.final_path in self.working_paths: - self.working_paths.remove(self.final_path) - for working_path in self.working_paths: - working_path.unlink(missing_ok=True) - except OSError as exc: - cprint(f"ERROR: cleanup_after_optimize: {exc}", "red") + return_data = b"" + final_data_buffer.close() + if ( + self.working_path + and self.working_path != self.final_path + and isinstance(final_data_buffer, BufferedReader) + ): + self.working_path.unlink(missing_ok=True) + return ReportStats( + self.final_path, + path_info=self.path_info, + config=self.config, + bytes_in=bytes_in, + bytes_out=bytes_out, + data=return_data, + ) + except Exception as exc: + cprint(f"ERROR: cleanup_after_optimize: {self.final_path} {exc}", "red") raise - return (bytes_in, bytes_out) + @abstractmethod + def optimize(self) -> BinaryIO: + """Implement by subclasses.""" def error(self, exc: Exception) -> ReportStats: """Return an error result.""" - info = ReportInfo(self.original_path, self.convert, self.config.test, exc=exc) - return ReportStats(info) + return ReportStats(self.original_path, exc=exc) + + def optimize_wrapper(self) -> ReportStats: + """Wrap subclass optimize.""" + try: + buffer = self.optimize() + report_stats = self._cleanup_after_optimize(buffer) + except Exception as exc: + report_stats = self.error(exc) + if self.config.verbose: + report_stats.report() + return report_stats diff --git a/picopt/handlers/image.py b/picopt/handlers/image.py index 5f34340..dd21aea 100644 --- a/picopt/handlers/image.py +++ b/picopt/handlers/image.py @@ -1,90 +1,84 @@ """FileFormat Superclass.""" from abc import ABCMeta -from pathlib import Path +from collections.abc import Mapping +from io import BufferedReader, BytesIO from types import MappingProxyType -from typing import Any +from typing import Any, BinaryIO from PIL import Image -from PIL.BmpImagePlugin import BmpImageFile from PIL.PngImagePlugin import PngImageFile -from PIL.PpmImagePlugin import PpmImageFile -from PIL.TiffImagePlugin import TiffImageFile +from termcolor import cprint -from picopt.data import ReportInfo -from picopt.handlers.handler import FileFormat, Handler -from picopt.stats import ReportStats - -PPM_FILE_FORMAT = FileFormat(PpmImageFile.format, True, False) -BPM_FILE_FORMAT = FileFormat(BmpImageFile.format, True, False) -CONVERTABLE_FILE_FORMATS = {BPM_FILE_FORMAT, PPM_FILE_FORMAT} -CONVERTABLE_FORMAT_STRS = { - img_format.format_str for img_format in CONVERTABLE_FILE_FORMATS -} -PNG_ANIMATED_FILE_FORMAT = FileFormat(PngImageFile.format, True, True) -TIFF_FORMAT_STR = TiffImageFile.format -TIFF_FILE_FORMAT = FileFormat(TIFF_FORMAT_STR, True, False) -TIFF_ANIMATED_FILE_FORMAT = FileFormat(TIFF_FORMAT_STR, True, True) -_NATIVE_ONLY_FILE_FORMATS = { - PNG_ANIMATED_FILE_FORMAT, - TIFF_ANIMATED_FILE_FORMAT, - TIFF_FILE_FORMAT, -} +from picopt.handlers.handler import Handler class ImageHandler(Handler, metaclass=ABCMeta): """Image Handler superclass.""" - PIL2_ARGS: MappingProxyType[str, Any] = MappingProxyType({}) - PREFERRED_PROGRAM: str = "unimplemented" + PIL2_KWARGS: MappingProxyType[str, Any] = MappingProxyType({}) + PIL2PNG_KWARGS: MappingProxyType[str, Any] = MappingProxyType({"compress_level": 0}) + EMPTY_EXEC_ARGS: tuple[str, tuple[str, ...]] = ("", ()) - def _optimize_with_progs(self) -> ReportStats: + def optimize(self) -> BinaryIO: """Use the correct optimizing functions in sequence. And report back statistics. """ - path = self.original_path - for func in self.PROGRAMS: - if path != self.original_path: - self.working_paths.add(path) - new_path = self.get_working_path(func) - path = getattr(self, func)(path, new_path) - if self.BEST_ONLY: - break + stages = self.config.computed.handler_stages.get(self.__class__, {}) + if not stages: + cprint( + f"Tried to execute handler {self.__class__.__name__} with no available stages.", + "yellow", + ) + raise ValueError - bytes_count = self.cleanup_after_optimize(path) - info = ReportInfo( - self.final_path, - self.convert, - self.config.test, - bytes_count[0], - bytes_count[1], - ) - return ReportStats(info) + image_buffer: BinaryIO = self.path_info.fp_or_buffer() + + for func, exec_args in stages.items(): + new_image_buffer: BinaryIO = getattr(self, func)(exec_args, image_buffer) + if image_buffer != new_image_buffer: + image_buffer.close() + image_buffer = new_image_buffer - def optimize_image(self) -> ReportStats: - """Optimize a given image from a filename.""" - try: - report_stats = self._optimize_with_progs() - if self.config.verbose: - report_stats.report() - except Exception as exc: - report_stats = self.error(exc) - return report_stats + return image_buffer - def pil2native(self, old_path: Path, new_path: Path) -> Path: + def pil2native( + self, + _exec_args: tuple[str, tuple[str, ...]], + input_buffer: BytesIO | BufferedReader, + format_str: None | str = None, + opts: None | Mapping[str, Any] = None, + ) -> BytesIO | BufferedReader: """Use PIL to save the image.""" - if ( - self.input_file_format not in _NATIVE_ONLY_FILE_FORMATS - and self.PREFERRED_PROGRAM in self.config.computed.available_programs - ): - return old_path - with Image.open(old_path) as image: + if self.input_file_format in self._input_file_formats: + return input_buffer + if format_str is None: + format_str = self.OUTPUT_FORMAT_STR + if opts is None: + opts = self.PIL2_KWARGS + + info = self.prepare_info(format_str) + + output_buffer = BytesIO() + with Image.open(input_buffer) as image: image.save( - new_path, - self.OUTPUT_FORMAT_STR, - exif=self.metadata.exif, - icc_profile=self.metadata.icc_profile, - **self.PIL2_ARGS, + output_buffer, + format_str, + save_all=True, + **opts, + **info, ) image.close() # for animated images - return new_path + self.input_file_format = self.OUTPUT_FILE_FORMAT + return output_buffer + + def pil2png( + self, _exec_args: tuple[str, ...], input_buffer: BytesIO | BufferedReader + ) -> BytesIO | BufferedReader: + """Internally convert unhandled formats to uncompressed png for cwebp.""" + return self.pil2native( + self.EMPTY_EXEC_ARGS, + input_buffer, + format_str=PngImageFile.format, + opts=self.PIL2PNG_KWARGS, + ) diff --git a/picopt/handlers/image_animated.py b/picopt/handlers/image_animated.py new file mode 100644 index 0000000..d9b6515 --- /dev/null +++ b/picopt/handlers/image_animated.py @@ -0,0 +1,112 @@ +"""Animated images are treated like containers.""" +from abc import ABC +from collections.abc import Generator +from io import BytesIO +from types import MappingProxyType +from typing import Any + +from PIL import Image, ImageSequence +from PIL.PngImagePlugin import PngImageFile +from termcolor import cprint + +from picopt.formats import FileFormat +from picopt.handlers.container import ContainerHandler +from picopt.path import PathInfo + +ANIMATED_INFO_KEYS = ("bbox", "blend", "disposal", "duration") + + +class ImageAnimated(ContainerHandler, ABC): + """Animated image container.""" + + PROGRAMS = (("pil2native",),) + PIL2_FRAME_KWARGS = MappingProxyType( + {"format": PngImageFile.format, "compress_level": 0} + ) + PIL2_KWARGS: MappingProxyType[str, Any] = MappingProxyType({}) + + @classmethod + def identify_format(cls, path_info: PathInfo) -> FileFormat | None: # noqa: ARG003 + """Return the format if this handler can handle this path.""" + return cls.OUTPUT_FILE_FORMAT + + def unpack_into(self) -> Generator[PathInfo, None, None]: + """Unpack webp into temp dir.""" + frame_index = 1 + frame_info = {} + with Image.open(self.original_path) as image: + for frame in ImageSequence.Iterator(image): + # Save the frame as quickly as possible with the correct + # lossless format. Real optimization happens later with + # the specific handler. + # XXX It would be better to do what i do for mpo and read + # the bytes directly because this shows bad numbers for + # compressing uncompressed frames. But Pillow doesn't have + # raw access to frames. + with BytesIO() as frame_buffer: + frame.save( + frame_buffer, + **self.PIL2_FRAME_KWARGS, + ) + for key in ANIMATED_INFO_KEYS: + value = frame.info.get(key) + if value is not None: + if key not in frame_info: + frame_info[key] = [] + frame_info[key].append(value) + frame_buffer.seek(0) + frame_path_info = PathInfo( + self.path_info.top_path, + self.path_info.mtime(), + self.path_info.convert, + self.path_info.is_case_sensitive, + frame=frame_index, + data=frame_buffer.read(), + container_paths=self.get_container_paths(), + ) + if self.config.verbose: + cprint(".", end="") + yield frame_path_info + frame_index += 1 + image.close() + for key in tuple(frame_info): + value = frame_info[key] + if value is not None: + frame_info[key] = tuple(value) + self.frame_info = frame_info + + def pack_into(self) -> BytesIO: + """Remux the optimized frames into an animated webp.""" + sorted_pairs = sorted( + self._optimized_contents.items(), + key=lambda pair: 0 if pair[0].frame is None else pair[0].frame, + ) + head_image_data = sorted_pairs.pop()[1] + + # Collect frames as images. + total_len = 0 + append_images = [] + while sorted_pairs: + _, frame_data = sorted_pairs.pop() + total_len += len(frame_data) + frame = Image.open(BytesIO(frame_data)) + append_images.append(frame) + if self.config.verbose: + cprint(".", end="") + + # Prepare info + info = dict(self.prepare_info(self.OUTPUT_FORMAT_STR)) + info.update(self.frame_info) + + # Save Frames + output_buffer = BytesIO() + with Image.open(BytesIO(head_image_data)) as image: + image.save( + output_buffer, + self.OUTPUT_FORMAT_STR, + save_all=True, + append_images=append_images, + **self.PIL2_KWARGS, + **info, + ) + return output_buffer diff --git a/picopt/handlers/jpeg.py b/picopt/handlers/jpeg.py index 2d91186..fee7662 100644 --- a/picopt/handlers/jpeg.py +++ b/picopt/handlers/jpeg.py @@ -1,12 +1,17 @@ """JPEG format.""" -from pathlib import Path -from types import MappingProxyType -from typing import Optional +from io import BytesIO +from typing import BinaryIO +import piexif from PIL.JpegImagePlugin import JpegImageFile +from termcolor import cprint -from picopt.handlers.handler import FileFormat +from picopt.formats import MPO_FILE_FORMAT, FileFormat from picopt.handlers.image import ImageHandler +from picopt.pillow.jpeg_xmp import set_jpeg_xmp + +MPO_METADATA: int = 45058 +MPO_TYPE_PRIMARY: str = "Baseline MP Primary Image" class Jpeg(ImageHandler): @@ -14,18 +19,10 @@ class Jpeg(ImageHandler): OUTPUT_FORMAT_STR = JpegImageFile.format OUTPUT_FILE_FORMAT = FileFormat(OUTPUT_FORMAT_STR, False, False) - PROGRAMS: MappingProxyType[str, Optional[str]] = ImageHandler.init_programs( - ("mozjpeg", "jpegtran") - ) - _ARGS_PREFIX = ("-optimize", "-progressive", "-copy") - _MOZJPEG_ARGS_PREFIX: tuple[Optional[str], ...] = ( - PROGRAMS["mozjpeg"], - *_ARGS_PREFIX, - ) - _JPEGTRAN_ARGS_PREFIX: tuple[Optional[str], ...] = ( - PROGRAMS["jpegtran"], - *_ARGS_PREFIX, - ) + INPUT_FILE_FORMATS = frozenset({OUTPUT_FILE_FORMAT}) + PROGRAMS = (("mozjpeg", "jpegtran", "pil2jpeg"),) + _JPEGTRAN_ARGS_PREFIX = ("-optimize", "-progressive") + # PIL Cannot save jpegs losslessly @classmethod def get_default_suffix(cls) -> str: @@ -37,26 +34,78 @@ def get_suffixes(cls, default_suffix: str) -> frozenset: """Initialize suffix instance variables.""" return frozenset((default_suffix, "." + cls.OUTPUT_FORMAT_STR.lower())) - def _jpegtran( - self, args: tuple[Optional[str], ...], old_path: Path, new_path: Path - ) -> Path: + def _jpegtran(self, exec_args: tuple[str, ...], input_buffer: BinaryIO) -> BytesIO: """Run the jpegtran type program.""" - args_l = list(args) - if not bin: - return old_path - if self.config.keep_metadata: - args_l += ["all"] - else: - args_l += ["none"] - args_l += ["-outfile", str(new_path), str(old_path)] - args_t = tuple(args_l) - self.run_ext(args_t) - return new_path + copy_arg = "all" if self.config.keep_metadata else "none" + args = (*exec_args, *self._JPEGTRAN_ARGS_PREFIX, "-copy", copy_arg) + return self.run_ext(args, input_buffer) - def mozjpeg(self, old_path: Path, new_path: Path) -> Path: + def mozjpeg(self, exec_args: tuple[str, ...], input_buffer: BinaryIO) -> BytesIO: """Create argument list for mozjpeg.""" - return self._jpegtran(self._MOZJPEG_ARGS_PREFIX, old_path, new_path) + return self._jpegtran(exec_args, input_buffer) - def jpegtran(self, old_path: Path, new_path: Path) -> Path: + def jpegtran(self, exec_args: tuple[str, ...], input_buffer: BinaryIO) -> BytesIO: """Create argument list for jpegtran.""" - return self._jpegtran(self._JPEGTRAN_ARGS_PREFIX, old_path, new_path) + return self._jpegtran(exec_args, input_buffer) + + def _mpo2jpeg_get_frame(self, input_buffer: BinaryIO) -> bytes: + """Get Primary JPEG Offsets.""" + mpo_info = self.info.pop("mpinfo", {}) + mpo_metadata_list = mpo_info.get(MPO_METADATA, ()) + + for mpo_metadata in mpo_metadata_list: + attr = mpo_metadata.get("Attribute", {}) + mp_type = attr.get("MPType") + if mp_type == MPO_TYPE_PRIMARY: + offset = mpo_metadata.get("DataOffset", -1) + size = mpo_metadata.get("Size", -1) + break + else: + offset = size = -1 + + if -1 in (offset, size): + reason = f"{self.original_path} could not find {MPO_TYPE_PRIMARY} in MPO" + raise ValueError(reason) + input_buffer.seek(offset) + return input_buffer.read(size) + + def _mpo2jpeg_copy_exif(self, jpeg_data: bytes) -> bytes: + """Copy MPO EXIF into JPEG.""" + output_buffer = BytesIO() + if exif := self.info.get("exif"): + piexif.insert(exif, jpeg_data, output_buffer) + return output_buffer.read() + return jpeg_data + + def _mpo2jpeg_copy_xmp(self, jpeg_data: bytes) -> bytes: + """Copy MPO XMP into JPEG manually.""" + if xmp := self.info.get("xmp"): + jpeg_data = set_jpeg_xmp(jpeg_data, xmp) + return jpeg_data + + def pil2jpeg( + self, + exec_args: tuple[str, ...], # noqa: ARG002 + input_buffer: BinaryIO, + ) -> BinaryIO: + """Convert MPOs with primary images to jpeg.""" + # XXX Much work because PIL doesn't have direct unprocessed file bytes access. + if self.input_file_format != MPO_FILE_FORMAT: + return input_buffer + + jpeg_data = self._mpo2jpeg_get_frame(input_buffer) + try: + jpeg_data = self._mpo2jpeg_copy_exif(jpeg_data) + except Exception as exc: + cprint( + f"WARNING: could not copy EXIF data for {self.path_info.full_name()}: {exc}" + ) + try: + jpeg_data = self._mpo2jpeg_copy_xmp(jpeg_data) + except Exception as exc: + cprint( + f"WARNING: could not copy XMP data for {self.path_info.full_name()}: {exc}" + ) + + self.input_file_format = self.OUTPUT_FILE_FORMAT + return BytesIO(jpeg_data) diff --git a/picopt/handlers/non_pil.py b/picopt/handlers/non_pil.py new file mode 100644 index 0000000..83f3172 --- /dev/null +++ b/picopt/handlers/non_pil.py @@ -0,0 +1,20 @@ +"""Methods for files that that can't be identified with PIL.""" + +from picopt.formats import FileFormat +from picopt.handlers.handler import Handler +from picopt.path import PathInfo + + +class NonPILIdentifier(Handler): + """Methods for files that that can't be identified with PIL.""" + + @classmethod + def identify_format( + cls, + path_info: PathInfo, + ) -> FileFormat | None: + """Return the format if this handler can handle this path.""" + suffix = path_info.suffix().lower() + if suffix == cls.get_default_suffix(): + return cls.OUTPUT_FILE_FORMAT + return None diff --git a/picopt/handlers/png.py b/picopt/handlers/png.py index 79f06ee..ad4b910 100644 --- a/picopt/handlers/png.py +++ b/picopt/handlers/png.py @@ -1,12 +1,14 @@ """PNG format.""" -from pathlib import Path +from io import BufferedReader, BytesIO from types import MappingProxyType -from typing import Optional +from typing import Any +import oxipng +from PIL.GifImagePlugin import GifImageFile from PIL.PngImagePlugin import PngImageFile from termcolor import cprint -from picopt.handlers.handler import FileFormat +from picopt.formats import CONVERTIBLE_FORMAT_STRS, FileFormat from picopt.handlers.image import ImageHandler from picopt.pillow.png_bit_depth import png_bit_depth @@ -14,47 +16,56 @@ class Png(ImageHandler): """PNG format class.""" - BEST_ONLY: bool = False OUTPUT_FORMAT_STR = PngImageFile.format OUTPUT_FILE_FORMAT = FileFormat(OUTPUT_FORMAT_STR, True, False) - PIL2_ARGS: MappingProxyType[str, bool] = MappingProxyType({"optimize": True}) - PROGRAMS: MappingProxyType[str, Optional[str]] = ImageHandler.init_programs( - ("pil2png", "optipng", "pngout") + INPUT_FILE_FORMATS = frozenset({OUTPUT_FILE_FORMAT}) + CONVERT_FROM_FORMAT_STRS = frozenset( + CONVERTIBLE_FORMAT_STRS | {GifImageFile.format} ) - PREFERRED_PROGRAM: str = "optipng" - _OPTIPNG_ARGS: tuple[Optional[str], ...] = ( - PROGRAMS["optipng"], - "-o5", - "-fix", - "-force", + PROGRAMS = ( + ("pil2png",), + ("internal_oxipng",), + ("pngout",), ) - _PNGOUT_ARGS: tuple[Optional[str], ...] = (PROGRAMS["pngout"], "-force", "-y") - - def pil2png(self, old_path: Path, new_path: Path) -> Path: - """Pillow png optimization.""" - return self.pil2native(old_path, new_path) + PIL2_KWARGS = MappingProxyType({"optimize": True}) + _OXIPNG_KWARGS: MappingProxyType[str, Any] = MappingProxyType( + { + "level": 5, + "fix_errors": True, + "force": True, + "optimize_alpha": True, + "deflate": oxipng.Deflaters.zopfli(15), + } + ) + _PNGOUT_ARGS: tuple[str, ...] = ("-", "-", "-force", "-y", "-q") + _PNGOUT_DEPTH_MAX = 8 - def optipng(self, old_path: Path, new_path: Path) -> Path: - """Run the external program optipng on the file.""" - args_l = list(self._OPTIPNG_ARGS) + def internal_oxipng( + self, _exec_args: tuple[str, ...], input_buffer: BufferedReader | BytesIO + ) -> BytesIO: + """Run internal oxipng on the file.""" + opts = {**self._OXIPNG_KWARGS} if not self.config.keep_metadata: - args_l += ["-strip", "all"] - args_l += ["-out", str(new_path), str(old_path)] - self.run_ext(tuple(args_l)) - return new_path + opts["strip"] = oxipng.StripChunks.safe() + input_buffer.seek(0) + with input_buffer: + result = oxipng.optimize_from_memory(input_buffer.read(), **opts) + return BytesIO(result) - def pngout(self, old_path: Path, new_path: Path) -> Path: + def pngout( + self, + exec_args: tuple[str, ...], + input_buffer: BufferedReader | BytesIO, + ) -> BytesIO | BufferedReader: """Run the external program pngout on the file.""" - depth = png_bit_depth(old_path) - if depth in (16, None): + depth = png_bit_depth(input_buffer) + if not depth or depth > self._PNGOUT_DEPTH_MAX or depth < 1: cprint( - f"Skipped pngout for {depth} bit PNG: {old_path}", + f"Skipped pngout for {depth} bit PNG: {self.original_path}", "white", attrs=["dark"], ) - result = old_path - else: - args = (*self._PNGOUT_ARGS, str(old_path), str(new_path)) - self.run_ext(args) - result = new_path - return result + return input_buffer + opts = ("-k1",) if self.config.keep_metadata else ("-k0",) + args = (*exec_args, *self._PNGOUT_ARGS, *opts) + return self.run_ext(args, input_buffer) diff --git a/picopt/handlers/png_animated.py b/picopt/handlers/png_animated.py new file mode 100644 index 0000000..cac5877 --- /dev/null +++ b/picopt/handlers/png_animated.py @@ -0,0 +1,24 @@ +"""WebP Animated images are treated like containers.""" +from picopt.formats import ( + CONVERTIBLE_ANIMATED_FILE_FORMATS, + CONVERTIBLE_ANIMATED_FORMAT_STRS, + FileFormat, +) +from picopt.handlers.gif import Gif, GifAnimated +from picopt.handlers.image_animated import ImageAnimated +from picopt.handlers.png import Png + + +class PngAnimated(ImageAnimated): + """Animated Png container.""" + + OUTPUT_FORMAT_STR: str = Png.OUTPUT_FORMAT_STR + OUTPUT_FILE_FORMAT = FileFormat(OUTPUT_FORMAT_STR, True, True) + INPUT_FILE_FORMATS = frozenset( + {OUTPUT_FILE_FORMAT, GifAnimated.OUTPUT_FILE_FORMAT} + | CONVERTIBLE_ANIMATED_FILE_FORMATS, + ) + CONVERT_FROM_FORMAT_STRS = frozenset( + CONVERTIBLE_ANIMATED_FORMAT_STRS | {Gif.OUTPUT_FORMAT_STR} + ) + PIL2_KWARGS = Png.PIL2_KWARGS diff --git a/picopt/handlers/svg.py b/picopt/handlers/svg.py new file mode 100644 index 0000000..f2331c7 --- /dev/null +++ b/picopt/handlers/svg.py @@ -0,0 +1,31 @@ +"""WebP format.""" +from io import BytesIO +from typing import BinaryIO + +from picopt.formats import SVG_FORMAT_STR, FileFormat +from picopt.handlers.image import ImageHandler +from picopt.handlers.non_pil import NonPILIdentifier + + +class Svg(ImageHandler, NonPILIdentifier): + """SVG format class.""" + + OUTPUT_FORMAT_STR = SVG_FORMAT_STR + OUTPUT_FILE_FORMAT = FileFormat(OUTPUT_FORMAT_STR, True) + INPUT_FORMAT_SUFFIX = "." + OUTPUT_FORMAT_STR.lower() + INPUT_FILE_FORMATS = frozenset({OUTPUT_FILE_FORMAT}) + PROGRAMS = (("svgo", "npx_svgo"),) + _SVGO_ARGS = ("--multipass", "--output", "-", "--input", "-") + + def _svgo(self, exec_args: tuple[str, ...], input_buffer: BinaryIO) -> BytesIO: + """Optimize using svgo.""" + args = (*exec_args, *self._SVGO_ARGS) + return self.run_ext(args, input_buffer) + + def svgo(self, exec_args: tuple[str, ...], input_buffer: BinaryIO) -> BytesIO: + """Svgo executable.""" + return self._svgo(exec_args, input_buffer) + + def npx_svgo(self, exec_args: tuple[str, ...], input_buffer: BinaryIO) -> BytesIO: + """Npx installed svgo.""" + return self._svgo(exec_args, input_buffer) diff --git a/picopt/handlers/webp.py b/picopt/handlers/webp.py index 1688fc9..cf3ae4c 100644 --- a/picopt/handlers/webp.py +++ b/picopt/handlers/webp.py @@ -1,154 +1,121 @@ """WebP format.""" from abc import ABC -from pathlib import Path +from collections.abc import Mapping +from io import BytesIO from types import MappingProxyType -from typing import Any, Optional +from typing import TYPE_CHECKING, Any, BinaryIO -from PIL import Image +from confuse import AttrDict from PIL.WebPImagePlugin import WebPImageFile -from picopt.handlers.handler import FileFormat -from picopt.handlers.image import ( - CONVERTABLE_FILE_FORMATS, - TIFF_FILE_FORMAT, - ImageHandler, -) +from picopt.formats import MODERN_CWEBP_FORMATS, FileFormat +from picopt.handlers.image import ImageHandler from picopt.handlers.png import Png +from picopt.path import PathInfo + +if TYPE_CHECKING: + from pathlib import Path class WebPBase(ImageHandler, ABC): """Base for handlers that use WebP utility commands.""" - PIL2WEBP_KWARGS: MappingProxyType[str, Any] = MappingProxyType( - {"lossless": True, "quality": 100, "method": 6} - ) - PIL2_ARGS: MappingProxyType[str, Any] = PIL2WEBP_KWARGS - - def get_metadata_args(self) -> list[str]: - """Get webp utility metadata args.""" - args = ["-metadata"] - if self.config.keep_metadata: - args += ["all"] - else: - args += ["none"] - return args - - def pil2webp(self, old_path: Path, new_path: Path) -> Path: - """Pillow webp optimization.""" - return self.pil2native(old_path, new_path) - - -class WebP(WebPBase, ABC): - """WebP format class.""" - + PIL2_KWARGS = MappingProxyType({"quality": 100, "method": 6}) OUTPUT_FORMAT_STR = WebPImageFile.format - PROGRAMS: MappingProxyType[str, Optional[str]] = WebPBase.init_programs(("cwebp",)) + PROGRAMS = (("cwebp", "pil2native"),) # https://developers.google.com/speed/webp/docs/cwebp - ARGS_PREFIX = ( - PROGRAMS["cwebp"], - "-near_lossless", - "0" "-q", + CWEBP_ARGS_PREFIX = ( + "-mt", + "-q", "100", "-m", "6", - "-mt", # advanced "-sharp_yuv", + # logging, + "-quiet", # additional "-alpha_filter", "best", ) - def cwebp(self, old_path: Path, new_path: Path) -> Path: + def cwebp( + self, + exec_args: tuple[str, ...], + input_buffer: BinaryIO, + opts: tuple[str, ...] | None = None, + ) -> BinaryIO: """Optimize using cwebp.""" - args = ( - *self.ARGS_PREFIX, - *self.get_metadata_args(), - *[str(old_path), "-o", str(new_path)], - ) - self.run_ext(args) - return new_path - - -class WebPLossless(WebP): - """Handle lossless webp images and images that convert to lossless webp.""" - - BEST_ONLY: bool = False - OUTPUT_FILE_FORMAT = FileFormat(WebP.OUTPUT_FORMAT_STR, True, False) - PREFERRED_PROGRAM: str = "cwebp" - PROGRAMS: MappingProxyType[str, Optional[str]] = MappingProxyType( - { - "pil2png": None, - **WebP.PROGRAMS, - "pil2webp": None, - } - ) - ARGS_PREFIX = (*WebP.ARGS_PREFIX, "-lossless") - _PIL2PNG_FILE_FORMATS = CONVERTABLE_FILE_FORMATS | {TIFF_FILE_FORMAT} - - def pil2png(self, old_path: Path, new_path: Path) -> Path: - """Internally convert unhandled formats to uncompressed png for cwebp.""" - if ( - self.input_file_format in self._PIL2PNG_FILE_FORMATS - and self.PREFERRED_PROGRAM in self.config.computed.available_programs - ): - new_path = new_path.with_suffix(Png.get_default_suffix()) - with Image.open(old_path) as image: - image.save( - new_path, - Png.OUTPUT_FORMAT_STR, - compress_level=0, - exif=self.metadata.exif, - icc_profile=self.metadata.icc_profile, - ) - image.close() - self.input_file_format = Png.OUTPUT_FILE_FORMAT + args = [*exec_args] + if opts: + args += [*opts] + args += [*self.CWEBP_ARGS_PREFIX] + args += ["-metadata"] + if self.config.keep_metadata: + args += ["all"] else: - new_path = old_path - return new_path + args += ["none"] + input_path_tmp = isinstance(input_buffer, BytesIO) + input_path: Path | None = ( + self.get_working_path("cwebp-input") + if input_path_tmp + else self.path_info.path + ) + if not input_path: + reason = "No input path for cwebp" + raise ValueError(reason) + + output_path = self.get_working_path("cwebp-output") + output_path_tmp = bool(self.path_info.path) + args += [str(input_path), "-o", str(output_path)] + # XXX if python cwebp gains enough options to beat this or + # or cwebp gains stdin or stdout powers we can not use this + return self.run_ext_fs( + tuple(args), + input_buffer, + input_path, + output_path, + input_path_tmp, + output_path_tmp, + ) -class Gif2WebP(WebPBase): - """Animated WebP format class. - There are no easy animated WebP optimization tools. So this only - converts animated gifs. - """ +class WebPLossless(WebPBase): + """Handle lossless webp images and images that convert to lossless webp.""" - OUTPUT_FORMAT_STR = WebP.OUTPUT_FORMAT_STR - OUTPUT_FILE_FORMAT = FileFormat(WebP.OUTPUT_FORMAT_STR, True, True) - PIL2WEBP_KWARGS: MappingProxyType[str, Any] = MappingProxyType( - { - **WebPLossless.PIL2WEBP_KWARGS, - "minimize_size": True, - } + OUTPUT_FILE_FORMAT = FileFormat(WebPBase.OUTPUT_FORMAT_STR, True, False) + INPUT_FILE_FORMATS = frozenset({OUTPUT_FILE_FORMAT, Png.OUTPUT_FILE_FORMAT}) + CONVERT_FROM_FORMAT_STRS = frozenset( + Png.CONVERT_FROM_FORMAT_STRS | {Png.OUTPUT_FORMAT_STR} ) - PREFERRED_PROGRAM = "gif2webp" - PROGRAMS: MappingProxyType[str, Optional[str]] = WebPBase.init_programs( - ("gif2webp", "pil2webp") + CWEBP_ARGS_PREFIX = ( + # https://groups.google.com/a/webmproject.org/g/webp-discuss/c/0GmxDmlexek + "-lossless", + *WebPBase.CWEBP_ARGS_PREFIX, ) - _ARGS_PREFIX = ( - PROGRAMS["gif2webp"], - "-mixed", - "-min_size", - "-q", - "100", - "-m", - "6", - "-mt", - ) - - @classmethod - def native_input_file_formats(cls): - """No native formats.""" - return frozenset() - - def gif2webp(self, old_path: Path, new_path: Path) -> Path: - """Convert animated gif to animated webp.""" - args = ( - *self._ARGS_PREFIX, - *self.get_metadata_args(), - *[str(old_path), "-o", str(new_path)], - ) - self.run_ext(args) - return new_path + PIL2_KWARGS = MappingProxyType({**WebPBase.PIL2_KWARGS, "lossless": True}) + PROGRAMS = (("pil2png",), ("cwebp", "pil2native")) + NEAR_LOSSLESS_OPTS: tuple[str, ...] = ("-near_lossless", "0") + + def __init__( + self, + config: AttrDict, + path_info: PathInfo, + input_file_format: FileFormat, + info: Mapping[str, Any], + ): + """Initialize extra input formats.""" + super().__init__(config, path_info, input_file_format, info) + if config.computed.is_modern_cwebp: + self._input_file_formats |= MODERN_CWEBP_FORMATS + + def cwebp( + self, + exec_args: tuple[str, ...], + input_buffer: BinaryIO, + opts: tuple[str, ...] | None = None, + ) -> BinaryIO: + """Optimize using cwebp and with runtime optional arguments.""" + opts = self.NEAR_LOSSLESS_OPTS if self.config.near_lossless else None + return super().cwebp(exec_args, input_buffer, opts=opts) diff --git a/picopt/handlers/webp_animated.py b/picopt/handlers/webp_animated.py index a36605d..9e7c769 100644 --- a/picopt/handlers/webp_animated.py +++ b/picopt/handlers/webp_animated.py @@ -1,118 +1,33 @@ """WebP Animated images are treated like containers.""" -from pathlib import Path -from typing import Optional +from types import MappingProxyType -from PIL import Image, ImageSequence +from PIL.WebPImagePlugin import WebPImageFile -from picopt.handlers.container import ContainerHandler -from picopt.handlers.handler import FileFormat -from picopt.handlers.image import PNG_ANIMATED_FILE_FORMAT, TIFF_ANIMATED_FILE_FORMAT -from picopt.handlers.webp import WebP +from picopt.formats import FileFormat +from picopt.handlers.image_animated import ImageAnimated +from picopt.handlers.png_animated import PngAnimated +from picopt.handlers.webp import WebPBase -class WebPAnimatedBase(ContainerHandler): +class WebPAnimatedBase(ImageAnimated): """Animated WebP container.""" - OUTPUT_FORMAT_STR: str = WebP.OUTPUT_FORMAT_STR - PROGRAMS = ContainerHandler.init_programs(("webpmux", "img2webp")) - _OPEN_WITH_PIL_FILE_FORMATS = frozenset( - {PNG_ANIMATED_FILE_FORMAT, TIFF_ANIMATED_FILE_FORMAT} - ) - _IMG2WEBP_ARGS_PREFIX = (PROGRAMS["img2webp"], "-min_size") - _WEBPMUX_ARGS_PREFIX = (PROGRAMS["webpmux"], "-get", "frame") - _LOSSLESS = True - - @classmethod - def identify_format(cls, path: Path) -> Optional[FileFormat]: # noqa: ARG003 - """Return the format if this handler can handle this path.""" - return cls.OUTPUT_FILE_FORMAT - - def _get_frame_path(self, frame_index: int) -> Path: - """Return a frame path for an index.""" - return self.tmp_container_dir / f"frame-{frame_index:08d}.webp" - - def unpack_into(self) -> None: - """Unpack webp into temp dir.""" - if self.input_file_format in self._OPEN_WITH_PIL_FILE_FORMATS: - with Image.open(self.original_path) as image: - frame_index = 0 - for frame in ImageSequence.Iterator(image): - frame_path = self._get_frame_path(frame_index) - frame.save( - frame_path, - self.OUTPUT_FORMAT_STR, - lossless=self._LOSSLESS, - quality=100, - method=0, - ) - frame_index += 1 - image.close() - else: - for frame_index in range(self.metadata.n_frames): - frame_path = self._get_frame_path(frame_index) - args = [ - *self._WEBPMUX_ARGS_PREFIX, - str(frame_index), - str(self.original_path), - "-o", - str(frame_path), - ] - self.run_ext(tuple(args)) - - def _prepare_metadata(self, data: Optional[bytes], working_path: Path, md_arg: str): - """Prepare a metadata file and args for webpmux.""" - if not data: - return [] - md_path = working_path.with_suffix("." + md_arg) - with md_path.open("wb") as md_file: - md_file.write(data) - self.working_paths.add(md_path) - return ["-set", md_arg, str(md_path)] - - def _set_metadata(self, working_path): - """Set the exif data on the rebuilt image.""" - if not self.metadata.exif or self.metadata.icc_profile: - return - - args = ["webpmux"] - # dump exif - if self.metadata.exif: - args += self._prepare_metadata(self.metadata.exif, working_path, "exif") - - if self.metadata.icc_profile: - args += self._prepare_metadata( - self.metadata.icc_profile.encode(), working_path, "icc" - ) - - # move working file - container_exif_working_path = self.get_working_path("exif") - working_path.replace(container_exif_working_path) - self.working_paths.add(container_exif_working_path) - - # run exif set - args += [ - str(container_exif_working_path), - "-o", - str(working_path), - ] - self.run_ext(tuple(args)) - - def pack_into(self, working_path: Path) -> None: - """Remux the optimized frames into an animated webp.""" - frames = sorted([str(path) for path in self.tmp_container_dir.iterdir()]) - args = [ - *self._IMG2WEBP_ARGS_PREFIX, - *frames, - "-o", - str(working_path), - ] - self.run_ext(tuple(args)) - if self.config.keep_metadata: - self._set_metadata(working_path) + OUTPUT_FORMAT_STR: str = WebPBase.OUTPUT_FORMAT_STR + PIL2_KWARGS = MappingProxyType({**WebPBase.PIL2_KWARGS, "minimize_size": True}) + PIL2_FRAME_KWARGS = MappingProxyType({"format": WebPImageFile.format, "method": 0}) class WebPAnimatedLossless(WebPAnimatedBase): """Animated Lossless WebP Handler.""" - _LOSSLESS = True - OUTPUT_FILE_FORMAT = FileFormat(WebPAnimatedBase.OUTPUT_FORMAT_STR, _LOSSLESS, True) + OUTPUT_FILE_FORMAT = FileFormat(WebPAnimatedBase.OUTPUT_FORMAT_STR, True, True) + INPUT_FILE_FORMATS = frozenset( + {*PngAnimated.INPUT_FILE_FORMATS, OUTPUT_FILE_FORMAT} + ) + CONVERT_FROM_FORMAT_STRS = frozenset( + {*PngAnimated.CONVERT_FROM_FORMAT_STRS, PngAnimated.OUTPUT_FORMAT_STR} + ) + PIL2_FRAME_KWARGS = MappingProxyType( + {**WebPAnimatedBase.PIL2_FRAME_KWARGS, "lossless": True, "quality": 0} + ) + PIL2_KWARGS = MappingProxyType({**WebPAnimatedBase.PIL2_KWARGS, "lossless": True}) diff --git a/picopt/handlers/zip.py b/picopt/handlers/zip.py index 4416e4d..14dce6d 100644 --- a/picopt/handlers/zip.py +++ b/picopt/handlers/zip.py @@ -1,37 +1,31 @@ """Handler for zip files.""" -import os -from pathlib import Path -from types import MappingProxyType -from typing import Optional -from zipfile import ZIP_DEFLATED, ZipFile, is_zipfile - -from PIL import Image, UnidentifiedImageError -from rarfile import RarFile, is_rarfile +from collections.abc import Generator +from io import BytesIO +from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile, ZipInfo, is_zipfile + +from rarfile import RarFile, RarInfo, is_rarfile from termcolor import cprint +from picopt.formats import FileFormat from picopt.handlers.container import ContainerHandler -from picopt.handlers.handler import FileFormat +from picopt.handlers.non_pil import NonPILIdentifier +from picopt.path import PathInfo -class Zip(ContainerHandler): +class Zip(NonPILIdentifier, ContainerHandler): """Ziplike container.""" OUTPUT_FORMAT_STR: str = "ZIP" OUTPUT_FILE_FORMAT: FileFormat = FileFormat(OUTPUT_FORMAT_STR) - PROGRAMS: MappingProxyType[str, Optional[str]] = MappingProxyType( - { - ContainerHandler.INTERNAL: None, - } - ) + INPUT_FILE_FORMATS = frozenset({OUTPUT_FILE_FORMAT}) + PROGRAMS = ((ContainerHandler.INTERNAL,),) @classmethod - def identify_format(cls, path: Path) -> Optional[FileFormat]: + def identify_format(cls, path_info: PathInfo) -> FileFormat | None: """Return the format if this handler can handle this path.""" - file_format = None - suffix = path.suffix.lower() - if is_zipfile(path) and suffix == cls.get_default_suffix(): - file_format = cls.OUTPUT_FILE_FORMAT - return file_format + if is_zipfile(path_info.path_or_buffer()): + return super().identify_format(path_info) + return None def _get_archive(self) -> ZipFile: """Use the zipfile builtin for this archive.""" @@ -42,96 +36,125 @@ def _get_archive(self) -> ZipFile: raise ValueError(msg) return archive - def _set_comment(self, comment: Optional[bytes]) -> None: + def _set_comment(self, comment: bytes | None) -> None: """Set the comment from the archive.""" if comment: self.comment = comment - def unpack_into(self) -> None: + @staticmethod + def to_zipinfo(archive_info: ZipInfo) -> ZipInfo: + """Convert archive info to zipinfo.""" + return archive_info + + def unpack_into(self) -> Generator[PathInfo, None, None]: """Uncompress archive.""" with self._get_archive() as archive: - archive.extractall(self.tmp_container_dir) self._set_comment(archive.comment) - - @staticmethod - def _is_image(path: Path) -> bool: - """Is a file an image.""" - result = False - try: - with Image.open(path, mode="r") as image: - if image.format: - result = True - image.close() - except UnidentifiedImageError: - pass - return result - - def pack_into(self, working_path: Path) -> None: + for archive_info in archive.infolist(): + zipinfo = self.to_zipinfo(archive_info) + if zipinfo.is_dir(): + continue + path_info = PathInfo( + self.path_info.top_path, + self.path_info.mtime(), + self.path_info.convert, + self.path_info.is_case_sensitive, + zipinfo=zipinfo, + data=archive.read(zipinfo.filename), + container_paths=self.get_container_paths(), + ) + yield path_info + + def pack_into(self) -> BytesIO: """Zip up the files in the tempdir into the new filename.""" + output_buffer = BytesIO() with ZipFile( - working_path, "w", compression=ZIP_DEFLATED, compresslevel=9 + output_buffer, "w", compression=ZIP_DEFLATED, compresslevel=9 ) as new_zf: - for root, _, filenames in os.walk(self.tmp_container_dir): - root_path = Path(root) - for fname in sorted(filenames): - if self.config.verbose: - cprint(".", end="") - full_path = root_path / fname - # Do not deflate images in zipfile. - # Picopte should have already achieved maximum - # compression over deflate. - compress_type = None if self._is_image(full_path) else ZIP_DEFLATED - archive_path = full_path.relative_to(self.tmp_container_dir) - new_zf.write(full_path, archive_path, compress_type) + for path_info in tuple(self._optimized_contents): + data = self._optimized_contents.pop(path_info) + zipinfo = path_info.zipinfo + if not zipinfo: + continue + if ( + path_info.container_filename + and path_info.container_filename != zipinfo.filename + ): + zipinfo.filename = path_info.container_filename + if ( + not self.config.keep_metadata + and zipinfo + and zipinfo.compress_type == ZIP_STORED + ): + zipinfo.compress_type = ZIP_DEFLATED + new_zf.writestr(zipinfo, data) + if self.config.verbose: + cprint(".", end="") if self.comment: new_zf.comment = self.comment + if self.config.verbose: + cprint(".", end="") + return output_buffer class Rar(Zip): """RAR Container.""" INPUT_FORMAT_STR: str = "RAR" - INPUT_FILE_FORMAT: FileFormat = FileFormat(INPUT_FORMAT_STR) INPUT_SUFFIX: str = "." + INPUT_FORMAT_STR.lower() - PROGRAMS: MappingProxyType[str, Optional[str]] = Zip.init_programs(("unrar",)) + INPUT_FILE_FORMAT = FileFormat(INPUT_FORMAT_STR) + INPUT_FILE_FORMATS = frozenset({INPUT_FILE_FORMAT}) + PROGRAMS = (("unrar",),) + + @staticmethod + def to_zipinfo(archive_info: RarInfo | ZipInfo) -> ZipInfo: + """Convert RarInfo to ZipInfo.""" + zipinfo_kwargs = {} + if archive_info.filename: + zipinfo_kwargs["filename"] = archive_info.filename + if archive_info.date_time: + zipinfo_kwargs["date_time"] = archive_info.date_time + return ZipInfo(**zipinfo_kwargs) @classmethod - def identify_format(cls, path: Path) -> Optional[FileFormat]: + def identify_format(cls, path_info: PathInfo) -> FileFormat | None: """Return the format if this handler can handle this path.""" file_format = None - suffix = path.suffix.lower() - if is_rarfile(path) and suffix == cls.INPUT_SUFFIX: + suffix = path_info.suffix().lower() + if is_rarfile(path_info.path_or_buffer()) and suffix == cls.INPUT_SUFFIX: file_format = cls.INPUT_FILE_FORMAT return file_format def _get_archive(self) -> RarFile: # type: ignore """Use the zipfile builtin for this archive.""" if is_rarfile(self.original_path): - archive = RarFile(self.original_path) + archive = RarFile(self.original_path, mode="r") else: msg = f"Unknown archive type: {self.original_path}" raise ValueError(msg) return archive - def _set_comment(self, comment: Optional[str]) -> None: # type: ignore + def _set_comment(self, comment: str | None) -> None: # type: ignore """Set the comment from the archive.""" if comment: self.comment = comment.encode() -class CBZ(Zip): +class Cbz(Zip): """CBZ Container.""" OUTPUT_FORMAT_STR: str = "CBZ" OUTPUT_FILE_FORMAT: FileFormat = FileFormat(OUTPUT_FORMAT_STR) + INPUT_FILE_FORMATS = frozenset({OUTPUT_FILE_FORMAT}) -class CBR(Rar): +class Cbr(Rar): """CBR Container.""" INPUT_FORMAT_STR: str = "CBR" - INPUT_FILE_FORMAT: FileFormat = FileFormat(INPUT_FORMAT_STR) INPUT_SUFFIX: str = "." + INPUT_FORMAT_STR.lower() + INPUT_FILE_FORMAT = FileFormat(INPUT_FORMAT_STR) + INPUT_FILE_FORMATS = frozenset({INPUT_FILE_FORMAT}) OUTPUT_FORMAT_STR: str = "CBZ" OUTPUT_FILE_FORMAT: FileFormat = FileFormat(OUTPUT_FORMAT_STR) @@ -139,6 +162,8 @@ class CBR(Rar): class EPub(Zip): """Epub Container.""" + # never convert inside epubs, breaks src links. + CONVERT: bool = False OUTPUT_FORMAT_STR: str = "EPUB" OUTPUT_FILE_FORMAT: FileFormat = FileFormat(OUTPUT_FORMAT_STR) - CONVERT: bool = False + INPUT_FILE_FORMATS = frozenset({OUTPUT_FILE_FORMAT}) diff --git a/picopt/old_timestamps.py b/picopt/old_timestamps.py index 23e30b4..8434546 100644 --- a/picopt/old_timestamps.py +++ b/picopt/old_timestamps.py @@ -5,12 +5,12 @@ from confuse.templates import AttrDict from treestamps import Treestamps -from picopt.configurable import Configurable +from picopt.path import is_path_ignored OLD_TIMESTAMPS_NAME = ".picopt_timestamp" -class OldTimestamps(Configurable): +class OldTimestamps: """Old timestamps importer.""" def _add_old_timestamp(self, old_timestamp_path: Path) -> None: @@ -24,7 +24,7 @@ def _add_old_timestamp(self, old_timestamp_path: Path) -> None: def _import_old_parent_timestamps(self, path: Path) -> None: """Walk up to the root eating old style timestamps.""" - if self.is_path_ignored(path) or ( + if is_path_ignored(self._config, path) or ( not self._config.symlinks and path.is_symlink() ): return @@ -35,7 +35,7 @@ def _import_old_parent_timestamps(self, path: Path) -> None: def _import_old_child_timestamps(self, path: Path) -> None: if ( - self.is_path_ignored(path) + is_path_ignored(self._config, path) or not self._config.symlinks and path.is_symlink() ): @@ -57,5 +57,5 @@ def import_old_timestamps(self) -> None: def __init__(self, config: AttrDict, timestamps: Treestamps): """Hold new timestamp object.""" - super().__init__(config) + self._config = config self._timestamps = timestamps diff --git a/picopt/path.py b/picopt/path.py new file mode 100644 index 0000000..817a314 --- /dev/null +++ b/picopt/path.py @@ -0,0 +1,173 @@ +"""Data classes.""" +from collections.abc import Sequence +from datetime import datetime, timezone +from io import BufferedReader, BytesIO +from os import stat_result +from pathlib import Path +from zipfile import ZipInfo + +from confuse import AttrDict + +TMP_DIR = Path("__picopt_tmp") +CONTAINER_PATH_DELIMETER = " - " + + +class PathInfo: + """Path Info object, mostly for passing down walk.""" + + def __init__( # noqa: PLR0913 + self, + top_path: Path, + container_mtime: float, + convert: bool, + is_case_sensitive: bool, + path: Path | None = None, + frame: int | None = None, + zipinfo: ZipInfo | None = None, + data: bytes | None = None, + container_paths: Sequence[str] | None = None, + ): + """Initialize.""" + self.top_path: Path = top_path + self.container_mtime: float = container_mtime + self.convert: bool = convert + self.is_case_sensitive: bool = is_case_sensitive + self.container_filename: str = "" + + # type + # A filesystem path + self.path: Path | None = path + # An animated image frame (in a container) + self.frame: int | None = frame + # An archived file (in a container) + self.zipinfo: ZipInfo | None = zipinfo + # The history of parent container names + self.container_paths: tuple[str, ...] = ( + tuple(container_paths) if container_paths else () + ) + + # optionally computed + self._data: bytes | None = data + + # always computed + self._is_dir: bool | None = None + self._stat: stat_result | bool | None = None + self._bytes_in: int | None = None + self._mtime: float | None = None + self._name: str | None = None + self._full_name: str | None = None + self._suffix: str | None = None + self._is_container_child: bool | None = None + + def is_dir(self) -> bool: + """Is the file a directory.""" + if self._is_dir is None: + if self.zipinfo: + self._is_dir = self.zipinfo.is_dir() + elif self.path: + self._is_dir = self.path.is_dir() + else: + self._is_dir = False + + return self._is_dir + + def is_container_child(self) -> bool: + """Is this path inside a container.""" + if self._is_container_child is None: + self._is_container_child = self.frame is not None or bool( + self.container_mtime + ) + return self._is_container_child + + def stat(self) -> stat_result | bool: + """Return fs_stat if possible.""" + if self._stat is None: + self._stat = self.path.stat() if self.path else False + return self._stat + + def data(self) -> bytes: + """Get the data from the file.""" + if self._data is None: + if not self.path or self.path.is_dir(): + self._data = b"" + else: + with self.path.open("rb") as fp: + self._data = fp.read() + return self._data + + def data_clear(self) -> None: + """Clear the data cache.""" + self._data = None + + def _buffer(self) -> BytesIO: + """Return a seekable buffer for the data.""" + return BytesIO(self.data()) + + def path_or_buffer(self) -> Path | BytesIO: + """Return a the path or the buffered data.""" + return self.path if self.path else self._buffer() + + def fp_or_buffer(self) -> BufferedReader | BytesIO: + """Return an file pointer for chunking or buffer.""" + if self.path: + return self.path.open("rb") + return self._buffer() + + def bytes_in(self) -> int: + """Return the length of the data.""" + if self._bytes_in is None: + stat = self.stat() + if stat not in (False, True): + self._bytes_in = stat.st_size + else: + self._bytes_in = len(self.data()) + return self._bytes_in + + def mtime(self) -> float: + """Choose an mtime.""" + if self._mtime is None: + if self.zipinfo: + self._mtime = datetime( + *self.zipinfo.date_time, tzinfo=timezone.utc + ).timestamp() + elif self.container_mtime: + self._mtime = self.container_mtime + else: + stat = self.stat() + if stat and stat is not True: + self._mtime = stat.st_mtime + else: + self._mtime = 0.0 + return self._mtime + + def name(self) -> str: + """Name.""" + if self._name is None: + if self.path: + self._name = str(self.path) + elif self.frame: + self._name = f"frame_#{self.frame:03d}.img" + elif self.zipinfo: + self._name = self.zipinfo.filename + else: + self._name = "Unknown" + return self._name + + def full_name(self) -> str: + """Full name.""" + if self._full_name is None: + self._full_name = CONTAINER_PATH_DELIMETER.join( + (*self.container_paths, self.name()) + ) + return self._full_name + + def suffix(self) -> str: + """Return file suffix.""" + if self._suffix is None: + self._suffix = Path(self.name()).suffix + return self._suffix + + +def is_path_ignored(config: AttrDict, path: Path): + """Match path against the ignore list.""" + return any(path.match(ignore_glob) for ignore_glob in config.ignore) diff --git a/picopt/pillow/header.py b/picopt/pillow/header.py index e63c915..7d47315 100644 --- a/picopt/pillow/header.py +++ b/picopt/pillow/header.py @@ -1,14 +1,7 @@ """Unpack items from a file descriptor.""" -import struct from dataclasses import dataclass -from typing import BinaryIO, Union - - -def unpack( - fmt_type: str, length: int, file_desc: BinaryIO -) -> Union[tuple[bytes, ...], tuple[int]]: - """Unpack information from a file according to format string & length.""" - return struct.unpack(fmt_type * length, file_desc.read(length)) +from mmap import mmap +from typing import BinaryIO @dataclass @@ -16,10 +9,10 @@ class ImageHeader: """The seek location and value of a byte header.""" offset: int - compare_bytes: tuple[bytes, ...] + compare_bytes: bytes - def compare(self, img: BinaryIO) -> bool: + def compare(self, img: BinaryIO | mmap) -> bool: """Seek to a spot in a binary file and compare a byte array.""" img.seek(self.offset) - compare = unpack("c", len(self.compare_bytes), img) + compare = img.read(len(self.compare_bytes)) return compare == self.compare_bytes diff --git a/picopt/pillow/jpeg_xmp.py b/picopt/pillow/jpeg_xmp.py new file mode 100755 index 0000000..e19dd83 --- /dev/null +++ b/picopt/pillow/jpeg_xmp.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +"""Get raw jpeg xml from Pillow.""" +# XXX This only seems to get some XMP data. xmp-tool finds more. +import struct + +from PIL.JpegImagePlugin import JpegImageFile + +APP1_SECTION_DELIMETER = b"\x00" +XAP_MARKER = b"http://ns.adobe.com/xap/1.0/" +SOI_MARKER = b"\xFF\xD8" +EOI_MARKER = b"\xFF\xE1" + + +def get_jpeg_xmp(image: JpegImageFile) -> str | None: + """Get raw jpeg xml from Pillow.""" + xmp = None + # Copied from PIL JpegImageFile + for segment, content in image.applist: # type: ignore + if segment == "APP1": + sections = content.split(APP1_SECTION_DELIMETER) + marker, xmp_tags = sections[:2] + if marker == XAP_MARKER: + xmp = xmp_tags.decode() + break + return xmp + + +def set_jpeg_xmp(jpeg_data: bytes, xmp: str) -> bytes: + """Insert xmp data into jpeg.""" + jpeg_buffer = bytearray(jpeg_data) + soi_index = jpeg_buffer.find(SOI_MARKER) + if soi_index == -1: + reason = "SOI marker not found in JPEG buffer." + raise ValueError(reason) + xmp_bytes = ( + XAP_MARKER + + APP1_SECTION_DELIMETER + + xmp.encode("utf-8") + + APP1_SECTION_DELIMETER + ) + return ( + jpeg_buffer[: soi_index + len(SOI_MARKER)] + + EOI_MARKER + + struct.pack(" Optional[int]: +def png_bit_depth(buffer: BinaryIO) -> int | None: """If a file is a png, get the bit depth from the standard position.""" - result = None - with path.open("rb") as img: - if PNG_HEADER.compare(img): - img.seek(BIT_DEPTH_OFFSET) # bit depth offset - depth = unpack("b", 1, img)[0] - result = int(depth) - else: - cprint(f"WARNING: {path} is not a png!", "yellow") + if PNG_HEADER.compare(buffer): + buffer.seek(BIT_DEPTH_OFFSET) # bit depth offset + depth = buffer.read(1) + result = int.from_bytes(depth, byteorder="little") + else: + cprint("WARNING: cannot find bit depts of non png.", "yellow") + result = None return result @@ -32,7 +31,8 @@ def main() -> None: """Stand alone cli tool for getting png bit depth.""" import sys - bit_depth = png_bit_depth(Path(sys.argv[1])) + with Path(sys.argv[1]).open("rb") as f: + bit_depth = png_bit_depth(f) print(bit_depth) # noqa T201 diff --git a/picopt/pillow/webp_lossless.py b/picopt/pillow/webp_lossless.py index 331cce8..a9f8231 100755 --- a/picopt/pillow/webp_lossless.py +++ b/picopt/pillow/webp_lossless.py @@ -4,26 +4,47 @@ This should be a part of Pillow https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification """ +from io import BufferedReader, BytesIO +from mmap import PROT_READ, mmap from pathlib import Path from picopt.pillow.header import ImageHeader -RIFF_HEADER = ImageHeader(0, (b"R", b"I", b"F", b"F")) -WEBP_HEADER = ImageHeader(8, (b"W", b"E", b"B", b"P")) -VP8L_HEADER = ImageHeader(12, (b"V", b"P", b"8", b"L")) +# RIFF_HEADER = ImageHeader(0, b"RIFF")) +# WEBP_HEADER = ImageHeader(8, b"WEBP")) +VP8_HEADER = ImageHeader(12, b"VP8") +VP8L_HEADER = b"VP8L" +SEARCH_LEN = 128 -HEADERS = (RIFF_HEADER, WEBP_HEADER, VP8L_HEADER) - -def is_lossless(filename: str) -> bool: +def is_lossless(input_buffer: BytesIO | BufferedReader) -> bool: """Compare header types against lossless types.""" result = True - path = Path(filename) - with path.open("rb") as img: - for header in HEADERS: - if not header.compare(img): - result = False - break + + buffer: BytesIO | mmap = ( + mmap(input_buffer.fileno(), 0, prot=PROT_READ) + if isinstance(input_buffer, BufferedReader) + else input_buffer + ) + + if not VP8_HEADER.compare(buffer): + result = False + else: + x = buffer.read(1) + if x == b"L": + result = True + elif x == b"X": + finder = ( + buffer + if isinstance(buffer, mmap) + else bytearray(buffer.read(SEARCH_LEN)) + ) # type: ignore + result = finder.find(VP8L_HEADER) != -1 # type: ignore + else: + result = False + + input_buffer.close() + buffer.close() return result @@ -31,7 +52,8 @@ def main() -> None: """Stand alone cli tool for getting lossless status.""" import sys - lossless = is_lossless(sys.argv[1]) + with Path(sys.argv[1]).open("rb") as f: + lossless = is_lossless(f) print(lossless) # noqa T201 diff --git a/picopt/stats.py b/picopt/stats.py index 5bd90bf..048a03f 100644 --- a/picopt/stats.py +++ b/picopt/stats.py @@ -1,33 +1,51 @@ """Statistics for the optimization operations.""" -from dataclasses import dataclass, field +from dataclasses import dataclass +from pathlib import Path from subprocess import CalledProcessError from typing import TYPE_CHECKING +from confuse import AttrDict from humanize import naturalsize from termcolor import cprint +from picopt.path import CONTAINER_PATH_DELIMETER, PathInfo + if TYPE_CHECKING: from termcolor._types import Attribute, Color -from picopt.data import ReportInfo + +@dataclass +class ReportStatBase: + """Base dataclass for ReportStats.""" + + path: Path | None + bytes_in: int = 0 + bytes_out: int = 0 + exc: Exception | None = None + data: bytes = b"" -class ReportStats: +class ReportStats(ReportStatBase): """Container for reported stats from optimization operations.""" _TAB = " " * 4 def __init__( self, - info: ReportInfo, + *args, + config: AttrDict | None = None, + path_info: PathInfo | None = None, + **kwargs, ) -> None: """Initialize required instance variables.""" - self.path = info.path - self.test = info.test - self.convert = info.convert - self.exc = info.exc - self.bytes_in = info.bytes_in - self.bytes_out = info.bytes_out + # Don't store these large data structs, just tidbits. + self.bigger: bool = config.bigger if config else False + self.test: bool = config.test if config else False + self.convert: bool = path_info.convert if path_info else False + self.container_paths: tuple[str, ...] = ( + tuple(path_info.container_paths) if path_info else () + ) + super().__init__(*args, **kwargs) self.saved = self.bytes_in - self.bytes_out def _new_percent_saved(self) -> str: @@ -38,19 +56,28 @@ def _new_percent_saved(self) -> str: return f"{percent_saved:.2f}% ({saved})" + def _get_full_path(self) -> str: + cps = self.container_paths + return CONTAINER_PATH_DELIMETER.join((*cps, str(self.path))) + def _report_saved(self) -> str: """Return the percent saved.""" - report = "" - - report += f"{self.path}: " + report = f"{self._get_full_path()}: " report += self._new_percent_saved() if self.test: - report += " could be saved." + report += " would be" + if self.saved > 0: + report += " saved" + elif self.saved < 0: + report += " lost" + if self.saved <= 0 and not self.bigger: + report += ", kept original" + return report def _report_error(self) -> str: """Return the error report string.""" - report = f"ERROR: {self.path}" + report = f"ERROR: {self._get_full_path()}" if isinstance(self.exc, CalledProcessError): report += f"\n{self._TAB}retcode: {self.exc.returncode}" if self.exc.cmd: @@ -81,10 +108,55 @@ def report(self) -> None: cprint(report, color, attrs=attrs) -@dataclass class Totals: """Totals for final report.""" - bytes_in: int = 0 - bytes_out: int = 0 - errors: list[ReportStats] = field(default_factory=list) + def __init__(self, config: AttrDict): + """Initialize Totals.""" + self.bytes_in: int = 0 + self.bytes_out: int = 0 + self.errors: list[ReportStats] = [] + self._config: AttrDict = config + + ########## + # Finish # + ########## + def _report_bytes_in(self) -> None: + """Report Totals if there were bytes in.""" + if not self._config.verbose and not self._config.test: + return + bytes_saved = self.bytes_in - self.bytes_out + percent_bytes_saved = bytes_saved / self.bytes_in * 100 + msg = "" + if self._config.test: + if percent_bytes_saved > 0: + msg += "Could save" + elif percent_bytes_saved == 0: + msg += "Could even out for" + else: + msg += "Could lose" + elif percent_bytes_saved > 0: + msg += "Saved" + elif percent_bytes_saved == 0: + msg += "Evened out" + else: + msg = "Lost" + natural_saved = naturalsize(bytes_saved) + msg += f" a total of {natural_saved} or {percent_bytes_saved:.2f}%" + cprint(msg) + if self._config.test: + cprint("Test run did not change any files.") + + def report(self) -> None: + """Report the total number and percent of bytes saved.""" + if self._config.verbose == 1: + cprint("") + if self.bytes_in: + self._report_bytes_in() + elif self._config.verbose: + cprint("Didn't optimize any files.") + + if self.errors: + cprint("Errors with the following files:", "red") + for rs in self.errors: + rs.report() diff --git a/picopt/walk.py b/picopt/walk.py index 810309f..36ae871 100644 --- a/picopt/walk.py +++ b/picopt/walk.py @@ -1,72 +1,39 @@ """Walk the directory trees and files and call the optimizers.""" import os import shutil -import time import traceback from multiprocessing.pool import ApplyResult, Pool from pathlib import Path -from typing import Optional +from types import MappingProxyType from confuse.templates import AttrDict -from humanize import naturalsize from termcolor import cprint from treestamps import Grovestamps, GrovestampsConfig, Treestamps from picopt import PROGRAM_NAME from picopt.config import TIMESTAMPS_CONFIG_KEYS -from picopt.configurable import Configurable -from picopt.data import PathInfo, ReportInfo +from picopt.exceptions import PicoptError from picopt.handlers.container import ContainerHandler from picopt.handlers.factory import create_handler from picopt.handlers.handler import Handler from picopt.handlers.image import ImageHandler -from picopt.handlers.png import Png -from picopt.handlers.webp import WebP -from picopt.handlers.zip import CBR, Rar from picopt.old_timestamps import OLD_TIMESTAMPS_NAME, OldTimestamps +from picopt.path import PathInfo, is_path_ignored from picopt.stats import ReportStats, Totals -class Walk(Configurable): +class Walk: """Walk object for storing state of a walk run.""" - TIMESTAMPS_FILENAMES = frozenset(Treestamps.get_filenames(PROGRAM_NAME)) + TIMESTAMPS_FILENAMES = frozenset( + {*Treestamps.get_filenames(PROGRAM_NAME), OLD_TIMESTAMPS_NAME} + ) LOWERCASE_TESTNAME = ".picopt_case_sensitive_test" UPPERCASE_TESTNAME = LOWERCASE_TESTNAME.upper() ######## # Init # ######## - def _convert_message( - self, convert_from_formats: frozenset[str], convert_handler: type[Handler] - ): - convert_from = ", ".join( - sorted(convert_from_formats & frozenset(self._config.formats)) - ) - convert_to = convert_handler.OUTPUT_FORMAT_STR - cprint(f"Converting {convert_from} to {convert_to}", "cyan") - - def _init_run_verbose(self) -> None: - """Print verbose init messages.""" - format_list = ", ".join(sorted(self._config.formats)) - cprint(f"Optimizing formats: {format_list}") - if self._config.convert_to: - if WebP.OUTPUT_FORMAT_STR in self._config.convert_to: - self._convert_message( - self._config.computed.convertable_formats.webp, WebP - ) - elif Png.OUTPUT_FORMAT_STR in self._config.convert_to: - self._convert_message( - self._config.computed.convertable_formats.png, Png - ) - if Rar.OUTPUT_FORMAT_STR in self._config.convert_to: - self._convert_message(frozenset([Rar.INPUT_FORMAT_STR]), Rar) - if CBR.OUTPUT_FORMAT_STR in self._config.convert_to: - self._convert_message(frozenset([CBR.INPUT_FORMAT_STR]), CBR) - if self._config.after is not None: - after = time.ctime(self._config.after) - cprint(f"Optimizing after {after}") - def _init_run_timestamps(self) -> None: """Init timestamps.""" config = GrovestampsConfig( @@ -88,15 +55,11 @@ def _init_run(self): # Validate top_paths if not self._top_paths: msg = "No paths to optimize." - raise ValueError(msg) + raise PicoptError(msg) for path in self._top_paths: if not path.exists(): msg = f"Path does not exist: {path}" - raise ValueError(msg) - - # Tell the user what we're doing - if self._config.verbose: - self._init_run_verbose() + raise PicoptError(msg) # Init timestamps if self._config.timestamps: @@ -105,63 +68,54 @@ def _init_run(self): ############ # Checkers # ############ - def _is_skippable(self, path: Path) -> bool: # noqa C901 + def _is_skippable(self, path_info: PathInfo) -> bool: """Handle things that are not optimizable files.""" - skip = False - # File types - if not self._config.symlinks and path.is_symlink(): - if self._config.verbose > 1: - cprint(f"Skip symlink {path}", "white", attrs=["dark"]) - skip = True - elif path.name in self.TIMESTAMPS_FILENAMES: - if self._config.verbose > 1: - cprint(f"Skip timestamp {path}", "white", attrs=["dark"]) - skip = True - elif path.name in OLD_TIMESTAMPS_NAME: - if self._config.verbose > 1: - cprint(f"Skip legacy timestamp {path}", "white", attrs=["dark"]) - skip = True - elif not path.exists(): - if self._config.verbose > 1: - cprint(f"WARNING: {path} not found.", "yellow") - skip = True - elif self.is_path_ignored(path): - if self._config.verbose > 1: - cprint(f"Skip ignored {path}", "white", attrs=["dark"]) - skip = True + reason = None + color = "white" + attrs: list = ["dark"] - return skip + # File types + if path_info.zipinfo and path_info.is_dir(): + reason = f"Skip archive directory {path_info.full_name()}" + elif ( + not self._config.symlinks and path_info.path and path_info.path.is_symlink() + ): + reason = f"Skip symlink {path_info.full_name()}" + elif path_info.name() in self.TIMESTAMPS_FILENAMES: + legacy = "legacy " if path_info.name() == OLD_TIMESTAMPS_NAME else "" + reason = f"Skip {legacy}timestamp {path_info.full_name()}" + elif not path_info.zipinfo and path_info.path and not path_info.path.exists(): + reason = f"WARNING: {path_info.full_name()} not found." + color = "yellow" + attrs = [] + elif is_path_ignored(self._config, Path(path_info.name())): + reason = f"Skip ignored {path_info.full_name()}" + + if reason and self._config.verbose > 1: + cprint(reason, color, attrs=attrs) + + return bool(reason) def _is_older_than_timestamp( self, - info: PathInfo, + path_info: PathInfo, ) -> bool: """Is the file older than the timestamp.""" - # If the file is in an container, use the container time. - # This helps if you have a new container that you - # collected from someone who put really old files in it that - # should still be optimised - mtime = ( - info.container_mtime - if info.container_mtime is not None - else info.path.stat().st_mtime - ) - - # The timestamp or configured walk after time for comparison. if self._config.after is not None: walk_after = self._config.after - elif info.container_mtime is None and self._config.timestamps: - timestamps = self._timestamps.get(info.top_path, {}) - walk_after = timestamps.get(info.path) + elif path_info.path and self._config.timestamps: + timestamps = self._timestamps.get(path_info.top_path, {}) + walk_after = timestamps.get(path_info.path) else: walk_after = None if walk_after is None: return False + mtime = path_info.mtime() return bool(mtime <= walk_after) - def _clean_up_working_files(self, path) -> None: + def _clean_up_working_files(self, path: Path) -> None: """Auto-clean old working temp files if encountered.""" try: if path.is_dir(): @@ -179,7 +133,7 @@ def _clean_up_working_files(self, path) -> None: def _finish_results( self, results: list[ApplyResult], - container_mtime: Optional[float], + container_mtime: float | None, top_path: Path, ) -> None: """Get the async results and total them.""" @@ -187,76 +141,79 @@ def _finish_results( final_result = result.get() if final_result.exc: final_result.report() + self._totals.errors.append(final_result) else: self._totals.bytes_in += final_result.bytes_in - self._totals.bytes_out += final_result.bytes_out + if final_result.saved > 0 and not self._config.bigger: + self._totals.bytes_out += final_result.bytes_out + else: + self._totals.bytes_out += final_result.bytes_in if self._config.timestamps and not container_mtime: timestamps = self._timestamps[top_path] timestamps.set(final_result.path) - def walk_dir(self, info: PathInfo) -> None: + def walk_dir(self, path_info: PathInfo) -> None: """Recursively optimize a directory.""" - if not self._config.recurse and info.container_mtime is None: + if ( + not self._config.recurse + or path_info.is_container_child() + or not path_info.is_dir() + ): # Skip return results = [] files = [] - for name in sorted(os.listdir(info.path)): - entry_path = info.path / name + dir_path: Path = path_info.path # type: ignore + + for name in sorted(os.listdir(dir_path)): + entry_path = dir_path / name if entry_path.is_dir(): path_info = PathInfo( - entry_path, - info.top_path, - info.container_mtime, - info.convert, - info.is_case_sensitive, + path_info.top_path, + path_info.container_mtime, + path_info.convert, + path_info.is_case_sensitive, + path=entry_path, ) self.walk_file(path_info) else: files.append(entry_path) - for entry_path in files: + for entry_path in sorted(files): path_info = PathInfo( - entry_path, - info.top_path, - info.container_mtime, - info.convert, - info.is_case_sensitive, + path_info.top_path, + path_info.container_mtime, + path_info.convert, + path_info.is_case_sensitive, + path=entry_path, ) - result = self.walk_file(path_info) - if result: + if result := self.walk_file(path_info): results.append(result) self._finish_results( results, - info.container_mtime, - info.top_path, + path_info.container_mtime, + path_info.top_path, ) - if self._config.timestamps and not info.container_mtime: + if self._config.timestamps: # Compact timestamps after every directory completes - timestamps = self._timestamps[info.top_path] - timestamps.set(info.path, compact=True) + timestamps = self._timestamps[path_info.top_path] + timestamps.set(dir_path, compact=True) - def _walk_container( - self, top_path: Path, handler: ContainerHandler, is_case_sensitive: bool - ) -> ApplyResult: + def _walk_container(self, handler: ContainerHandler) -> ApplyResult: """Optimize a container.""" result: ApplyResult try: - handler.unpack() - container_mtime = handler.original_path.stat().st_mtime - path_info = PathInfo( - handler.tmp_container_dir, - top_path, - container_mtime, - handler.CONVERT, - is_case_sensitive, - ) + for path_info in handler.unpack(): + container_result = self.walk_file(path_info) + handler.set_task(path_info, container_result) + + handler.optimize_contents() - self.walk_dir(path_info) + # at this point handler_final_result array contains buffers not mp-results result = self._pool.apply_async(handler.repack) except Exception as exc: traceback.print_exc() @@ -274,115 +231,77 @@ def _skip_older_than_timestamp(self, path) -> None: def _is_walk_file_skip( self, - info: PathInfo, + path_info: PathInfo, ) -> bool: """Decide on skip the file or not.""" - if self._is_skippable(info.path): + if self._is_skippable(path_info): if self._config.verbose == 1: cprint(".", "white", attrs=["dark"], end="") return True - if info.path.name.rfind(Handler.WORKING_SUFFIX) > -1: - self._clean_up_working_files(info.path) + path = path_info.path + if path and path.name.rfind(Handler.WORKING_SUFFIX) > -1: + self._clean_up_working_files(path) if self._config.verbose == 1: cprint(".", "yellow", end="") return True return False - def _handle_file(self, handler, top_path, is_case_sensitive): + def _handle_file(self, handler): """Call the correct walk or pool apply for the handler.""" if isinstance(handler, ContainerHandler): # Unpack inline, not in the pool, and walk immediately like dirs. - result = self._walk_container(top_path, handler, is_case_sensitive) + result = self._walk_container(handler) elif isinstance(handler, ImageHandler): - result = self._pool.apply_async(handler.optimize_image) + result = self._pool.apply_async(handler.optimize_wrapper) else: - msg = f"bad handler {handler}" + msg = f"Bad picopt handler {handler}" raise TypeError(msg) return result - def walk_file(self, info: PathInfo) -> Optional[ApplyResult]: + def walk_file(self, path_info: PathInfo) -> ApplyResult | None: """Optimize an individual file.""" try: - if self._is_walk_file_skip(info): - return None + if path_info.frame is None: + if self._is_walk_file_skip(path_info): + return None - if info.path.is_dir(): - return self.walk_dir(info) + if path_info.is_dir(): + return self.walk_dir(path_info) - if self._is_older_than_timestamp(info): - self._skip_older_than_timestamp(info.path) - return None + if self._is_older_than_timestamp(path_info): + self._skip_older_than_timestamp(path_info) + return None - handler = create_handler(self._config, info) + handler = create_handler(self._config, path_info) if handler is None: return None if self._config.list_only: return None - result = self._handle_file(handler, info.top_path, info.is_case_sensitive) + result = self._handle_file(handler) except Exception as exc: traceback.print_exc() - report_info = ReportInfo( - path=info.path, - convert=info.convert, - test=self._config.test, - exc=exc, + apply_kwargs = MappingProxyType( + { + "path": path_info.path, + "convert": path_info.convert, + "bytes_in": path_info.bytes_in(), + "test": self._config.test, + "exc": exc, + } ) - result = self._pool.apply_async(ReportStats, (report_info,)) + result = self._pool.apply_async(ReportStats, (), apply_kwargs) return result - ########## - # Finish # - ########## - def _report_totals_bytes_in(self) -> None: - """Report Totals if there were bytes in.""" - if not self._config.verbose and not self._config.test: - return - bytes_saved = self._totals.bytes_in - self._totals.bytes_out - percent_bytes_saved = bytes_saved / self._totals.bytes_in * 100 - msg = "" - if self._config.test: - if percent_bytes_saved > 0: - msg += "Could save" - elif percent_bytes_saved == 0: - msg += "Could even out for" - else: - msg += "Could lose" - elif percent_bytes_saved > 0: - msg += "Saved" - elif percent_bytes_saved == 0: - msg += "Evened out" - else: - msg = "Lost" - natural_saved = naturalsize(bytes_saved) - msg += f" a total of {natural_saved} or {percent_bytes_saved:.2f}%" - cprint(msg) - if self._config.test: - cprint("Test run did not change any files.") - - def _report_totals(self) -> None: - """Report the total number and percent of bytes saved.""" - if self._config.verbose == 1: - cprint("") - if self._totals.bytes_in: - self._report_totals_bytes_in() - elif self._config.verbose: - cprint("Didn't optimize any files.") - - if self._totals.errors: - cprint("Errors with the following files:", "red") - for rs in self._totals.errors: - rs.report() - ################ # Init and run # ################ def __init__(self, config: AttrDict) -> None: """Initialize.""" - super().__init__(config) - self._totals = Totals() + self._config = config + self._totals = Totals(config) top_paths = [] paths: list[Path] = sorted(frozenset(self._config.paths)) for path in paths: @@ -417,7 +336,7 @@ def run(self) -> Totals: for top_path in self._top_paths: dirpath = Treestamps.get_dir(top_path) is_case_sensitive = self._is_case_sensitive(dirpath) - path_info = PathInfo(top_path, dirpath, None, True, is_case_sensitive) + path_info = PathInfo(dirpath, 0.0, True, is_case_sensitive, path=top_path) result = self.walk_file(path_info) if not result: continue @@ -433,9 +352,9 @@ def run(self) -> Totals: self._pool.close() self._pool.join() + cprint("done.") + if self._config.timestamps: self._timestamps.dump() - # Finish by reporting totals - self._report_totals() return self._totals diff --git a/poetry.lock b/poetry.lock index fdc7cdb..484ba0b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -7,15 +7,32 @@ description = "Codespell" optional = false python-versions = ">=3.8" files = [ - {file = "codespell-2.2.6-py3-none-any.whl", hash = "sha256:9ee9a3e5df0990604013ac2a9f22fa8e57669c827124a2e961fe8a1da4cacc07"}, - {file = "codespell-2.2.6.tar.gz", hash = "sha256:a8c65d8eb3faa03deabab6b3bbe798bea72e1799c7e9e955d57eca4096abcff9"}, + { file = "codespell-2.2.6-py3-none-any.whl", hash = "sha256:9ee9a3e5df0990604013ac2a9f22fa8e57669c827124a2e961fe8a1da4cacc07" }, + { file = "codespell-2.2.6.tar.gz", hash = "sha256:a8c65d8eb3faa03deabab6b3bbe798bea72e1799c7e9e955d57eca4096abcff9" }, ] [package.extras] -dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"] +dev = [ + "Pygments", + "build", + "chardet", + "pre-commit", + "pytest", + "pytest-cov", + "pytest-dependency", + "ruff", + "tomli", + "twine", +] hard-encoding-detection = ["chardet"] toml = ["tomli"] -types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"] +types = [ + "chardet (>=5.1.0)", + "mypy", + "pytest", + "pytest-cov", + "pytest-dependency", +] [[package]] name = "colorama" @@ -24,8 +41,8 @@ description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, + { file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, + { file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" }, ] [[package]] @@ -35,8 +52,8 @@ description = "Painless YAML configuration." optional = false python-versions = ">=3.6" files = [ - {file = "confuse-2.0.1-py3-none-any.whl", hash = "sha256:9b9e5bbc70e2cb9b318bcab14d917ec88e21bf1b724365e3815eb16e37aabd2a"}, - {file = "confuse-2.0.1.tar.gz", hash = "sha256:7379a2ad49aaa862b79600cc070260c1b7974d349f4fa5e01f9afa6c4dd0611f"}, + { file = "confuse-2.0.1-py3-none-any.whl", hash = "sha256:9b9e5bbc70e2cb9b318bcab14d917ec88e21bf1b724365e3815eb16e37aabd2a" }, + { file = "confuse-2.0.1.tar.gz", hash = "sha256:7379a2ad49aaa862b79600cc070260c1b7974d349f4fa5e01f9afa6c4dd0611f" }, ] [package.dependencies] @@ -44,67 +61,67 @@ pyyaml = "*" [[package]] name = "coverage" -version = "7.4.0" +version = "7.4.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"}, - {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"}, - {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"}, - {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"}, - {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"}, - {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"}, - {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"}, - {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"}, - {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"}, - {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"}, - {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"}, - {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"}, - {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"}, - {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"}, - {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"}, - {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"}, - {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"}, - {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"}, - {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"}, - {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"}, - {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"}, - {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"}, - {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"}, - {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"}, - {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"}, - {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"}, - {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"}, - {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"}, - {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"}, - {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"}, - {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"}, - {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"}, - {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"}, - {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"}, - {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"}, - {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"}, - {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"}, - {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"}, - {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"}, - {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"}, - {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"}, - {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"}, - {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"}, - {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"}, - {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"}, - {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"}, - {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"}, - {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"}, - {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"}, - {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"}, - {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"}, - {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"}, + { file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7" }, + { file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61" }, + { file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee" }, + { file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25" }, + { file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19" }, + { file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630" }, + { file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c" }, + { file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b" }, + { file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016" }, + { file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018" }, + { file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295" }, + { file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c" }, + { file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676" }, + { file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd" }, + { file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011" }, + { file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74" }, + { file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1" }, + { file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6" }, + { file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5" }, + { file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968" }, + { file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581" }, + { file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6" }, + { file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66" }, + { file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156" }, + { file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3" }, + { file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1" }, + { file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1" }, + { file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc" }, + { file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74" }, + { file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448" }, + { file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218" }, + { file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45" }, + { file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d" }, + { file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06" }, + { file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766" }, + { file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75" }, + { file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60" }, + { file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad" }, + { file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042" }, + { file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d" }, + { file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54" }, + { file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70" }, + { file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628" }, + { file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950" }, + { file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1" }, + { file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7" }, + { file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756" }, + { file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35" }, + { file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c" }, + { file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a" }, + { file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166" }, + { file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04" }, ] [package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} +tomli = { version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\"" } [package.extras] toml = ["tomli"] @@ -116,8 +133,8 @@ description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + { file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14" }, + { file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68" }, ] [package.extras] @@ -130,64 +147,64 @@ description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, + { file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a" }, + { file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881" }, + { file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b" }, + { file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a" }, + { file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83" }, + { file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405" }, + { file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f" }, + { file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb" }, + { file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9" }, + { file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61" }, + { file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559" }, + { file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e" }, + { file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33" }, + { file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379" }, + { file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22" }, + { file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3" }, + { file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d" }, + { file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728" }, + { file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be" }, + { file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e" }, + { file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676" }, + { file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc" }, + { file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230" }, + { file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf" }, + { file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305" }, + { file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6" }, + { file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2" }, + { file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274" }, + { file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0" }, + { file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f" }, + { file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414" }, + { file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c" }, + { file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41" }, + { file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7" }, + { file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6" }, + { file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d" }, + { file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67" }, + { file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca" }, + { file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04" }, + { file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc" }, + { file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506" }, + { file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b" }, + { file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4" }, + { file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5" }, + { file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da" }, + { file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3" }, + { file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf" }, + { file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53" }, + { file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257" }, + { file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac" }, + { file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71" }, + { file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61" }, + { file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b" }, + { file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6" }, + { file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113" }, + { file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e" }, + { file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067" }, + { file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491" }, ] [package.extras] @@ -201,8 +218,8 @@ description = "Python humanize utilities" optional = false python-versions = ">=3.8" files = [ - {file = "humanize-4.9.0-py3-none-any.whl", hash = "sha256:ce284a76d5b1377fd8836733b983bfb0b76f1aa1c090de2566fcf008d7f6ab16"}, - {file = "humanize-4.9.0.tar.gz", hash = "sha256:582a265c931c683a7e9b8ed9559089dea7edcf6cc95be39a3cbc2c5d5ac2bcfa"}, + { file = "humanize-4.9.0-py3-none-any.whl", hash = "sha256:ce284a76d5b1377fd8836733b983bfb0b76f1aa1c090de2566fcf008d7f6ab16" }, + { file = "humanize-4.9.0.tar.gz", hash = "sha256:582a265c931c683a7e9b8ed9559089dea7edcf6cc95be39a3cbc2c5d5ac2bcfa" }, ] [package.extras] @@ -215,8 +232,8 @@ description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + { file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" }, + { file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3" }, ] [[package]] @@ -226,8 +243,8 @@ description = "Create Python CLI apps with little to no effort at all!" optional = false python-versions = "*" files = [ - {file = "mando-0.7.1-py2.py3-none-any.whl", hash = "sha256:26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a"}, - {file = "mando-0.7.1.tar.gz", hash = "sha256:18baa999b4b613faefb00eac4efadcf14f510b59b924b66e08289aa1de8c3500"}, + { file = "mando-0.7.1-py2.py3-none-any.whl", hash = "sha256:26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a" }, + { file = "mando-0.7.1.tar.gz", hash = "sha256:18baa999b4b613faefb00eac4efadcf14f510b59b924b66e08289aa1de8c3500" }, ] [package.dependencies] @@ -243,62 +260,62 @@ description = "MessagePack serializer" optional = false python-versions = ">=3.8" files = [ - {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862"}, - {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329"}, - {file = "msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b"}, - {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6"}, - {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee"}, - {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d"}, - {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d"}, - {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1"}, - {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681"}, - {file = "msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9"}, - {file = "msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e"}, - {file = "msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1"}, - {file = "msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5"}, - {file = "msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9"}, - {file = "msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf"}, - {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95"}, - {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0"}, - {file = "msgpack-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7"}, - {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d"}, - {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524"}, - {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc"}, - {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc"}, - {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf"}, - {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c"}, - {file = "msgpack-1.0.7-cp38-cp38-win32.whl", hash = "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2"}, - {file = "msgpack-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c"}, - {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f"}, - {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81"}, - {file = "msgpack-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc"}, - {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d"}, - {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7"}, - {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61"}, - {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819"}, - {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd"}, - {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f"}, - {file = "msgpack-1.0.7-cp39-cp39-win32.whl", hash = "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad"}, - {file = "msgpack-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3"}, - {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, + { file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862" }, + { file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329" }, + { file = "msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b" }, + { file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6" }, + { file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee" }, + { file = "msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d" }, + { file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d" }, + { file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1" }, + { file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681" }, + { file = "msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9" }, + { file = "msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415" }, + { file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84" }, + { file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93" }, + { file = "msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8" }, + { file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46" }, + { file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b" }, + { file = "msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e" }, + { file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002" }, + { file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c" }, + { file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e" }, + { file = "msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1" }, + { file = "msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82" }, + { file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b" }, + { file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4" }, + { file = "msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee" }, + { file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5" }, + { file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672" }, + { file = "msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075" }, + { file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba" }, + { file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c" }, + { file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5" }, + { file = "msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9" }, + { file = "msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf" }, + { file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95" }, + { file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0" }, + { file = "msgpack-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7" }, + { file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d" }, + { file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524" }, + { file = "msgpack-1.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc" }, + { file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc" }, + { file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf" }, + { file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c" }, + { file = "msgpack-1.0.7-cp38-cp38-win32.whl", hash = "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2" }, + { file = "msgpack-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c" }, + { file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f" }, + { file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81" }, + { file = "msgpack-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc" }, + { file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d" }, + { file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7" }, + { file = "msgpack-1.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61" }, + { file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819" }, + { file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd" }, + { file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f" }, + { file = "msgpack-1.0.7-cp39-cp39-win32.whl", hash = "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad" }, + { file = "msgpack-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3" }, + { file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87" }, ] [[package]] @@ -308,7 +325,7 @@ description = "Transition packgage for pynvim" optional = false python-versions = "*" files = [ - {file = "neovim-0.3.1.tar.gz", hash = "sha256:a6a0e7a5b4433bf4e6ddcbc5c5ff44170be7d84259d002b8e8d8fb4ee78af60f"}, + { file = "neovim-0.3.1.tar.gz", hash = "sha256:a6a0e7a5b4433bf4e6ddcbc5c5ff44170be7d84259d002b8e8d8fb4ee78af60f" }, ] [package.dependencies] @@ -321,8 +338,8 @@ description = "Node.js virtual environment builder" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, + { file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec" }, + { file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2" }, ] [package.dependencies] @@ -335,8 +352,19 @@ description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + { file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" }, + { file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5" }, +] + +[[package]] +name = "piexif" +version = "1.1.3" +description = "To simplify exif manipulations with python. Writing, reading, and more..." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + { file = "piexif-1.1.3-py2.py3-none-any.whl", hash = "sha256:3bc435d171720150b81b15d27e05e54b8abbde7b4242cddd81ef160d283108b6" }, + { file = "piexif-1.1.3.zip", hash = "sha256:83cb35c606bf3a1ea1a8f0a25cb42cf17e24353fd82e87ae3884e74a302a5f1b" }, ] [[package]] @@ -346,81 +374,100 @@ description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, - {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, - {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, - {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, - {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, - {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, - {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, - {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, - {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, - {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, - {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, - {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, - {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, - {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, - {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, - {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, - {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, - {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, - {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, - {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, - {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, - {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, - {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, - {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, - {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, - {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, - {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, - {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, - {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, - {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, - {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, - {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, - {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, - {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, + { file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e" }, + { file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588" }, + { file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452" }, + { file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4" }, + { file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563" }, + { file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2" }, + { file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c" }, + { file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0" }, + { file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023" }, + { file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72" }, + { file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad" }, + { file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5" }, + { file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67" }, + { file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61" }, + { file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e" }, + { file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f" }, + { file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311" }, + { file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1" }, + { file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757" }, + { file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068" }, + { file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56" }, + { file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1" }, + { file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef" }, + { file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac" }, + { file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c" }, + { file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa" }, + { file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2" }, + { file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04" }, + { file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f" }, + { file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb" }, + { file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f" }, + { file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9" }, + { file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48" }, + { file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9" }, + { file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483" }, + { file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129" }, + { file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e" }, + { file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213" }, + { file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d" }, + { file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6" }, + { file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe" }, + { file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e" }, + { file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39" }, + { file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67" }, + { file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364" }, + { file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb" }, + { file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e" }, + { file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01" }, + { file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13" }, + { file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7" }, + { file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591" }, + { file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516" }, + { file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8" }, + { file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869" }, + { file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a" }, + { file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2" }, + { file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04" }, + { file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2" }, + { file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a" }, + { file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6" }, + { file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7" }, + { file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f" }, + { file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e" }, + { file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5" }, + { file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b" }, + { file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a" }, + { file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868" }, + { file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e" }, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +docs = [ + "furo", + "olefile", + "sphinx (>=2.4)", + "sphinx-copybutton", + "sphinx-inline-tabs", + "sphinx-removed-in", + "sphinxext-opengraph", +] fpx = ["olefile"] mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +tests = [ + "check-manifest", + "coverage", + "defusedxml", + "markdown2", + "olefile", + "packaging", + "pyroma", + "pytest", + "pytest-cov", + "pytest-timeout", +] typing = ["typing-extensions"] xmp = ["defusedxml"] @@ -431,8 +478,8 @@ description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + { file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981" }, + { file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be" }, ] [package.extras] @@ -446,8 +493,8 @@ description = "Python client for Neovim" optional = false python-versions = ">=3.7" files = [ - {file = "pynvim-0.5.0-py2.py3-none-any.whl", hash = "sha256:2ac197ef0cdfff53719184a45c33cfb7cef88d1c9bf7f0525c21b3239cb5365f"}, - {file = "pynvim-0.5.0.tar.gz", hash = "sha256:e80a11f6f5d194c6a47bea4135b90b55faca24da3544da7cf4a5f7ba8fb09215"}, + { file = "pynvim-0.5.0-py2.py3-none-any.whl", hash = "sha256:2ac197ef0cdfff53719184a45c33cfb7cef88d1c9bf7f0525c21b3239cb5365f" }, + { file = "pynvim-0.5.0.tar.gz", hash = "sha256:e80a11f6f5d194c6a47bea4135b90b55faca24da3544da7cf4a5f7ba8fb09215" }, ] [package.dependencies] @@ -458,15 +505,93 @@ msgpack = ">=0.5.0" pyuv = ["pyuv (>=1.0.0)"] test = ["pytest"] +[[package]] +name = "pyoxipng" +version = "9.0.0" +description = "Python wrapper for multithreaded .png image file optimizer oxipng" +optional = false +python-versions = ">=3.8" +files = [ + { file = "pyoxipng-9.0.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:6390c9ca861eecdd2392075a5427e5f778af3df4b6bcd76db68f039fb6eb7077" }, + { file = "pyoxipng-9.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ea488551d8ac73084ff38642bfe170282bc151e963d382e170cefbe6a82eedd2" }, + { file = "pyoxipng-9.0.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2e959ec92d6776ee7e1564fbf0ac1d82e7a2503eaf4ab5fa4214989a793b565e" }, + { file = "pyoxipng-9.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa43dcab2856066675532fa9827f271e34859c66f18835b6ad497b1c6dcc910e" }, + { file = "pyoxipng-9.0.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4df410adc15b12555be5ecd96b413e7c93255ea54493f3a2f961e54d5226e1d4" }, + { file = "pyoxipng-9.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1faa7b25ba8e2268c42e00c2a06d36c225ffe5aa7bfe471ca5240d3f39af2c08" }, + { file = "pyoxipng-9.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:38a71bfb8e845133f149728ef51b2902c78a215dea18ebd212134163ae480f23" }, + { file = "pyoxipng-9.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f847f79ba09c0c184dab0f533fc64edbc4ec67a0a7433684dcd00860c2a96fc" }, + { file = "pyoxipng-9.0.0-cp310-none-win32.whl", hash = "sha256:6b4c48add03c2a4c71225dea75d64cbe7384eae60c65eace30435d76e2dad99b" }, + { file = "pyoxipng-9.0.0-cp310-none-win_amd64.whl", hash = "sha256:7392f64b93152df6a8738dded154441c5140b17f622d82e00a22129d22dece6f" }, + { file = "pyoxipng-9.0.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:fdfb392a01f6a7e319a6b45e77b72e213a20ea2d661fd2a8261e22b23f599d86" }, + { file = "pyoxipng-9.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72fe5559815a08ea80d887e6017433bb8da9c7cdcb578553e1591b7d6fb58000" }, + { file = "pyoxipng-9.0.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ae21122c7500088f8e009c821e71ca2788df52de769e1475c25bafdc16c10e53" }, + { file = "pyoxipng-9.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ddce80d1950704f794ef819a221e67ce2445f3bc5c1961ecb97b3610595bed2" }, + { file = "pyoxipng-9.0.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:273a5e4bb8a49e7be77ecce841cb7ff11eda413833a0403c788902af52a8a94c" }, + { file = "pyoxipng-9.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cc6c05a09a1e366121a002c9f75e4ff30d557139fd397ba755ddf46423d78af5" }, + { file = "pyoxipng-9.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596a1727c2ffa9880ec45b23c44a7940e70a6d29a8e8a57b5faa58da099e3ed3" }, + { file = "pyoxipng-9.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935439f538729660b0fcc2f5ca6321bc77e04140f3d2ad88627a9288c469bd49" }, + { file = "pyoxipng-9.0.0-cp311-none-win32.whl", hash = "sha256:a65d4379b11bdbee3ad020f3005c0f7458933b802d00abb79724ac4cf9b83ab5" }, + { file = "pyoxipng-9.0.0-cp311-none-win_amd64.whl", hash = "sha256:973ee04a0a55a5ae4104e17cbc7f03be1cca412c1ceb2dc2520316d5db524ca4" }, + { file = "pyoxipng-9.0.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b3d811b6d8574cc273dc2d65727afa787e768a3c8ded1283894499211bf5f59d" }, + { file = "pyoxipng-9.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0182e6ded60659a6b41aa6641fac6e405cd8decb638ebf54e28aa595ebda8bd1" }, + { file = "pyoxipng-9.0.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b41eed6202126575f140ec73421380965146e528466dd9b68556124da1660b5f" }, + { file = "pyoxipng-9.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:917d05340a06726d403bb385258fe4a18c492af984e214f0ca2a9b86798c34bc" }, + { file = "pyoxipng-9.0.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc13a2d9929b10e4dca7d1104192d2dbd45e871188f76711bb0174aa5ab91036" }, + { file = "pyoxipng-9.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6df8f223fd4c3efeb5caa68c41adf88f18c2e496b2f982fdc2087e39638601ad" }, + { file = "pyoxipng-9.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a75a68729eaf622c91a1258518bdee807347c587756d4c4882f6559681288f2" }, + { file = "pyoxipng-9.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e404378b908549f76cc1fe54cb9a393249881a98f47fecd87b66856e88627eba" }, + { file = "pyoxipng-9.0.0-cp312-none-win32.whl", hash = "sha256:dd4f2bb01081dddecec443e553ff41b3e11a9957e64c058b8fbbab8f77224d7b" }, + { file = "pyoxipng-9.0.0-cp312-none-win_amd64.whl", hash = "sha256:f767ee827841e6cd0780132a85fccec42e2fd2029657ccb2e188af85f0d02f47" }, + { file = "pyoxipng-9.0.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6436bc48933e6d63d6c97eaac83581630eab75fca6e8fa02507502beeddc15ef" }, + { file = "pyoxipng-9.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9c2491b085189c943fbd36e80c59fc24ed1cb8a8839ae41db0f6d6d86a00296" }, + { file = "pyoxipng-9.0.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f20e54c2811398046ff51d58a2b137716f87bf20bbe77b516167522b0e604b8e" }, + { file = "pyoxipng-9.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26df39584a2cada1e73ac697f9026aba174ab223b9ed925fe381e46044f647dd" }, + { file = "pyoxipng-9.0.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:93d466ddd87443d474b1ba7cf7016b9cf5629890fac70e8769c7a31c69487579" }, + { file = "pyoxipng-9.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c008de8a3e052ab4040058d5ca093d8ef646bc19f8ef6f3f111123219feffe27" }, + { file = "pyoxipng-9.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a271a65c8451e56c38a0e1463b6324e27597e5d6b666c791776616c5c7f8fe5c" }, + { file = "pyoxipng-9.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93c2db5e2c62444f014a33172db8a71524d435379ee611fbdbdbd773b95cc5dd" }, + { file = "pyoxipng-9.0.0-cp38-none-win32.whl", hash = "sha256:4c2c0cc420cf3cea4f5871de20775de61f2a87b70c32fe0a7cc9010e20260d37" }, + { file = "pyoxipng-9.0.0-cp38-none-win_amd64.whl", hash = "sha256:32d29bba4ed08e6002009c447f51d54de10acb545766d158062d313c418f931c" }, + { file = "pyoxipng-9.0.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:83c29410b8df4e5901c0dd28c81d30efa38344106bb261187341e0d1ae2cf5cc" }, + { file = "pyoxipng-9.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6df95f740c73e7477fb1ebd67f0713e4f45572d4ee28fd8bc21c51a0ecd8d9c6" }, + { file = "pyoxipng-9.0.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:11868fd380f5ddbdf7b6729302016e0ae1a0eca2820c91a2250dfd17f557a749" }, + { file = "pyoxipng-9.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e22d01633d95c89d292e0dd5a0ee0a2745b2d152e6dd4b924852890796b27f1" }, + { file = "pyoxipng-9.0.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7dd84d794fb63bb95ebcfcdb309f5f408d6a22a22a4b1d8238f2a886b1d0f5ed" }, + { file = "pyoxipng-9.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40b4dc92a65bfe40e038ca691bfa5d87695d3f8bd86e6593afb3ed3f5cb87f3d" }, + { file = "pyoxipng-9.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61186ef09eb6d4cd0fe1407da192fbf3bfd337ecbd784dbfa71636118e1f2847" }, + { file = "pyoxipng-9.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00672b46e0f3d67e6fc1f2dbbc840e932cc3e75932e1c10cffe8e92f4c9e6b2" }, + { file = "pyoxipng-9.0.0-cp39-none-win32.whl", hash = "sha256:95b9eb590da5ba3a4196518b9c3e4aa4939bf51bcd70191a686072c81a66b19f" }, + { file = "pyoxipng-9.0.0-cp39-none-win_amd64.whl", hash = "sha256:e465bdbadd4c76eff3b6bfb580ed3c84b2a71c6a70e4469adf30e63a847a58f4" }, + { file = "pyoxipng-9.0.0-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b8e5c285f3e8cbe0a74f243e66ccc0df5be9772389fac0ce60dc020da8005f09" }, + { file = "pyoxipng-9.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b26e312822d3bff17ccd5cd7322135b3684874526db880424a7357403f8e4bec" }, + { file = "pyoxipng-9.0.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a23043e196343a5a6b6a99324b6c528ab2240d39393066aa36c2014e0762bfc9" }, + { file = "pyoxipng-9.0.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5a2c6a37f2175bffa9c02321e33fca3bd81928f7d60c9afe204ea40d1bb5ab8" }, + { file = "pyoxipng-9.0.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4a4c28342d07239ecdfca5157d102432d063b2dd42d39f5639abda8140e0d08" }, + { file = "pyoxipng-9.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8144d161bcc5ce7fb1855b17a8d401d2d789672983a8093b9581dbe4c2fa99dd" }, + { file = "pyoxipng-9.0.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5065fafc4ca7e9b773a383c302d56240ce2fe29f49796d042d5c8450045593f3" }, + { file = "pyoxipng-9.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa97e8f596a9b0e371838ec5774b1bba7dc4ec59f45ee079cd839ec27b278535" }, + { file = "pyoxipng-9.0.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9406f10d5bf09d7c9bdae506b1a3812bf80cbcf8d1d86fff71b57ef9b61b808f" }, + { file = "pyoxipng-9.0.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:690b78a57d47da8b4fffbab1c0cd3873a3db07d7a0aa35bb8a4ad81a33bd765f" }, + { file = "pyoxipng-9.0.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ac674560a9ce14eada6bdad3208559c36453cc3a75cb9c09c981b763f1bc146" }, + { file = "pyoxipng-9.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7955f617b5f3abf2492f6577547e17708c5027a4a68678acc1716ff72127fac" }, + { file = "pyoxipng-9.0.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:435d7b7a6329569835b9e13c0eb424f39218811a7c6bff369b214b98e780b51e" }, + { file = "pyoxipng-9.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b4e5db38c5582ef6856a89eccbf3c606011ed5aa4594854d4ec531e556c4b23" }, + { file = "pyoxipng-9.0.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75568c861d043b1b7186c6617a684bea66a0844cf4fbd3f9dccc90bbacfe9a32" }, + { file = "pyoxipng-9.0.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4ea7af8b3587b35c84ea2faaff95f8d348babc5ce52ada63d7f4532c344eb06" }, + { file = "pyoxipng-9.0.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:059e22e00c37f74d0740d9dd0a7c09a33791438f839047ada85c017df321d4e2" }, + { file = "pyoxipng-9.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8809b7d4c4c77febfd0858f45bbbe993b74c5463a26a4526328e98d4bb61af9a" }, + { file = "pyoxipng-9.0.0.tar.gz", hash = "sha256:acbcda3f696957e84ddb421b95e23a1f9cf0f37418ef891793efaf2973c82863" }, +] + [[package]] name = "pyright" -version = "1.1.348" +version = "1.1.350" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.348-py3-none-any.whl", hash = "sha256:e7d4df504c4c082b5c3725a8c15fc3fda62da5d09fc77994baa77f359a1b62f2"}, - {file = "pyright-1.1.348.tar.gz", hash = "sha256:1c6994546f7ab130b9da8c357f8b2a99bef268b6d8ae2eae292bde66923aa7af"}, + { file = "pyright-1.1.350-py3-none-any.whl", hash = "sha256:f1dde6bcefd3c90aedbe9dd1c573e4c1ddbca8c74bf4fa664dd3b1a599ac9a66" }, + { file = "pyright-1.1.350.tar.gz", hash = "sha256:a8ba676de3a3737ea4d8590604da548d4498cc5ee9ee00b1a403c6db987916c6" }, ] [package.dependencies] @@ -478,25 +603,35 @@ dev = ["twine (>=3.4.1)"] [[package]] name = "pytest" -version = "7.4.4" +version = "8.0.1" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + { file = "pytest-8.0.1-py3-none-any.whl", hash = "sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca" }, + { file = "pytest-8.0.1.tar.gz", hash = "sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae" }, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +colorama = { version = "*", markers = "sys_platform == \"win32\"" } +exceptiongroup = { version = ">=1.0.0rc8", markers = "python_version < \"3.11\"" } iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +pluggy = ">=1.3.0,<2.0" +tomli = { version = ">=1.0.0", markers = "python_version < \"3.11\"" } [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +testing = [ + "argcomplete", + "attrs (>=19.2.0)", + "hypothesis (>=3.56)", + "mock", + "nose", + "pygments (>=2.7.2)", + "requests", + "setuptools", + "xmlschema", +] [[package]] name = "pytest-cov" @@ -505,16 +640,23 @@ description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, + { file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6" }, + { file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a" }, ] [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} +coverage = { version = ">=5.2.1", extras = ["toml"] } pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = [ + "fields", + "hunter", + "process-tests", + "pytest-xdist", + "six", + "virtualenv", +] [[package]] name = "pytest-gitignore" @@ -523,8 +665,8 @@ description = "py.test plugin to ignore the same files as git" optional = false python-versions = "*" files = [ - {file = "pytest-gitignore-1.3.tar.gz", hash = "sha256:e62a59ad42731504926a0f7b66b0dd829b587ac9c07e304834ae050c82aca1e3"}, - {file = "pytest_gitignore-1.3-py2.py3-none-any.whl", hash = "sha256:a7ed6d47c3aa10807ecb76c5b52716dce512bc704d68717b238c65f6b59b6cb1"}, + { file = "pytest-gitignore-1.3.tar.gz", hash = "sha256:e62a59ad42731504926a0f7b66b0dd829b587ac9c07e304834ae050c82aca1e3" }, + { file = "pytest_gitignore-1.3-py2.py3-none-any.whl", hash = "sha256:a7ed6d47c3aa10807ecb76c5b52716dce512bc704d68717b238c65f6b59b6cb1" }, ] [package.dependencies] @@ -537,8 +679,8 @@ description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + { file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86" }, + { file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" }, ] [package.dependencies] @@ -551,57 +693,57 @@ description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + { file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a" }, + { file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" }, + { file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938" }, + { file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d" }, + { file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515" }, + { file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290" }, + { file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924" }, + { file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d" }, + { file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007" }, + { file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab" }, + { file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d" }, + { file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc" }, + { file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673" }, + { file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b" }, + { file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741" }, + { file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34" }, + { file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28" }, + { file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9" }, + { file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef" }, + { file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0" }, + { file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4" }, + { file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54" }, + { file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df" }, + { file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47" }, + { file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98" }, + { file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c" }, + { file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd" }, + { file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585" }, + { file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa" }, + { file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3" }, + { file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27" }, + { file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3" }, + { file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c" }, + { file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba" }, + { file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867" }, + { file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595" }, + { file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5" }, + { file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696" }, + { file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735" }, + { file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6" }, + { file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206" }, + { file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62" }, + { file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8" }, + { file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859" }, + { file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6" }, + { file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0" }, + { file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c" }, + { file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5" }, + { file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c" }, + { file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486" }, + { file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43" }, ] [[package]] @@ -611,14 +753,14 @@ description = "Code Metrics in Python" optional = false python-versions = "*" files = [ - {file = "radon-6.0.1-py2.py3-none-any.whl", hash = "sha256:632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859"}, - {file = "radon-6.0.1.tar.gz", hash = "sha256:d1ac0053943a893878940fedc8b19ace70386fc9c9bf0a09229a44125ebf45b5"}, + { file = "radon-6.0.1-py2.py3-none-any.whl", hash = "sha256:632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859" }, + { file = "radon-6.0.1.tar.gz", hash = "sha256:d1ac0053943a893878940fedc8b19ace70386fc9c9bf0a09229a44125ebf45b5" }, ] [package.dependencies] -colorama = {version = ">=0.4.1", markers = "python_version > \"3.4\""} +colorama = { version = ">=0.4.1", markers = "python_version > \"3.4\"" } mando = ">=0.6,<0.8" -tomli = {version = ">=2.0.1", optional = true, markers = "extra == \"toml\""} +tomli = { version = ">=2.0.1", optional = true, markers = "extra == \"toml\"" } [package.extras] toml = ["tomli (>=2.0.1)"] @@ -630,23 +772,23 @@ description = "RAR archive reader for Python" optional = false python-versions = ">=3.6" files = [ - {file = "rarfile-4.1-py3-none-any.whl", hash = "sha256:17d7554c93c776ceae677e9d927051267d4c5eba38bf64b9cc89a415d9a5f901"}, - {file = "rarfile-4.1.tar.gz", hash = "sha256:db60b3b5bc1c4bdeb941427d50b606d51df677353385255583847639473eda48"}, + { file = "rarfile-4.1-py3-none-any.whl", hash = "sha256:17d7554c93c776ceae677e9d927051267d4c5eba38bf64b9cc89a415d9a5f901" }, + { file = "rarfile-4.1.tar.gz", hash = "sha256:db60b3b5bc1c4bdeb941427d50b606d51df677353385255583847639473eda48" }, ] [[package]] name = "ruamel-yaml" -version = "0.18.5" +version = "0.18.6" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = ">=3.7" files = [ - {file = "ruamel.yaml-0.18.5-py3-none-any.whl", hash = "sha256:a013ac02f99a69cdd6277d9664689eb1acba07069f912823177c5eced21a6ada"}, - {file = "ruamel.yaml-0.18.5.tar.gz", hash = "sha256:61917e3a35a569c1133a8f772e1226961bf5a1198bea7e23f06a0841dea1ab0e"}, + { file = "ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636" }, + { file = "ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b" }, ] [package.dependencies] -"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} +"ruamel.yaml.clib" = { version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\"" } [package.extras] docs = ["mercurial (>5.7)", "ryd"] @@ -659,99 +801,147 @@ description = "C version of reader, parser and emitter for ruamel.yaml derived f optional = false python-versions = ">=3.6" files = [ - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, - {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, - {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, + { file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d" }, + { file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462" }, + { file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412" }, + { file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f" }, + { file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334" }, + { file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d" }, + { file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d" }, + { file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31" }, + { file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069" }, + { file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248" }, + { file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b" }, + { file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe" }, + { file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899" }, + { file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9" }, + { file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7" }, + { file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb" }, + { file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1" }, + { file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2" }, + { file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92" }, + { file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62" }, + { file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9" }, + { file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d" }, + { file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa" }, + { file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b" }, + { file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed" }, + { file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942" }, + { file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875" }, + { file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6" }, + { file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3" }, + { file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7" }, + { file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3" }, + { file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b" }, + { file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675" }, + { file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615" }, + { file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337" }, + { file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1" }, + { file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91" }, + { file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28" }, + { file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d" }, + { file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe" }, + { file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312" }, + { file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001" }, + { file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf" }, + { file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c" }, + { file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5" }, + { file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b" }, + { file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880" }, + { file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5" }, + { file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15" }, + { file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512" }, ] [[package]] name = "ruff" -version = "0.1.14" +version = "0.2.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.14-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:96f76536df9b26622755c12ed8680f159817be2f725c17ed9305b472a757cdbb"}, - {file = "ruff-0.1.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab3f71f64498c7241123bb5a768544cf42821d2a537f894b22457a543d3ca7a9"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7060156ecc572b8f984fd20fd8b0fcb692dd5d837b7606e968334ab7ff0090ab"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a53d8e35313d7b67eb3db15a66c08434809107659226a90dcd7acb2afa55faea"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bea9be712b8f5b4ebed40e1949379cfb2a7d907f42921cf9ab3aae07e6fba9eb"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2270504d629a0b064247983cbc495bed277f372fb9eaba41e5cf51f7ba705a6a"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80258bb3b8909b1700610dfabef7876423eed1bc930fe177c71c414921898efa"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:653230dd00aaf449eb5ff25d10a6e03bc3006813e2cb99799e568f55482e5cae"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b3acc6c4e6928459ba9eb7459dd4f0c4bf266a053c863d72a44c33246bfdbf"}, - {file = "ruff-0.1.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6b3dadc9522d0eccc060699a9816e8127b27addbb4697fc0c08611e4e6aeb8b5"}, - {file = "ruff-0.1.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1c8eca1a47b4150dc0fbec7fe68fc91c695aed798532a18dbb1424e61e9b721f"}, - {file = "ruff-0.1.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:62ce2ae46303ee896fc6811f63d6dabf8d9c389da0f3e3f2bce8bc7f15ef5488"}, - {file = "ruff-0.1.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b2027dde79d217b211d725fc833e8965dc90a16d0d3213f1298f97465956661b"}, - {file = "ruff-0.1.14-py3-none-win32.whl", hash = "sha256:722bafc299145575a63bbd6b5069cb643eaa62546a5b6398f82b3e4403329cab"}, - {file = "ruff-0.1.14-py3-none-win_amd64.whl", hash = "sha256:e3d241aa61f92b0805a7082bd89a9990826448e4d0398f0e2bc8f05c75c63d99"}, - {file = "ruff-0.1.14-py3-none-win_arm64.whl", hash = "sha256:269302b31ade4cde6cf6f9dd58ea593773a37ed3f7b97e793c8594b262466b67"}, - {file = "ruff-0.1.14.tar.gz", hash = "sha256:ad3f8088b2dfd884820289a06ab718cde7d38b94972212cc4ba90d5fbc9955f3"}, + { file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6" }, + { file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39" }, + { file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73" }, + { file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba" }, + { file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c" }, + { file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e" }, + { file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca" }, + { file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001" }, + { file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3" }, + { file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726" }, + { file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e" }, + { file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e" }, + { file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9" }, + { file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325" }, + { file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d" }, + { file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd" }, + { file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d" }, ] [[package]] name = "setuptools" -version = "69.0.3" +version = "69.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, - {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, + { file = "setuptools-69.1.0-py3-none-any.whl", hash = "sha256:c054629b81b946d63a9c6e732bc8b2513a7c3ea645f11d0139a2191d735c60c6" }, + { file = "setuptools-69.1.0.tar.gz", hash = "sha256:850894c4195f09c4ed30dba56213bf7c3f21d86ed6bdaafb5df5972593bfc401" }, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = [ + "furo", + "jaraco.packaging (>=9.3)", + "jaraco.tidelift (>=1.4)", + "pygments-github-lexers (==0.0.5)", + "rst.linker (>=1.9)", + "sphinx (<7.2.5)", + "sphinx (>=3.5)", + "sphinx-favicon", + "sphinx-inline-tabs", + "sphinx-lint", + "sphinx-notfound-page (>=1,<2)", + "sphinx-reredirects", + "sphinxcontrib-towncrier", +] +testing = [ + "build[virtualenv]", + "filelock (>=3.4.0)", + "flake8-2020", + "ini2toml[lite] (>=0.9)", + "jaraco.develop (>=7.21)", + "jaraco.envs (>=2.2)", + "jaraco.path (>=3.2.0)", + "pip (>=19.1)", + "pytest (>=6)", + "pytest-checkdocs (>=2.4)", + "pytest-cov", + "pytest-enabler (>=2.2)", + "pytest-home (>=0.5)", + "pytest-mypy (>=0.9.1)", + "pytest-perf", + "pytest-ruff (>=0.2.1)", + "pytest-timeout", + "pytest-xdist", + "tomli-w (>=1.0.0)", + "virtualenv (>=13.0.0)", + "wheel", +] +testing-integration = [ + "build[virtualenv] (>=1.0.3)", + "filelock (>=3.4.0)", + "jaraco.envs (>=2.2)", + "jaraco.path (>=3.2.0)", + "packaging (>=23.1)", + "pytest", + "pytest-enabler", + "pytest-xdist", + "tomli", + "virtualenv (>=13.0.0)", + "wheel", +] [[package]] name = "six" @@ -760,8 +950,8 @@ description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + { file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" }, + { file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926" }, ] [[package]] @@ -771,8 +961,8 @@ description = "ANSI color formatting for output in terminal" optional = false python-versions = ">=3.8" files = [ - {file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"}, - {file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"}, + { file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63" }, + { file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a" }, ] [package.extras] @@ -785,19 +975,19 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.7" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + { file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc" }, + { file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" }, ] [[package]] name = "treestamps" -version = "0.4.4" +version = "1.0.1" description = "Create timestamp records for recursive operations on directory trees." optional = false -python-versions = ">=3.9,<4.0" +python-versions = ">=3.10,<4.0" files = [ - {file = "treestamps-0.4.4-py3-none-any.whl", hash = "sha256:9ea74fc41a8f0db3c6ee9152f186530a10f1abf2874a7301731694fac8e6a4a2"}, - {file = "treestamps-0.4.4.tar.gz", hash = "sha256:be1d2f00598ce1b2545d9a85de2c869bea21b5ee29ab71a6daa294bca6c3d994"}, + { file = "treestamps-1.0.1-py3-none-any.whl", hash = "sha256:d837fdb039470430c424a9e734acdebc0f93723968b96f3f6de4f54b2928897f" }, + { file = "treestamps-1.0.1.tar.gz", hash = "sha256:6541202b3b750bd24c0266b4dd6d149528b7bb0a82678b15ed8635dc626d0054" }, ] [package.dependencies] @@ -811,8 +1001,8 @@ description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.8.19.20240106.tar.gz", hash = "sha256:1f8db221c3b98e6ca02ea83a58371b22c374f42ae5bbdf186db9c9a76581459f"}, - {file = "types_python_dateutil-2.8.19.20240106-py3-none-any.whl", hash = "sha256:efbbdc54590d0f16152fa103c9879c7d4a00e82078f6e2cf01769042165acaa2"}, + { file = "types-python-dateutil-2.8.19.20240106.tar.gz", hash = "sha256:1f8db221c3b98e6ca02ea83a58371b22c374f42ae5bbdf186db9c9a76581459f" }, + { file = "types_python_dateutil-2.8.19.20240106-py3-none-any.whl", hash = "sha256:efbbdc54590d0f16152fa103c9879c7d4a00e82078f6e2cf01769042165acaa2" }, ] [[package]] @@ -822,14 +1012,14 @@ description = "Find dead code" optional = false python-versions = ">=3.8" files = [ - {file = "vulture-2.11-py2.py3-none-any.whl", hash = "sha256:12d745f7710ffbf6aeb8279ba9068a24d4e52e8ed333b8b044035c9d6b823aba"}, - {file = "vulture-2.11.tar.gz", hash = "sha256:f0fbb60bce6511aad87ee0736c502456737490a82d919a44e6d92262cb35f1c2"}, + { file = "vulture-2.11-py2.py3-none-any.whl", hash = "sha256:12d745f7710ffbf6aeb8279ba9068a24d4e52e8ed333b8b044035c9d6b823aba" }, + { file = "vulture-2.11.tar.gz", hash = "sha256:f0fbb60bce6511aad87ee0736c502456737490a82d919a44e6d92262cb35f1c2" }, ] [package.dependencies] -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomli = { version = ">=1.1.0", markers = "python_version < \"3.11\"" } [metadata] lock-version = "2.0" -python-versions = "^3.9" -content-hash = "15eacd0cbd1f33cb4ac8fd7dca8bdf29300c4c384cbe5105bc205543ab8b85ae" +python-versions = "^3.10" +content-hash = "30c0f85250d8a966baa8df7ec7be8b27e59c94a50bdd04268e4b4d558b27dec5" diff --git a/pyproject.toml b/pyproject.toml index afc61de..bb50350 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,14 @@ +[project] +name = "picopt" +requires-python = ">=3.10" + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry] name = "picopt" -version = "3.3.7" +version = "4.0.0rc0" description = "A multi format lossless image optimizer that uses external tools" license = "GPL-3.0-only" authors = ["AJ Slater "] @@ -27,26 +31,28 @@ packages = [{ include = "picopt" }, { include = "tests", format = "sdist" }] include = ["NEWS.md"] [tool.poetry.dependencies] -python = "^3.9" +python = "^3.10" confuse = "^2.0.0" humanize = "^4.0.0" +piexif = "^1.1.3" +pyoxipng = "^9.0.0" python-dateutil = "^2.8" rarfile = "^4.0" "ruamel.yaml" = "^0.18.5" termcolor = "^2.0.1" -treestamps = "^0.4.4" -Pillow = ">=9.4,<11.0" +treestamps = "^1.0.1" +Pillow = ">=9.5,<11.0" [tool.poetry.group.dev.dependencies] codespell = "^2.1.0" coverage = { extras = ["toml"], version = "^7.0" } neovim = "^0.3.1" pyright = "^1.1.237" -pytest = "^7.0.0" +pytest = "^8.0.0" pytest-cov = "^4.0" pytest-gitignore = "^1.3" radon = { version = "^6.0.1", extras = ["toml"] } -ruff = "^0.1.13" +ruff = "^0.2.0" types-python-dateutil = "^2.8.0" vulture = "^2.1" @@ -54,8 +60,8 @@ vulture = "^2.1" picopt = "picopt.cli:main" [tool.poetry.urls] -"Source" = "https://github.com/ajslater/picopt" -"Issues" = "https://github.com/ajslater/picopt/issues" +Source = "https://github.com/ajslater/picopt" +Issues = "https://github.com/ajslater/picopt/issues" [tool.codespell] skip = ".git,.mypy_cache,.pytest_cache,.ruff_cache,.venv,*~,./dist,./node_modules,./package-lock.json,./poetry.lock,./test-results" @@ -77,33 +83,16 @@ omit = [ "dist/*", "node_modules/*", "test-results/*", - "typings/*" + "typings/*", ] [tool.coverage.html] directory = "test-results/coverage" [tool.pytest.ini_options] +addopts = "--junitxml=test-results/pytest/junit.xml -ra --strict-markers --cov --cov-append --cov-report=html --cov-report=term" junit_family = "xunit2" -addopts = """ - --junitxml=test-results/pytest/junit.xml - -ra - --strict-markers - --cov - --cov-append - --cov-report=html - --cov-report=term - --ignore=.git - --ignore=.mypy_cache - --ignore=.pytest_cache - --ignore=.venv - --ignore=dist - --ignore=node_modules - --ignore=test-results - --ignore=typings - --ignore=vulture_whitelist.py - --ignore-glob=*__pycache__* -""" +testpaths = "tests" [tool.pyright] exclude = [ @@ -119,41 +108,100 @@ exclude = [ "poetry.lock", "test-results", "typings", - "vulture_whitelist.py" + "vulture_whitelist.py", ] -useLibraryCodeForTypes = true -reportMissingImports = true +pythonVersion = "3.10" +pythonPlatform = "All" reportImportCycles = true +reportMisingImports = true +useLibraryCodeForTypes = true [tool.radon] exclude = "*~,.git/*,.mypy_cache/*,.pytest_cache/*,.ruff_cache/*,.venv/*,__pycache__/*,dist/*,node_modules/*,test-results/*,typings/*" [tool.ruff] extend-exclude = ["cache", "typings"] -extend-ignore = ["S101", "D203", "D213", - # Format ignores - "W191", "E501", "E111", "E114", "E117", "D206", "D300", "Q000", "Q001", - "Q002", "Q003", "COM812", "COM819", "ISC001", "ISC002" +target-version = "py310" + +[tool.ruff.lint] +extend-ignore = [ + "S101", + "D203", + "D213", + "W191", + "E501", + "E111", + "E114", + "E117", + "D206", + "D300", + "Q000", + "Q001", + "Q002", + "Q003", + "COM812", + "COM819", + "ISC001", + "ISC002", ] -extend-select = ["A", "ARG", "B", "B9", "C", "C4", "C90", "D", "DJ", - "DTZ", "E", "EM", "EXE", "F", "I", "ICN", "INP", "ISC", "PIE", "PL", - "PT", "PTH", "PYI", "Q", "N", "RET", "RSE", "RUF", "S", "SIM", "SLF", - "T10", "T20", "TCH", "TID", "TRY", "UP", "W", "YTT" - # "ANN", "ERA", "COM" +extend-select = [ + "A", + "ARG", + "B", + "B9", + "C", + "C4", + "C90", + "D", + "DJ", + "DTZ", + "E", + "EM", + "EXE", + "F", + "I", + "ICN", + "INP", + "ISC", + "PIE", + "PL", + "PT", + "PTH", + "PYI", + "Q", + "N", + "RET", + "RSE", + "RUF", + "S", + "SIM", + "SLF", + "T10", + "T20", + "TCH", + "TID", + "TRY", + "UP", + "W", + "YTT", ] external = ["V101"] -# format = "grouped" -# show-source = true -target-version = "py39" task-tags = ["TODO", "FIXME", "XXX", "http", "HACK"] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "tests/*" = ["SLF001", "T201", "T203"] -[tool.ruff.pycodestyle] +[tool.ruff.lint.pycodestyle] ignore-overlong-task-comments = true [tool.vulture] -exclude = [".git/", ".mypy_cache/", ".pytest_cache", ".ruff_cache", ".venv", "*__pycache__*"] +exclude = [ + ".git/", + ".mypy_cache/", + ".pytest_cache", + ".ruff_cache", + ".venv", + "*__pycache__*", +] min_confidence = 61 sort_by_size = true diff --git a/tests/integration/base_test_images.py b/tests/integration/base_test_images.py new file mode 100644 index 0000000..d58f055 --- /dev/null +++ b/tests/integration/base_test_images.py @@ -0,0 +1,29 @@ +"""Base class for testing images.""" +import shutil +from abc import ABC +from collections.abc import Mapping +from pathlib import Path +from types import MappingProxyType + +from tests import IMAGES_DIR, get_test_dir + + +class BaseTestImagesDir(ABC): + """Test images dir.""" + + FNS: Mapping = MappingProxyType({}) + TMP_ROOT: Path = get_test_dir() + + def setup_method(self) -> None: + """Set up method.""" + self.teardown_method() + self.TMP_ROOT.mkdir(parents=True) + for fn, sizes in self.FNS.items(): + src = IMAGES_DIR / fn + dest = self.TMP_ROOT / fn + shutil.copy(src, dest) + assert dest.stat().st_size == sizes[0] + + def teardown_method(self) -> None: + """Tear down method.""" + shutil.rmtree(self.TMP_ROOT, ignore_errors=True) diff --git a/tests/integration/test_containers.py b/tests/integration/test_containers.py index 97d550d..871d1ab 100644 --- a/tests/integration/test_containers.py +++ b/tests/integration/test_containers.py @@ -1,6 +1,6 @@ """Test comic format.""" import shutil -from platform import system +from types import MappingProxyType from zipfile import ZipFile from picopt import PROGRAM_NAME, cli @@ -10,22 +10,15 @@ TMP_ROOT = get_test_dir() SRC_CBZ = CONTAINER_DIR / "test_cbz.cbz" -if system() == "Darwin": - FNS = { - "test_cbz.cbz": (93408, 84493, ("cbz", 93408)), - "test_cbr.cbr": (93725, 93725, ("cbz", 84506)), - "test_rar.rar": (93675, 93675, ("zip", 84493)), - "test_zip.zip": (2974, 2921, ("zip", 2974)), - "igp-twss.epub": (292448, 281248, ("epub", 292448)), - } -else: - FNS = { - "test_cbz.cbz": (93408, 84493, ("cbz", 93408)), - "test_cbr.cbr": (93725, 93725, ("cbz", 84506)), - "test_rar.rar": (93675, 93675, ("zip", 84493)), - "test_zip.zip": (2974, 2921, ("zip", 2974)), - "igp-twss.epub": (292448, 281248, ("epub", 292448)), +FNS = MappingProxyType( + { + "test_cbz.cbz": (93408, 84544, ("cbz", 84544)), + "test_cbr.cbr": (93725, 93725, ("cbz", 88048)), + "test_rar.rar": (93675, 93675, ("zip", 88035)), + "test_zip.zip": (7783, 7015, ("zip", 7015)), + "igp-twss.epub": (292448, 285439, ("epub", 285439)), } +) EPUB_FN = "igp-twss.epub" BMP_FN = "OPS/test_bmp.bmp" @@ -62,7 +55,7 @@ def test_containers_noop(self) -> None: def test_containers_no_convert(self) -> None: """Test containers no convert.""" - args = (PROGRAM_NAME, "-rx", "CBZ,ZIP,EPUB", "-c WEBP", str(TMP_ROOT)) + args = (PROGRAM_NAME, "-rx", "GIF,CBZ,ZIP,EPUB", "-c WEBP", str(TMP_ROOT)) cli.main(args) for name, sizes in FNS.items(): path = TMP_ROOT / name @@ -74,8 +67,16 @@ def test_containers_no_convert(self) -> None: def test_containers_convert_to_zip(self) -> None: """Test containers convert to zip.""" - args = (PROGRAM_NAME, "-rc", "ZIP,CBZ", str(TMP_ROOT)) + args = ( + PROGRAM_NAME, + "-rx", + "ZIP,CBZ,RAR,CBR,EPUB", + "-c", + "ZIP,CBZ", + str(TMP_ROOT), + ) cli.main(args) for name, sizes in FNS.items(): + print(name) path = (TMP_ROOT / name).with_suffix("." + sizes[2][0]) assert path.stat().st_size == sizes[2][1] diff --git a/tests/integration/test_images_dir.py b/tests/integration/test_images_dir.py index 75d9288..529edac 100644 --- a/tests/integration/test_images_dir.py +++ b/tests/integration/test_images_dir.py @@ -1,113 +1,138 @@ """Test comic format.""" import platform -import shutil +from types import MappingProxyType from picopt import PROGRAM_NAME, cli -from tests import IMAGES_DIR, get_test_dir +from tests import get_test_dir +from tests.integration.base_test_images import BaseTestImagesDir __all__ = () -TMP_ROOT = get_test_dir() FNS = { "07themecamplist.pdf": (93676, 93676, ("pdf", 93676), ("pdf", 93676)), - "test_animated_gif.gif": (16383, 16358, ("gif", 16358), ("webp", 11866)), - "test_animated_png.png": (63435, 63435, ("png", 63435), ("webp", 43064)), - "test_animated_webp.webp": (13610, 13610, ("webp", 13610), ("webp", 13610)), - "test_png.png": (7967, 4379, ("png", 4379), ("webp", 3870)), + "test_animated_gif.gif": (16383, 16358, ("png", 13389), ("webp", 11894)), + "test_animated_png.png": (63435, 63435, ("png", 63435), ("webp", 53430)), + "test_animated_webp.webp": (13610, 12178, ("webp", 12178), ("webp", 12178)), + "test_png.png": (7967, 4149, ("png", 4149), ("webp", 3870)), "test_pre-optimized_jpg.jpg": ( 22664, 22664, ("jpg", 22664), ("jpg", 22664), ), + "test_svg.svg": (5393, 4871, ("svg", 4871), ("svg", 4871)), "test_txt.txt": (6, 6, ("txt", 6), ("txt", 6)), "test_webp_lossless.webp": (5334, 3870, ("webp", 3870), ("webp", 3870)), "test_webp_lossless_pre-optimized.webp": ( - 4036, - 4036, - ("webp", 4036), - ("webp", 4036), + 3798, + 3798, + ("webp", 3798), + ("webp", 3798), ), - "mri.tif": (230578, 230578, ("tif", 230578), ("webp", 83822)), + "mri.tif": (230578, 230578, ("png", 131743), ("webp", 114740)), "test_webp_lossy.webp": (2764, 2764, ("webp", 2764), ("webp", 2764)), - "test_webp_lossy_pre-optimized.webp": ( - 1508, - 1508, - ("webp", 1508), - ("webp", 1508), - ), + "test_bmp.bmp": (141430, 141430, ("png", 67236), ("webp", 47524)), + "test_png_16rgba.png": (3435, 2090, ("png", 2090), ("webp", 1142)), + "test_pnm.pnm": (27661, 27661, ("png", 15510), ("webp", 12808)), + "test_jpg.jpg": (97373, 87913, ("jpg", 87913), ("jpg", 87913)), } if platform.system() == "Darwin": FNS.update( { - "test_bmp.bmp": (141430, 141430, ("png", 67215), ("webp", 30814)), - "test_gif.gif": (138952, 138944, ("png", 112467), ("webp", 107924)), - "test_jpg.jpg": (97373, 87913, ("jpg", 87913), ("jpg", 87913)), - "test_png_16rgba.png": (3435, 2870, ("png", 2870), ("webp", 728)), - "test_pnm.pnm": (27661, 27661, ("png", 15510), ("webp", 6852)), + "test_gif.gif": (138952, 138944, ("png", 112137), ("webp", 107924)), "test_pre-optimized_png.png": ( 256572, 256572, ("png", 256572), - ("webp", 107984), + ("webp", 197726), ), - "eight.tif": (59640, 59640, ("png", 30564), ("webp", 19436)), + "eight.tif": (59640, 59640, ("png", 30585), ("webp", 24974)), } ) else: FNS.update( { - "test_bmp.bmp": (141430, 141430, ("png", 67215), ("webp", 30774)), - "test_gif.gif": (138952, 138944, ("png", 112467), ("webp", 107952)), - "test_jpg.jpg": (97373, 87913, ("jpg", 87913), ("jpg", 87913)), - "test_png_16rgba.png": (3435, 2870, ("png", 2870), ("webp", 728)), - "test_pnm.pnm": (27661, 27661, ("png", 15510), ("webp", 6852)), + "test_gif.gif": (138952, 138944, ("png", 112137), ("webp", 107952)), "test_pre-optimized_png.png": ( 256572, 256572, ("png", 256572), - ("webp", 108254), + ("webp", 197680), ), - "eight.tif": (59640, 59640, ("png", 30564), ("webp", 19440)), + "eight.tif": (59640, 59640, ("png", 30585), ("webp", 24982)), } ) +NEAR_LOSSLESS_FNS = MappingProxyType( + { + "test_png_16rgba.png": (3435, 2090, ("png", 2090), ("webp", 728)), + "test_webp_lossless.webp": (5334, 3870, ("webp", 3870), ("webp", 2044)), + "test_webp_lossless_pre-optimized.webp": ( + 3798, + 3798, + ("webp", 3798), + ("webp", 3798), + ), + } +) -class TestImagesDir: - """Test images dir.""" - def setup_method(self) -> None: - """Set up method.""" - self.teardown_method() - shutil.copytree(IMAGES_DIR, TMP_ROOT) - for name, sizes in FNS.items(): - path = TMP_ROOT / name - assert path.stat().st_size == sizes[0] +class TestImagesDir(BaseTestImagesDir): + """Test images dir.""" - def teardown_method(self) -> None: - """Tear down method.""" - if TMP_ROOT.exists(): - shutil.rmtree(TMP_ROOT) + FNS = FNS + TMP_ROOT = get_test_dir() def test_no_convert(self) -> None: """Test no convert.""" - args = (PROGRAM_NAME, "-rvv", str(TMP_ROOT)) + args = (PROGRAM_NAME, "-rvvx SVG", str(self.TMP_ROOT)) cli.main(args) - for name, sizes in FNS.items(): - path = TMP_ROOT / name + for name, sizes in self.FNS.items(): + path = self.TMP_ROOT / name assert path.stat().st_size == sizes[1] def test_convert_to_png(self) -> None: """Test convert to PNG.""" - args = (PROGRAM_NAME, "-rvvx", "TIFF", "-c", "PNG", str(TMP_ROOT)) + args = ( + PROGRAM_NAME, + "-rvvx", + "BMP,PPM,SVG,TIFF", + "-c", + "PNG", + str(self.TMP_ROOT), + ) cli.main(args) - for name, sizes in FNS.items(): - path = (TMP_ROOT / name).with_suffix("." + sizes[2][0]) + for name, sizes in self.FNS.items(): + path = (self.TMP_ROOT / name).with_suffix("." + sizes[2][0]) assert path.stat().st_size == sizes[2][1] def test_convert_to_webp(self) -> None: """Test convert to WEBP.""" - args = (PROGRAM_NAME, "-rvvx", "TIFF", "-c", "WEBP", str(TMP_ROOT)) + args = ( + PROGRAM_NAME, + "-rvvx", + "BMP,PPM,SVG,TIFF", + "-c", + "WEBP", + str(self.TMP_ROOT), + ) + cli.main(args) + for name, sizes in self.FNS.items(): + path = (self.TMP_ROOT / name).with_suffix("." + sizes[3][0]) + assert path.stat().st_size == sizes[3][1] + + +class TestNearLosslessImageDir(BaseTestImagesDir): + FNS = NEAR_LOSSLESS_FNS + + def test_convert_to_webp_near_lossless(self) -> None: + """Test convert to WEBP.""" + args = ( + PROGRAM_NAME, + "-rvvvnc", + "WEBP", + str(self.TMP_ROOT), + ) cli.main(args) - for name, sizes in FNS.items(): - path = (TMP_ROOT / name).with_suffix("." + sizes[3][0]) + for name, sizes in self.FNS.items(): + path = (self.TMP_ROOT / name).with_suffix("." + sizes[3][0]) assert path.stat().st_size == sizes[3][1] diff --git a/tests/integration/test_mpo.py b/tests/integration/test_mpo.py new file mode 100644 index 0000000..8bd6e12 --- /dev/null +++ b/tests/integration/test_mpo.py @@ -0,0 +1,65 @@ +"""Test comic format.""" +from types import MappingProxyType + +from picopt import PROGRAM_NAME, cli +from tests import get_test_dir +from tests.integration.base_test_images import BaseTestImagesDir + +__all__ = () + +FNS = MappingProxyType( + { + "test_pre-optimized_jpg.jpg": ( + 22664, + 22664, + ("jpg", 22664), + ("jpg", 22664), + ), + "test_mpo.jpeg": ( + 7106225, + 7106225, + ("jpeg", 5963686), + ("jpeg", 6450372), + ), + } +) + + +class TestMPO(BaseTestImagesDir): + """Test images dir.""" + + TMP_ROOT = get_test_dir() + FNS = FNS + + def test_no_convert(self) -> None: + """Test no convert.""" + args = (PROGRAM_NAME, "-rvvv", str(self.TMP_ROOT)) + cli.main(args) + for name, sizes in self.FNS.items(): + path = self.TMP_ROOT / name + assert path.stat().st_size == sizes[1] + + def test_convert_to_jpeg(self) -> None: + """Test convert to PNG.""" + args = (PROGRAM_NAME, "-rvvvx", "MPO", "-c", "JPEG", str(self.TMP_ROOT)) + cli.main(args) + for name, sizes in self.FNS.items(): + path = (self.TMP_ROOT / name).with_suffix("." + sizes[2][0]) + assert path.stat().st_size == sizes[2][1] + + def test_convert_to_jpeg_no_local(self) -> None: + """Test convert to PNG.""" + args = ( + PROGRAM_NAME, + "-rvvvx", + "MPO", + "-c", + "JPEG", + "-D", + "mozjpeg,jpegtran", + str(self.TMP_ROOT), + ) + cli.main(args) + for name, sizes in self.FNS.items(): + path = (self.TMP_ROOT / name).with_suffix("." + sizes[3][0]) + assert path.stat().st_size == sizes[3][1] diff --git a/tests/integration/test_old_timestamps.py b/tests/integration/test_old_timestamps.py index b418b72..9832cc1 100644 --- a/tests/integration/test_old_timestamps.py +++ b/tests/integration/test_old_timestamps.py @@ -17,9 +17,10 @@ DEFAULT_CONFIG = { "bigger": False, "convert_to": [], - "formats": ["GIF", "PNG", "JPEG", "WEBP"], + "formats": ["GIF", "PNG", "JPEG", "SVG", "WEBP"], "keep_metadata": True, "ignore": [], + "near_lossless": False, "recurse": True, "symlinks": True, } diff --git a/tests/integration/test_one_container.py b/tests/integration/test_one_container.py index dabf12a..7f32aec 100644 --- a/tests/integration/test_one_container.py +++ b/tests/integration/test_one_container.py @@ -10,10 +10,10 @@ FN = "test_zip.zip" SRC_CBZ = CONTAINER_DIR / FN -FNS = {FN: (2974, 2921)} if system() == "Darwin" else {FN: (2974, 2921)} +FNS = {FN: (7783, 7015)} if system() == "Darwin" else {FN: (7783, 7015)} -class TestContainersDir: +class TestOneContainer: """Test containers dir.""" def setup_method(self) -> None: diff --git a/tests/integration/test_preserve.py b/tests/integration/test_preserve.py new file mode 100644 index 0000000..16e22f3 --- /dev/null +++ b/tests/integration/test_preserve.py @@ -0,0 +1,50 @@ +"""Test comic format.""" +from types import MappingProxyType + +from picopt import PROGRAM_NAME, cli +from tests import get_test_dir +from tests.integration.base_test_images import BaseTestImagesDir + +__all__ = () + +FNS = MappingProxyType( + { + "test_pre-optimized_jpg.jpg": ( + 22664, + 22664, + ("jpg", 22664), + ), + "test_jpg.jpg": ( + 97373, + 87913, + ("jpg", 87913), + ), + } +) +STATS = ("uid", "gid", "mode", "mtime_ns") + + +class TestPreserve(BaseTestImagesDir): + """Test images dir.""" + + TMP_ROOT = get_test_dir() + FNS = FNS + + def test_preserve(self) -> None: + """Test no convert.""" + stats = {} + for name in self.FNS: + path = self.TMP_ROOT / name + stats[path] = path.stat() + args = (PROGRAM_NAME, "-rpvvv", str(self.TMP_ROOT)) + cli.main(args) + for name, sizes in self.FNS.items(): + path = self.TMP_ROOT / name + old_stat = stats[path] + new_stat = path.stat() + assert new_stat.st_size == sizes[1] + print(name) + for stat_name_suffix in STATS: + stat_name = "st_" + stat_name_suffix + print(f"\t{stat_name}") + assert getattr(old_stat, stat_name) == getattr(new_stat, stat_name) diff --git a/tests/integration/test_timestamps.py b/tests/integration/test_timestamps.py index b2e4a0c..4d2b060 100644 --- a/tests/integration/test_timestamps.py +++ b/tests/integration/test_timestamps.py @@ -1,7 +1,7 @@ """Test comic format.""" -import platform import shutil from datetime import datetime, timezone +from types import MappingProxyType from ruamel.yaml import YAML @@ -18,14 +18,15 @@ WAL_FN = f".{PROGRAM_NAME}_treestamps.wal.yaml" WAL_PATH = TMP_ROOT / WAL_FN -FNS = {FN: (97373, 87913)} if platform.system() == "Darwin" else {FN: (97373, 87913)} +FNS = MappingProxyType({FN: (97373, 87913)}) DEFAULT_CONFIG = { "bigger": False, "convert_to": [], - "formats": ["GIF", "PNG", "JPEG", "WEBP"], + "formats": ["GIF", "JPEG", "PNG", "SVG", "WEBP"], "keep_metadata": True, "ignore": [], + "near_lossless": False, "recurse": True, "symlinks": True, } @@ -75,28 +76,28 @@ def _write_timestamp(path, ts=None, config=None): def test_no_timestamp(self) -> None: """Test no timestamp.""" - args = (PROGRAM_NAME, "-rtvvv", TMP_FN) + args = (PROGRAM_NAME, "-rtvvvx SVG", TMP_FN) cli.main(args) self._assert_sizes(1) def test_timestamp(self): """Test timestamp.""" self._write_timestamp(TMP_FN) - args = (PROGRAM_NAME, "-rtvvv", TMP_FN) + args = (PROGRAM_NAME, "-rtvvvx SVG", TMP_FN) cli.main(args) self._assert_sizes(0) def test_different_config(self): """Test different config.""" self._write_timestamp(FN) - args = (PROGRAM_NAME, "-brtvvv", TMP_FN) + args = (PROGRAM_NAME, "-brtvvvx SVG", TMP_FN) cli.main(args) self._assert_sizes(1) def test_timestamp_dir(self): """Test timestamp dir.""" self._write_timestamp(TMP_ROOT) - args = (PROGRAM_NAME, "-rtvvv", TMP_FN) + args = (PROGRAM_NAME, "-rtvvvx SVG", TMP_FN) cli.main(args) self._assert_sizes(0) @@ -112,7 +113,7 @@ def test_timestamp_children(self): """Test timestamp children.""" tmp_child_dir = self._setup_child_dir() self._write_timestamp(tmp_child_dir) - args = (PROGRAM_NAME, "-rtvvv", str(TMP_ROOT)) + args = (PROGRAM_NAME, "-rtvvvx SVG", str(TMP_ROOT)) cli.main(args) self._assert_sizes(0, tmp_child_dir) @@ -121,7 +122,7 @@ def test_timestamp_parents(self): tmp_child_dir = self._setup_child_dir() self._write_timestamp(TMP_ROOT) - args = (PROGRAM_NAME, "-rtvvv", str(tmp_child_dir)) + args = (PROGRAM_NAME, "-rtvvvx SVG", str(tmp_child_dir)) cli.main(args) self._assert_sizes(0, tmp_child_dir) assert (tmp_child_dir / TIMESTAMPS_FN).exists() @@ -129,7 +130,7 @@ def test_timestamp_parents(self): def test_journal_cleanup(self) -> None: """Test journal cleanup.""" - args = (PROGRAM_NAME, "-rtvvv", TMP_FN) + args = (PROGRAM_NAME, "-rtvvvx SVG", TMP_FN) cli.main(args) assert not WAL_PATH.exists() @@ -153,6 +154,6 @@ def _write_wal(path, ts=None, config=None): def test_timestamp_read_journal(self): """Test timestamp read journal.""" self._write_wal(TMP_FN) - args = (PROGRAM_NAME, "-rtvvv", TMP_FN) + args = (PROGRAM_NAME, "-rtvvvx SVG", TMP_FN) cli.main(args) self._assert_sizes(0) diff --git a/tests/test_files/containers/test_zip.zip b/tests/test_files/containers/test_zip.zip index 2a7053b..36ab23d 100644 Binary files a/tests/test_files/containers/test_zip.zip and b/tests/test_files/containers/test_zip.zip differ diff --git a/tests/test_files/images/test_mpo.jpeg b/tests/test_files/images/test_mpo.jpeg new file mode 100644 index 0000000..5892e4f Binary files /dev/null and b/tests/test_files/images/test_mpo.jpeg differ diff --git a/tests/test_files/images/test_svg.svg b/tests/test_files/images/test_svg.svg new file mode 100644 index 0000000..c8562e4 --- /dev/null +++ b/tests/test_files/images/test_svg.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_files/images/test_webp_lossless_pre-optimized.webp b/tests/test_files/images/test_webp_lossless_pre-optimized.webp index 580b57f..94811e0 100644 Binary files a/tests/test_files/images/test_webp_lossless_pre-optimized.webp and b/tests/test_files/images/test_webp_lossless_pre-optimized.webp differ diff --git a/tests/test_files/images/test_webp_lossy_pre-optimized.webp b/tests/test_files/images/test_webp_lossy_pre-optimized.webp deleted file mode 100644 index 96436dd..0000000 Binary files a/tests/test_files/images/test_webp_lossy_pre-optimized.webp and /dev/null differ diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index e31f4da..63126ce 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -3,7 +3,7 @@ import sys from picopt import cli -from picopt.handlers.zip import CBZ, Zip +from picopt.handlers.zip import Cbz, Zip from tests import IMAGES_DIR, get_test_dir __all__ = () # hides module from pydocstring @@ -34,8 +34,8 @@ def test_get_arguments(self) -> None: assert arguments.verbose == 0 assert arguments.convert_to == ("PNG", "WEBP") assert arguments.formats is None - assert arguments.computed.extra_formats == ( - CBZ.OUTPUT_FORMAT_STR, + assert arguments.extra_formats == ( + Cbz.OUTPUT_FORMAT_STR, Zip.OUTPUT_FORMAT_STR, ) assert arguments.symlinks diff --git a/tests/unit/test_png_bit_depth.py b/tests/unit/test_png_bit_depth.py index 3a4ff55..a33007f 100644 --- a/tests/unit/test_png_bit_depth.py +++ b/tests/unit/test_png_bit_depth.py @@ -12,19 +12,22 @@ def test_png_bit_depth() -> None: """Test PNG bit depth.""" - res = png_bit_depth(TEST_SRC_PATH) + with TEST_SRC_PATH.open("rb") as png_file: + res = png_bit_depth(png_file) assert res == 8 # noqa PLR2004 def test_png_bit_depth_16() -> None: """Test PNG bit depth 16.""" - res = png_bit_depth(TEST_SRC_PATH_16) + with TEST_SRC_PATH_16.open("rb") as png_file: + res = png_bit_depth(png_file) assert res == 16 # noqa PLR2004 def test_png_bit_depth_invalid() -> None: """Test PNG bit depth invalid.""" - res = png_bit_depth(TEST_SRC_PATH_JPG) + with TEST_SRC_PATH_JPG.open("rb") as jpg_file: + res = png_bit_depth(jpg_file) assert res is None