diff --git a/composer.json b/composer.json index b9224aa..8349def 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ ], "license" : "CC-BY-NC-SA-4.0", "require" : { - "php" : ">=8.4", + "php" : ">=8.3", "ext-json" : "*", "symfony/process" : "^7", "spatie/once" : "^3.0.1" diff --git a/composer.lock b/composer.lock index e8e4040..3d4b0e9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3e3328731edad114e0bc023a33c3c2a6", + "content-hash": "85977a4a95d87a531d899d016e609387", "packages": [ { "name": "spatie/once", - "version": "3.1.1", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/spatie/once.git", - "reference": "25252b821765d72566be17c52ea05b35329f0f8f" + "reference": "54c9908aff00913e82422da6938fba7bd2bf6c08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/once/zipball/25252b821765d72566be17c52ea05b35329f0f8f", - "reference": "25252b821765d72566be17c52ea05b35329f0f8f", + "url": "https://api.github.com/repos/spatie/once/zipball/54c9908aff00913e82422da6938fba7bd2bf6c08", + "reference": "54c9908aff00913e82422da6938fba7bd2bf6c08", "shasum": "" }, "require": { @@ -25,7 +25,7 @@ }, "require-dev": { "pestphp/pest": "^1.21", - "symfony/var-dumper": "^5.1" + "symfony/var-dumper": "^5.1|^6.0|^7.0|^8.0" }, "type": "library", "autoload": { @@ -58,7 +58,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/once/tree/3.1.1" + "source": "https://github.com/spatie/once/tree/3.1.2" }, "funding": [ { @@ -66,20 +66,20 @@ "type": "custom" } ], - "time": "2024-05-27T09:17:58+00:00" + "time": "2025-11-25T15:44:06+00:00" }, { "name": "symfony/process", - "version": "v7.3.11", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "81fe4ea2c3b8677fa2adfd8e48ba42374ede0e3b" + "reference": "608476f4604102976d687c483ac63a79ba18cc97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/81fe4ea2c3b8677fa2adfd8e48ba42374ede0e3b", - "reference": "81fe4ea2c3b8677fa2adfd8e48ba42374ede0e3b", + "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", + "reference": "608476f4604102976d687c483ac63a79ba18cc97", "shasum": "" }, "require": { @@ -111,7 +111,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.11" + "source": "https://github.com/symfony/process/tree/v7.4.5" }, "funding": [ { @@ -131,7 +131,7 @@ "type": "tidelift" } ], - "time": "2026-01-26T13:14:40+00:00" + "time": "2026-01-26T15:07:59+00:00" } ], "packages-dev": [ @@ -203,22 +203,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.9.3", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", - "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.3", - "guzzlehttp/psr7": "^2.7.0", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -309,7 +309,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.3" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -325,20 +325,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:37:11+00:00" + "time": "2025-08-23T22:36:01+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", - "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { @@ -346,7 +346,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "type": "library", "extra": { @@ -392,7 +392,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.2.0" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, "funding": [ { @@ -408,20 +408,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T13:27:01+00:00" + "time": "2025-08-22T14:34:08+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.7.1", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", - "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { @@ -437,7 +437,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -508,7 +508,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.1" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -524,20 +524,20 @@ "type": "tidelift" } ], - "time": "2025-03-27T12:30:47+00:00" + "time": "2025-08-23T21:21:41+00:00" }, { "name": "laravel/pint", - "version": "v1.26.0", + "version": "v1.27.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f" + "reference": "54cca2de13790570c7b6f0f94f37896bee4abcb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/69dcca060ecb15e4b564af63d1f642c81a241d6f", - "reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f", + "url": "https://api.github.com/repos/laravel/pint/zipball/54cca2de13790570c7b6f0f94f37896bee4abcb5", + "reference": "54cca2de13790570c7b6f0f94f37896bee4abcb5", "shasum": "" }, "require": { @@ -548,13 +548,13 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.90.0", - "illuminate/view": "^12.40.1", - "larastan/larastan": "^3.8.0", - "laravel-zero/framework": "^12.0.4", + "friendsofphp/php-cs-fixer": "^3.93.1", + "illuminate/view": "^12.51.0", + "larastan/larastan": "^3.9.2", + "laravel-zero/framework": "^12.0.5", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^2.3.3", - "pestphp/pest": "^3.8.4" + "pestphp/pest": "^3.8.5" }, "bin": [ "builds/pint" @@ -591,7 +591,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-11-25T21:15:52+00:00" + "time": "2026-02-10T20:00:20+00:00" }, { "name": "myclabs/deep-copy", @@ -831,11 +831,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.33", + "version": "2.1.40", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", - "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", + "reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", "shasum": "" }, "require": { @@ -880,20 +880,20 @@ "type": "github" } ], - "time": "2025-12-05T10:24:31+00:00" + "time": "2026-02-23T15:04:35+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "12.5.2", + "version": "12.5.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b" + "reference": "b015312f28dd75b75d3422ca37dff2cd1a565e8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4a9739b51cbcb355f6e95659612f92e282a7077b", - "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b015312f28dd75b75d3422ca37dff2cd1a565e8d", + "reference": "b015312f28dd75b75d3422ca37dff2cd1a565e8d", "shasum": "" }, "require": { @@ -949,7 +949,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.2" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.3" }, "funding": [ { @@ -969,20 +969,20 @@ "type": "tidelift" } ], - "time": "2025-12-24T07:03:04+00:00" + "time": "2026-02-06T06:01:44+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "6.0.0", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "961bc913d42fe24a257bfff826a5068079ac7782" + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/961bc913d42fe24a257bfff826a5068079ac7782", - "reference": "961bc913d42fe24a257bfff826a5068079ac7782", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", "shasum": "" }, "require": { @@ -1022,15 +1022,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" } ], - "time": "2025-02-07T04:58:37+00:00" + "time": "2026-02-02T14:04:18+00:00" }, { "name": "phpunit/php-invoker", @@ -1218,16 +1230,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.5.8", + "version": "12.5.14", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "37ddb96c14bfee10304825edbb7e66d341ec6889" + "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/37ddb96c14bfee10304825edbb7e66d341ec6889", - "reference": "37ddb96c14bfee10304825edbb7e66d341ec6889", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/47283cfd98d553edcb1353591f4e255dc1bb61f0", + "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0", "shasum": "" }, "require": { @@ -1241,8 +1253,8 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.3", - "phpunit/php-code-coverage": "^12.5.2", - "phpunit/php-file-iterator": "^6.0.0", + "phpunit/php-code-coverage": "^12.5.3", + "phpunit/php-file-iterator": "^6.0.1", "phpunit/php-invoker": "^6.0.0", "phpunit/php-text-template": "^5.0.0", "phpunit/php-timer": "^8.0.0", @@ -1253,6 +1265,7 @@ "sebastian/exporter": "^7.0.2", "sebastian/global-state": "^8.0.2", "sebastian/object-enumerator": "^7.0.0", + "sebastian/recursion-context": "^7.0.1", "sebastian/type": "^6.0.3", "sebastian/version": "^6.0.0", "staabm/side-effects-detector": "^1.0.5" @@ -1295,7 +1308,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.8" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.14" }, "funding": [ { @@ -1319,7 +1332,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T06:12:29+00:00" + "time": "2026-02-18T12:38:40+00:00" }, { "name": "psr/container", @@ -2529,16 +2542,16 @@ }, { "name": "symfony/console", - "version": "v7.3.1", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101" + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101", + "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", "shasum": "" }, "require": { @@ -2546,7 +2559,7 @@ "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2" + "symfony/string": "^7.2|^8.0" }, "conflict": { "symfony/dependency-injection": "<6.4", @@ -2560,16 +2573,16 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -2603,7 +2616,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.1" + "source": "https://github.com/symfony/console/tree/v7.4.4" }, "funding": [ { @@ -2614,12 +2627,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2026-01-13T11:36:38+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2690,23 +2707,23 @@ }, { "name": "symfony/finder", - "version": "v7.3.0", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0" + "symfony/filesystem": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -2734,7 +2751,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.0" + "source": "https://github.com/symfony/finder/tree/v7.4.5" }, "funding": [ { @@ -2745,16 +2762,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-30T19:00:26+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -2813,7 +2834,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -2824,6 +2845,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2833,16 +2858,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -2891,7 +2916,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -2902,16 +2927,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -2972,7 +3001,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -2983,6 +3012,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2992,7 +3025,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -3053,7 +3086,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -3064,6 +3097,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -3073,16 +3110,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -3136,7 +3173,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -3147,31 +3184,36 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", - "version": "v7.3.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "url": "https://api.github.com/repos/symfony/string/zipball/1c4b10461bf2ec27537b5f36105337262f5f5d6f", + "reference": "1c4b10461bf2ec27537b5f36105337262f5f5d6f", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-grapheme": "~1.33", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0" }, @@ -3179,12 +3221,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -3223,7 +3264,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.0" + "source": "https://github.com/symfony/string/tree/v7.4.4" }, "funding": [ { @@ -3234,37 +3275,41 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-20T20:19:01+00:00" + "time": "2026-01-12T10:54:30+00:00" }, { "name": "symfony/yaml", - "version": "v7.3.1", + "version": "v7.4.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb" + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0c3555045a46ab3cd4cc5a69d161225195230edb", - "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb", + "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", + "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0" + "symfony/console": "^6.4|^7.0|^8.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -3295,7 +3340,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.1" + "source": "https://github.com/symfony/yaml/tree/v7.4.1" }, "funding": [ { @@ -3306,12 +3351,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-03T06:57:57+00:00" + "time": "2025-12-04T18:11:45+00:00" }, { "name": "theseer/tokenizer", @@ -3370,7 +3419,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=8.4", + "php": ">=8.3", "ext-json": "*" }, "platform-dev": {}, diff --git a/src/StatsParser.php b/src/StatsParser.php index dc72e43..d1d877d 100644 --- a/src/StatsParser.php +++ b/src/StatsParser.php @@ -8,6 +8,26 @@ class StatsParser { public static function parse(string $output): object { + $output = trim($output); + if ($output === '') { + return (object) [ + 'errors' => 0, + 'checks' => 0, + 'files' => 0, + 'bytes' => 0, + 'elapsed_time' => 0.0, + 'speed_human' => '0 B/s', + 'speed_bytes_per_second' => 0.0, + ]; + } + + $lines = explode("\n", $output); + $firstLine = trim($lines[0] ?? ''); + + if (str_starts_with($firstLine, '{')) { + return self::parseJson($lines); + } + $stats = [ 'errors' => 0, 'checks' => 0, @@ -18,10 +38,10 @@ public static function parse(string $output): object 'speed_bytes_per_second' => 0.0, ]; - $lines = explode("\n", $output); - foreach ($lines as $line) { - if (preg_match('/Transferred:\s+([\d.]+\s*[KMGTPI]?B)/i', $line, $matches)) { + // Match main "Transferred:" line (e.g., "Transferred: 1.5 GiB / 1.5 GiB, 100%...") + // Updated regex to support IEC units (i.e. 'GiB') by adding 'i?' + if (preg_match('/Transferred:\s+([\d.]+\s*[KMGTPI]?i?B)/i', $line, $matches)) { $stats['bytes'] = self::convertSizeToBytes(trim($matches[1])); continue; } @@ -36,7 +56,8 @@ public static function parse(string $output): object switch ($key) { case 'Transferred': - if (preg_match('/^\s*([\d.]+\s*[KMGTPI]?B)/i', $value, $byteMatches)) { + // Handle multi-line transferred stats or other formats + if (preg_match('/^\s*([\d.]+\s*[KMGTPI]?i?B)/i', $value, $byteMatches)) { $stats['bytes'] += self::convertSizeToBytes(trim($byteMatches[1])); } elseif (preg_match('/^\s*(\d+)\s*\/\s*\d+/', $value, $fileMatches)) { $stats['files'] += (int) $fileMatches[1]; @@ -67,6 +88,57 @@ public static function parse(string $output): object return (object) $stats; } + private static function parseJson(array $lines): object + { + $stats = [ + 'errors' => 0, + 'checks' => 0, + 'files' => 0, + 'bytes' => 0, + 'elapsed_time' => 0.0, + 'speed_human' => '0 B/s', + 'speed_bytes_per_second' => 0.0, + ]; + + foreach ($lines as $line) { + $line = trim($line); + if (empty($line)) { + continue; + } + + $json = json_decode($line, false); + if (! is_object($json)) { + continue; + } + + // Map fields from rclone JSON stats + if (isset($json->bytes)) { + $stats['bytes'] = (int) $json->bytes; + } + if (isset($json->checks)) { + $stats['checks'] = (int) $json->checks; + } + if (isset($json->errors)) { + $stats['errors'] = (int) $json->errors; + } + if (isset($json->transfers)) { + $stats['files'] = (int) $json->transfers; + } elseif (isset($json->totalTransfers) && isset($json->transferring)) { + // logic for ongoing transfers? usually transfers is completed + } + + if (isset($json->elapsedTime)) { + $stats['elapsed_time'] = (float) $json->elapsedTime; + } + if (isset($json->speed)) { + $stats['speed_bytes_per_second'] = (float) $json->speed; + $stats['speed_human'] = self::formatBytes((int) $stats['speed_bytes_per_second']) . '/s'; + } + } + + return (object) $stats; + } + public static function convertSizeToBytes(string $sizeStr): int { $sizeStr = trim($sizeStr); @@ -75,7 +147,8 @@ public static function convertSizeToBytes(string $sizeStr): int } $units = ['B' => 0, 'K' => 1, 'M' => 2, 'G' => 3, 'T' => 4, 'P' => 5]; - preg_match('/([\d.]+)\s*([KMGTPI]?)B?/i', $sizeStr, $matches); + // Updated regex to support IEC units (i.e. 'GiB') by adding 'i?' + preg_match('/([\d.]+)\s*([KMGTPI]?)i?B?/i', $sizeStr, $matches); if (! isset($matches[1])) { return (int) $sizeStr; @@ -101,7 +174,7 @@ public static function convertDurationToSeconds(string $durationStr): float if (preg_match('/(\d+(\.\d+)?)h/', $durationStr, $matches)) { $totalSeconds += (float) $matches[1] * 3600; } - if (preg_match('/(\d+(\.\d+)?)m/', $durationStr, $matches)) { + if (preg_match('/(\d+(\.\d+)?)m(?!s)/', $durationStr, $matches)) { $totalSeconds += (float) $matches[1] * 60; } if (preg_match('/(\d+(\.\d+)?)s/', $durationStr, $matches)) { diff --git a/tests/Unit/StatsParserTest.php b/tests/Unit/StatsParserTest.php new file mode 100644 index 0000000..56581f8 --- /dev/null +++ b/tests/Unit/StatsParserTest.php @@ -0,0 +1,124 @@ +bytes); // 1.5 GiB + self::assertEquals(50, $stats->files); + self::assertEquals(0, $stats->errors); + self::assertEquals(10, $stats->checks); + self::assertEquals(90.5, $stats->elapsed_time); + } + + #[Test] + public function parse_output_with_errors(): void + { + $output = <<<'OUTPUT' +Transferred: 100 MiB / 200 MiB, 50%, 5 MiB/s, ETA 20s +Errors: 5 +Checks: 2 +Transferred: 10 / 20, 50% +Elapsed time: 10s +OUTPUT; + + $stats = StatsParser::parse($output); + + self::assertEquals(104857600, $stats->bytes); // 100 MiB + self::assertEquals(10, $stats->files); + self::assertEquals(5, $stats->errors); + self::assertEquals(2, $stats->checks); + self::assertEquals(10.0, $stats->elapsed_time); + } + + #[Test] + public function parse_empty_output(): void + { + $stats = StatsParser::parse(''); + + self::assertEquals(0, $stats->bytes); + self::assertEquals(0, $stats->files); + self::assertEquals(0, $stats->errors); + self::assertEquals(0, $stats->checks); + self::assertEquals(0.0, $stats->elapsed_time); + } + + #[Test] + public function format_bytes(): void + { + self::assertEquals('0 B', StatsParser::formatBytes(0)); + self::assertEquals('1 KiB', StatsParser::formatBytes(1024)); + self::assertEquals('1.5 MiB', StatsParser::formatBytes(1572864)); + } + + #[Test] + public function convert_size_to_bytes(): void + { + self::assertEquals(1024, StatsParser::convertSizeToBytes('1K')); + self::assertEquals(1048576, StatsParser::convertSizeToBytes('1M')); + self::assertEquals(1073741824, StatsParser::convertSizeToBytes('1G')); + self::assertEquals(1536, StatsParser::convertSizeToBytes('1.5K')); + self::assertEquals(1610612736, StatsParser::convertSizeToBytes('1.5 GiB')); + } + + #[Test] + public function convert_duration_to_seconds(): void + { + self::assertEquals(90.0, StatsParser::convertDurationToSeconds('1m30s')); + self::assertEquals(3661.0, StatsParser::convertDurationToSeconds('1h1m1s')); + self::assertEquals(0.5, StatsParser::convertDurationToSeconds('500ms')); + } + + #[Test] + public function parse_json_output(): void + { + $output = <<<'JSON' +{"bytes": 1610612736, "checks": 10, "elapsedTime": 90.5, "errors": 0, "speed": 10485760, "transfers": 50} +JSON; + + $stats = StatsParser::parse($output); + + self::assertEquals(1610612736, $stats->bytes); + self::assertEquals(50, $stats->files); + self::assertEquals(0, $stats->errors); + self::assertEquals(10, $stats->checks); + self::assertEquals(90.5, $stats->elapsed_time); + self::assertEquals('10 MiB/s', $stats->speed_human); + self::assertEquals(10485760, $stats->speed_bytes_per_second); + } + + #[Test] + public function parse_multiline_json_output(): void + { + // Simulating rclone outputting multiple JSON objects over time + $output = <<<'JSON' +{"bytes": 100, "transfers": 1} +{"bytes": 200, "transfers": 2} +JSON; + + $stats = StatsParser::parse($output); + + // Should take the last values + self::assertEquals(200, $stats->bytes); + self::assertEquals(2, $stats->files); + } +}