From a2af57285ce7940a32e6e0fffef12dd4b0e03dbf Mon Sep 17 00:00:00 2001 From: inhere Date: Tue, 9 Jun 2020 22:22:37 +0800 Subject: [PATCH 1/3] refactor libs --- composer.json | 33 +- libs/arr-utils/README.md | 17 - libs/arr-utils/composer.json | 31 - libs/arr-utils/src/Arr.php | 19 - libs/arr-utils/src/ArrBuffer.php | 115 -- libs/arr-utils/src/ArrayHelper.php | 1105 ----------------- libs/arr-utils/src/FixedArray.php | 219 ---- libs/arr-utils/test/boot.php | 27 - libs/cli-utils/.gitignore | 11 - libs/cli-utils/README.md | 82 -- libs/cli-utils/composer.json | 36 - libs/cli-utils/example/all-color-style.jpg | Bin 102239 -> 0 bytes .../example/cli-php-file-highlight.jpg | Bin 50719 -> 0 bytes libs/cli-utils/example/color.php | 15 - libs/cli-utils/example/down.php | 21 - libs/cli-utils/example/high.php | 19 - libs/cli-utils/example/liteApp | 24 - libs/cli-utils/src/App.php | 579 --------- libs/cli-utils/src/Cli.php | 266 ---- libs/cli-utils/src/Color.php | 340 ----- libs/cli-utils/src/ColorTag.php | 117 -- libs/cli-utils/src/Download.php | 273 ---- libs/cli-utils/src/Flags.php | 327 ----- libs/cli-utils/src/Highlighter.php | 380 ------ libs/cli-utils/src/Terminal.php | 243 ---- libs/cli-utils/test/ColorTagTest.php | 75 -- libs/cli-utils/test/ColorTest.php | 43 - libs/cli-utils/test/FlagsTest.php | 38 - libs/cli-utils/test/boot.php | 27 - libs/collection/phpunit.xml.dist | 23 - libs/di/phpunit.xml.dist | 23 - libs/obj-utils/LICENSE | 20 - libs/obj-utils/README.md | 17 - libs/obj-utils/composer.json | 34 - libs/obj-utils/src/Configurable.php | 43 - .../src/Exception/GetPropertyException.php | 20 - .../src/Exception/PropertyException.php | 20 - .../src/Exception/SetPropertyException.php | 20 - libs/obj-utils/src/Obj.php | 66 - libs/obj-utils/src/ObjectHelper.php | 283 ----- .../Traits/ArrayAccessByGetterSetterTrait.php | 83 -- .../src/Traits/ArrayAccessByPropertyTrait.php | 74 -- libs/obj-utils/src/Traits/ObjectPoolTrait.php | 128 -- .../PropertyAccessByGetterSetterTrait.php | 108 -- libs/obj-utils/src/Traits/SingletonTrait.php | 31 - libs/obj-utils/src/Traits/StdObjectTrait.php | 118 -- libs/obj-utils/test/boot.php | 27 - libs/php-utils/.gitignore | 10 - libs/php-utils/LICENSE | 20 - libs/str-utils/.gitignore | 10 - libs/str-utils/LICENSE | 20 - libs/str-utils/README.md | 24 - libs/str-utils/composer.json | 32 - libs/str-utils/phpunit.xml.dist | 23 - libs/str-utils/src/HtmlHelper.php | 241 ---- libs/str-utils/src/Json.php | 18 - libs/str-utils/src/JsonHelper.php | 203 --- libs/str-utils/src/Str.php | 18 - libs/str-utils/src/StrBuffer.php | 95 -- libs/str-utils/src/StringHelper.php | 1078 ---------------- libs/str-utils/src/Token.php | 210 ---- libs/str-utils/src/UUID.php | 472 ------- libs/str-utils/src/UrlHelper.php | 242 ---- libs/str-utils/test/boot.php | 27 - libs/sys-utils/.gitignore | 10 - libs/sys-utils/LICENSE | 20 - libs/sys-utils/phpunit.xml.dist | 24 - {libs => src}/autoload.php | 0 {libs/arr-utils => src/collection}/.gitignore | 0 {libs/arr-utils => src/collection}/LICENSE | 0 {libs => src}/collection/README.md | 0 {libs => src}/collection/composer.json | 0 .../collection}/phpunit.xml.dist | 0 {libs => src}/collection/src/ActiveData.php | 0 {libs => src}/collection/src/Collection.php | 0 .../collection/src/CollectionInterface.php | 0 .../collection/src/Configuration.php | 0 {libs => src}/collection/src/JsonMessage.php | 0 {libs => src}/collection/src/Language.php | 0 .../collection/src/LiteCollection.php | 0 .../collection/src/SimpleCollection.php | 0 .../collection/test/LanguageTest.php | 0 {libs => src}/collection/test/boot.php | 0 .../collection/test/testdata/en/response.php | 0 .../test/testdata/zh-CN/response.php | 0 .../collection => src/data-parser}/.gitignore | 0 {libs/cli-utils => src/data-parser}/LICENSE | 0 {libs => src}/data-parser/README.md | 0 {libs => src}/data-parser/composer.json | 0 {libs => src}/data-parser/phpunit.xml.dist | 0 .../data-parser/src/AbstractDataParser.php | 0 .../data-parser/src/DataParserAwareTrait.php | 0 .../data-parser/src/DataParserInterface.php | 0 {libs => src}/data-parser/src/JsonParser.php | 0 .../data-parser/src/MsgPackParser.php | 0 {libs => src}/data-parser/src/PhpParser.php | 0 .../data-parser/src/SwooleParser.php | 0 .../data-parser/test/JsonParserTest.php | 0 .../data-parser/test/PhpParserTest.php | 0 {libs => src}/data-parser/test/boot.php | 0 .../dev-helper/Console/DevController.php | 0 {libs/data-parser => src/di}/.gitignore | 0 {libs/collection => src/di}/LICENSE | 0 {libs => src}/di/README.md | 0 {libs => src}/di/composer.json | 0 {libs => src}/di/example/di.php | 0 {libs/cli-utils => src/di}/phpunit.xml.dist | 0 {libs => src}/di/src/CallableResolver.php | 0 .../di/src/CallableResolverAwareTrait.php | 0 {libs => src}/di/src/Container.php | 0 {libs => src}/di/src/DIManager.php | 0 .../DependencyResolutionException.php | 0 .../di/src/Exception/NotFoundException.php | 0 {libs => src}/di/src/NameAliasTrait.php | 0 {libs => src}/di/src/ObjectItem.php | 0 .../di/src/ServiceProviderInterface.php | 0 {libs => src}/di/test/ContainerTest.php | 0 {libs => src}/di/test/MakeByMethod.php | 0 {libs => src}/di/test/MakeByStatic.php | 0 {libs => src}/di/test/SomeClass.php | 0 {libs => src}/di/test/boot.php | 0 {libs/di => src/file-parse}/.gitignore | 0 {libs/data-parser => src/file-parse}/LICENSE | 0 {libs => src}/file-parse/README.md | 0 {libs => src}/file-parse/composer.json | 0 {libs => src}/file-parse/phpunit.xml.dist | 0 {libs => src}/file-parse/src/BaseParser.php | 0 {libs => src}/file-parse/src/IniParser.php | 0 {libs => src}/file-parse/src/JsonParser.php | 0 {libs => src}/file-parse/src/YmlParser.php | 0 .../file-parse/test/IniParserTest.php | 0 {libs => src}/file-parse/test/boot.php | 0 .../file-parse/test/data/include.ini | 0 {libs => src}/file-parse/test/data/test.ini | 0 .../file-parse => src/file-utils}/.gitignore | 0 {libs/di => src/file-utils}/LICENSE | 0 {libs => src}/file-utils/README.md | 0 {libs => src}/file-utils/composer.json | 0 .../file-utils/example/dir-watcher.php | 0 .../file-utils/example/file-finder.php | 0 {libs => src}/file-utils/phpunit.xml.dist | 0 {libs => src}/file-utils/src/Directory.php | 0 .../src/Exception/FileNotFoundException.php | 0 .../src/Exception/FileReadException.php | 0 .../src/Exception/FileSystemException.php | 0 .../file-utils/src/Exception/IOException.php | 0 {libs => src}/file-utils/src/File.php | 0 {libs => src}/file-utils/src/FileFinder.php | 0 {libs => src}/file-utils/src/FileSystem.php | 0 .../file-utils/src/ModifyWatcher.php | 0 {libs => src}/file-utils/src/ReadTrait.php | 0 {libs => src}/file-utils/test/boot.php | 0 .../helper-utils}/.gitignore | 0 {libs/file-parse => src/helper-utils}/LICENSE | 0 {libs => src}/helper-utils/README.md | 0 {libs => src}/helper-utils/composer.json | 0 {libs => src}/helper-utils/phpunit.xml.dist | 0 .../helper-utils/src/Helper/AssertHelper.php | 0 .../helper-utils/src/Helper/DataHelper.php | 0 .../helper-utils/src/Helper/DateHelper.php | 0 .../helper-utils/src/Helper/DsnHelper.php | 0 .../helper-utils/src/Helper/FormatHelper.php | 0 .../helper-utils/src/Helper/Http.php | 0 .../helper-utils/src/Helper/IntHelper.php | 0 .../helper-utils/src/Helper/SslHelper.php | 0 .../helper-utils/src/Helper/UtilHelper.php | 0 .../src/Traits/AopProxyAwareTrait.php | 0 .../src/Traits/Config/ConfigTrait.php | 0 .../src/Traits/Config/LiteConfigTrait.php | 0 .../src/Traits/Config/LiteOptionsTrait.php | 0 .../src/Traits/Config/OptionsTrait.php | 0 .../src/Traits/Event/EventTrait.php | 0 .../Traits/Event/FixedEventStaticTrait.php | 0 .../src/Traits/Event/FixedEventTrait.php | 0 .../src/Traits/Event/LiteEventStaticTrait.php | 0 .../src/Traits/Event/LiteEventTrait.php | 0 .../src/Traits/LiteContainerStaticTrait.php | 0 .../src/Traits/LiteContainerTrait.php | 0 .../src/Traits/LogProfileTrait.php | 0 .../helper-utils/src/Traits/LogShortTrait.php | 0 .../src/Traits/NameAliasStaticTrait.php | 0 .../src/Traits/NameAliasTrait.php | 0 .../src/Traits/PathAliasTrait.php | 0 .../src/Traits/PathResolverTrait.php | 0 .../src/Traits/RuntimeProfileTrait.php | 0 .../helper-utils/src/Util/AopProxy.php | 0 .../helper-utils/src/Util/DataProxy.php | 0 .../helper-utils/src/Util/DataResult.php | 0 .../src/Util/DeferredCallable.php | 0 .../helper-utils/src/Util/Pipeline.php | 0 .../src/Util/PipelineInterface.php | 0 {libs => src}/helper-utils/test/boot.php | 0 .../helper-utils => src/php-utils}/.gitignore | 0 {libs/file-utils => src/php-utils}/LICENSE | 0 {libs => src}/php-utils/README.md | 0 {libs => src}/php-utils/composer.json | 0 .../php-utils/example/property_exists.php | 0 .../php-utils}/phpunit.xml.dist | 0 {libs => src}/php-utils/src/AutoLoader.php | 0 {libs => src}/php-utils/src/Php.php | 0 {libs => src}/php-utils/src/PhpDoc.php | 0 {libs => src}/php-utils/src/PhpDotEnv.php | 0 {libs => src}/php-utils/src/PhpEnv.php | 0 {libs => src}/php-utils/src/PhpError.php | 0 {libs => src}/php-utils/src/PhpException.php | 0 {libs => src}/php-utils/src/PhpHelper.php | 0 {libs => src}/php-utils/src/Type.php | 0 {libs => src}/php-utils/test/PhpDocTest.php | 0 {libs => src}/php-utils/test/boot.php | 0 {libs/obj-utils => src/sys-utils}/.gitignore | 0 {libs/helper-utils => src/sys-utils}/LICENSE | 0 {libs => src}/sys-utils/README.md | 0 {libs => src}/sys-utils/composer.json | 0 .../sys-utils}/phpunit.xml.dist | 0 {libs => src}/sys-utils/src/ProcessUtil.php | 0 {libs => src}/sys-utils/src/Signal.php | 0 {libs => src}/sys-utils/src/Sys.php | 0 {libs => src}/sys-utils/src/SysEnv.php | 0 {libs => src}/sys-utils/test/boot.php | 0 219 files changed, 3 insertions(+), 8414 deletions(-) delete mode 100644 libs/arr-utils/README.md delete mode 100644 libs/arr-utils/composer.json delete mode 100644 libs/arr-utils/src/Arr.php delete mode 100644 libs/arr-utils/src/ArrBuffer.php delete mode 100644 libs/arr-utils/src/ArrayHelper.php delete mode 100644 libs/arr-utils/src/FixedArray.php delete mode 100644 libs/arr-utils/test/boot.php delete mode 100644 libs/cli-utils/.gitignore delete mode 100644 libs/cli-utils/README.md delete mode 100644 libs/cli-utils/composer.json delete mode 100644 libs/cli-utils/example/all-color-style.jpg delete mode 100644 libs/cli-utils/example/cli-php-file-highlight.jpg delete mode 100644 libs/cli-utils/example/color.php delete mode 100644 libs/cli-utils/example/down.php delete mode 100644 libs/cli-utils/example/high.php delete mode 100644 libs/cli-utils/example/liteApp delete mode 100644 libs/cli-utils/src/App.php delete mode 100644 libs/cli-utils/src/Cli.php delete mode 100644 libs/cli-utils/src/Color.php delete mode 100644 libs/cli-utils/src/ColorTag.php delete mode 100644 libs/cli-utils/src/Download.php delete mode 100644 libs/cli-utils/src/Flags.php delete mode 100644 libs/cli-utils/src/Highlighter.php delete mode 100644 libs/cli-utils/src/Terminal.php delete mode 100644 libs/cli-utils/test/ColorTagTest.php delete mode 100644 libs/cli-utils/test/ColorTest.php delete mode 100644 libs/cli-utils/test/FlagsTest.php delete mode 100644 libs/cli-utils/test/boot.php delete mode 100644 libs/collection/phpunit.xml.dist delete mode 100644 libs/di/phpunit.xml.dist delete mode 100644 libs/obj-utils/LICENSE delete mode 100644 libs/obj-utils/README.md delete mode 100644 libs/obj-utils/composer.json delete mode 100644 libs/obj-utils/src/Configurable.php delete mode 100644 libs/obj-utils/src/Exception/GetPropertyException.php delete mode 100644 libs/obj-utils/src/Exception/PropertyException.php delete mode 100644 libs/obj-utils/src/Exception/SetPropertyException.php delete mode 100644 libs/obj-utils/src/Obj.php delete mode 100644 libs/obj-utils/src/ObjectHelper.php delete mode 100644 libs/obj-utils/src/Traits/ArrayAccessByGetterSetterTrait.php delete mode 100644 libs/obj-utils/src/Traits/ArrayAccessByPropertyTrait.php delete mode 100644 libs/obj-utils/src/Traits/ObjectPoolTrait.php delete mode 100644 libs/obj-utils/src/Traits/PropertyAccessByGetterSetterTrait.php delete mode 100644 libs/obj-utils/src/Traits/SingletonTrait.php delete mode 100644 libs/obj-utils/src/Traits/StdObjectTrait.php delete mode 100644 libs/obj-utils/test/boot.php delete mode 100644 libs/php-utils/.gitignore delete mode 100644 libs/php-utils/LICENSE delete mode 100644 libs/str-utils/.gitignore delete mode 100644 libs/str-utils/LICENSE delete mode 100644 libs/str-utils/README.md delete mode 100644 libs/str-utils/composer.json delete mode 100644 libs/str-utils/phpunit.xml.dist delete mode 100644 libs/str-utils/src/HtmlHelper.php delete mode 100644 libs/str-utils/src/Json.php delete mode 100644 libs/str-utils/src/JsonHelper.php delete mode 100644 libs/str-utils/src/Str.php delete mode 100644 libs/str-utils/src/StrBuffer.php delete mode 100644 libs/str-utils/src/StringHelper.php delete mode 100644 libs/str-utils/src/Token.php delete mode 100644 libs/str-utils/src/UUID.php delete mode 100644 libs/str-utils/src/UrlHelper.php delete mode 100644 libs/str-utils/test/boot.php delete mode 100644 libs/sys-utils/.gitignore delete mode 100644 libs/sys-utils/LICENSE delete mode 100644 libs/sys-utils/phpunit.xml.dist rename {libs => src}/autoload.php (100%) rename {libs/arr-utils => src/collection}/.gitignore (100%) rename {libs/arr-utils => src/collection}/LICENSE (100%) rename {libs => src}/collection/README.md (100%) rename {libs => src}/collection/composer.json (100%) rename {libs/arr-utils => src/collection}/phpunit.xml.dist (100%) rename {libs => src}/collection/src/ActiveData.php (100%) rename {libs => src}/collection/src/Collection.php (100%) rename {libs => src}/collection/src/CollectionInterface.php (100%) rename {libs => src}/collection/src/Configuration.php (100%) rename {libs => src}/collection/src/JsonMessage.php (100%) rename {libs => src}/collection/src/Language.php (100%) rename {libs => src}/collection/src/LiteCollection.php (100%) rename {libs => src}/collection/src/SimpleCollection.php (100%) rename {libs => src}/collection/test/LanguageTest.php (100%) rename {libs => src}/collection/test/boot.php (100%) rename {libs => src}/collection/test/testdata/en/response.php (100%) rename {libs => src}/collection/test/testdata/zh-CN/response.php (100%) rename {libs/collection => src/data-parser}/.gitignore (100%) rename {libs/cli-utils => src/data-parser}/LICENSE (100%) rename {libs => src}/data-parser/README.md (100%) rename {libs => src}/data-parser/composer.json (100%) rename {libs => src}/data-parser/phpunit.xml.dist (100%) rename {libs => src}/data-parser/src/AbstractDataParser.php (100%) rename {libs => src}/data-parser/src/DataParserAwareTrait.php (100%) rename {libs => src}/data-parser/src/DataParserInterface.php (100%) rename {libs => src}/data-parser/src/JsonParser.php (100%) rename {libs => src}/data-parser/src/MsgPackParser.php (100%) rename {libs => src}/data-parser/src/PhpParser.php (100%) rename {libs => src}/data-parser/src/SwooleParser.php (100%) rename {libs => src}/data-parser/test/JsonParserTest.php (100%) rename {libs => src}/data-parser/test/PhpParserTest.php (100%) rename {libs => src}/data-parser/test/boot.php (100%) rename {libs => src}/dev-helper/Console/DevController.php (100%) rename {libs/data-parser => src/di}/.gitignore (100%) rename {libs/collection => src/di}/LICENSE (100%) rename {libs => src}/di/README.md (100%) rename {libs => src}/di/composer.json (100%) rename {libs => src}/di/example/di.php (100%) rename {libs/cli-utils => src/di}/phpunit.xml.dist (100%) rename {libs => src}/di/src/CallableResolver.php (100%) rename {libs => src}/di/src/CallableResolverAwareTrait.php (100%) rename {libs => src}/di/src/Container.php (100%) rename {libs => src}/di/src/DIManager.php (100%) rename {libs => src}/di/src/Exception/DependencyResolutionException.php (100%) rename {libs => src}/di/src/Exception/NotFoundException.php (100%) rename {libs => src}/di/src/NameAliasTrait.php (100%) rename {libs => src}/di/src/ObjectItem.php (100%) rename {libs => src}/di/src/ServiceProviderInterface.php (100%) rename {libs => src}/di/test/ContainerTest.php (100%) rename {libs => src}/di/test/MakeByMethod.php (100%) rename {libs => src}/di/test/MakeByStatic.php (100%) rename {libs => src}/di/test/SomeClass.php (100%) rename {libs => src}/di/test/boot.php (100%) rename {libs/di => src/file-parse}/.gitignore (100%) rename {libs/data-parser => src/file-parse}/LICENSE (100%) rename {libs => src}/file-parse/README.md (100%) rename {libs => src}/file-parse/composer.json (100%) rename {libs => src}/file-parse/phpunit.xml.dist (100%) rename {libs => src}/file-parse/src/BaseParser.php (100%) rename {libs => src}/file-parse/src/IniParser.php (100%) rename {libs => src}/file-parse/src/JsonParser.php (100%) rename {libs => src}/file-parse/src/YmlParser.php (100%) rename {libs => src}/file-parse/test/IniParserTest.php (100%) rename {libs => src}/file-parse/test/boot.php (100%) rename {libs => src}/file-parse/test/data/include.ini (100%) rename {libs => src}/file-parse/test/data/test.ini (100%) rename {libs/file-parse => src/file-utils}/.gitignore (100%) rename {libs/di => src/file-utils}/LICENSE (100%) rename {libs => src}/file-utils/README.md (100%) rename {libs => src}/file-utils/composer.json (100%) rename {libs => src}/file-utils/example/dir-watcher.php (100%) rename {libs => src}/file-utils/example/file-finder.php (100%) rename {libs => src}/file-utils/phpunit.xml.dist (100%) rename {libs => src}/file-utils/src/Directory.php (100%) rename {libs => src}/file-utils/src/Exception/FileNotFoundException.php (100%) rename {libs => src}/file-utils/src/Exception/FileReadException.php (100%) rename {libs => src}/file-utils/src/Exception/FileSystemException.php (100%) rename {libs => src}/file-utils/src/Exception/IOException.php (100%) rename {libs => src}/file-utils/src/File.php (100%) rename {libs => src}/file-utils/src/FileFinder.php (100%) rename {libs => src}/file-utils/src/FileSystem.php (100%) rename {libs => src}/file-utils/src/ModifyWatcher.php (100%) rename {libs => src}/file-utils/src/ReadTrait.php (100%) rename {libs => src}/file-utils/test/boot.php (100%) rename {libs/file-utils => src/helper-utils}/.gitignore (100%) rename {libs/file-parse => src/helper-utils}/LICENSE (100%) rename {libs => src}/helper-utils/README.md (100%) rename {libs => src}/helper-utils/composer.json (100%) rename {libs => src}/helper-utils/phpunit.xml.dist (100%) rename {libs => src}/helper-utils/src/Helper/AssertHelper.php (100%) rename {libs => src}/helper-utils/src/Helper/DataHelper.php (100%) rename {libs => src}/helper-utils/src/Helper/DateHelper.php (100%) rename {libs => src}/helper-utils/src/Helper/DsnHelper.php (100%) rename {libs => src}/helper-utils/src/Helper/FormatHelper.php (100%) rename {libs => src}/helper-utils/src/Helper/Http.php (100%) rename {libs => src}/helper-utils/src/Helper/IntHelper.php (100%) rename {libs => src}/helper-utils/src/Helper/SslHelper.php (100%) rename {libs => src}/helper-utils/src/Helper/UtilHelper.php (100%) rename {libs => src}/helper-utils/src/Traits/AopProxyAwareTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/Config/ConfigTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/Config/LiteConfigTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/Config/LiteOptionsTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/Config/OptionsTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/Event/EventTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/Event/FixedEventStaticTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/Event/FixedEventTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/Event/LiteEventStaticTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/Event/LiteEventTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/LiteContainerStaticTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/LiteContainerTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/LogProfileTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/LogShortTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/NameAliasStaticTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/NameAliasTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/PathAliasTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/PathResolverTrait.php (100%) rename {libs => src}/helper-utils/src/Traits/RuntimeProfileTrait.php (100%) rename {libs => src}/helper-utils/src/Util/AopProxy.php (100%) rename {libs => src}/helper-utils/src/Util/DataProxy.php (100%) rename {libs => src}/helper-utils/src/Util/DataResult.php (100%) rename {libs => src}/helper-utils/src/Util/DeferredCallable.php (100%) rename {libs => src}/helper-utils/src/Util/Pipeline.php (100%) rename {libs => src}/helper-utils/src/Util/PipelineInterface.php (100%) rename {libs => src}/helper-utils/test/boot.php (100%) rename {libs/helper-utils => src/php-utils}/.gitignore (100%) rename {libs/file-utils => src/php-utils}/LICENSE (100%) rename {libs => src}/php-utils/README.md (100%) rename {libs => src}/php-utils/composer.json (100%) rename {libs => src}/php-utils/example/property_exists.php (100%) rename {libs/obj-utils => src/php-utils}/phpunit.xml.dist (100%) rename {libs => src}/php-utils/src/AutoLoader.php (100%) rename {libs => src}/php-utils/src/Php.php (100%) rename {libs => src}/php-utils/src/PhpDoc.php (100%) rename {libs => src}/php-utils/src/PhpDotEnv.php (100%) rename {libs => src}/php-utils/src/PhpEnv.php (100%) rename {libs => src}/php-utils/src/PhpError.php (100%) rename {libs => src}/php-utils/src/PhpException.php (100%) rename {libs => src}/php-utils/src/PhpHelper.php (100%) rename {libs => src}/php-utils/src/Type.php (100%) rename {libs => src}/php-utils/test/PhpDocTest.php (100%) rename {libs => src}/php-utils/test/boot.php (100%) rename {libs/obj-utils => src/sys-utils}/.gitignore (100%) rename {libs/helper-utils => src/sys-utils}/LICENSE (100%) rename {libs => src}/sys-utils/README.md (100%) rename {libs => src}/sys-utils/composer.json (100%) rename {libs/php-utils => src/sys-utils}/phpunit.xml.dist (100%) rename {libs => src}/sys-utils/src/ProcessUtil.php (100%) rename {libs => src}/sys-utils/src/Signal.php (100%) rename {libs => src}/sys-utils/src/Sys.php (100%) rename {libs => src}/sys-utils/src/SysEnv.php (100%) rename {libs => src}/sys-utils/test/boot.php (100%) diff --git a/composer.json b/composer.json index eba42bd..c75622c 100644 --- a/composer.json +++ b/composer.json @@ -18,42 +18,15 @@ ], "require": { "php": ">7.1.0", - "psr/container": "^1.0" + "psr/container": "^1.0", + "toolkit/stdlib": "^1.0" }, "require-dev": { "inhere/console": "dev-master" }, - "replace": { - "toolkit/arr-utils": "self.version", - "toolkit/collection": "self.version", - "toolkit/cli-utils": "self.version", - "toolkit/data-parser": "self.version", - "toolkit/di": "self.version", - "toolkit/file-utils": "self.version", - "toolkit/file-parse": "self.version", - "toolkit/obj-utils": "self.version", - "toolkit/php-utils": "self.version", - "toolkit/str-utils": "self.version", - "toolkit/sys-utils": "self.version", - "toolkit/helper-utils": "self.version" - }, "autoload": { "psr-4": { - "Toolkit\\Dev\\": "libs/dev-helper/", - "Toolkit\\ArrUtil\\": "libs/arr-utils/src/", - "Toolkit\\Collection\\": "libs/collection/src/", - "Toolkit\\Cli\\": "libs/cli-utils/src/", - "Toolkit\\DI\\": "libs/di/src/", - "Toolkit\\DataParser\\": "libs/data-parser/src/", - "Toolkit\\File\\": "libs/file-utils/src/", - "Toolkit\\File\\Parse\\": "libs/file-parse/src/", - "Toolkit\\ObjUtil\\": "libs/obj-utils/src/", - "Toolkit\\PhpUtil\\": "libs/php-utils/src/", - "Toolkit\\StrUtil\\": "libs/str-utils/src/", - "Toolkit\\Sys\\": "libs/sys-utils/src/", - "Toolkit\\Util\\": "libs/helper-utils/src/Util", - "Toolkit\\Helper\\": "libs/helper-utils/src/Helper", - "Toolkit\\Traits\\": "libs/helper-utils/src/Traits" + "Toolkit\\Dev\\": "src/" } }, "scripts": { diff --git a/libs/arr-utils/README.md b/libs/arr-utils/README.md deleted file mode 100644 index c7a50b7..0000000 --- a/libs/arr-utils/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# array utils - -[![License](https://img.shields.io/packagist/l/toolkit/arr-utils.svg?style=flat-square)](LICENSE) -[![Php Version](https://img.shields.io/badge/php-%3E=7.1.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/toolkit/arr-utils) -[![Latest Stable Version](http://img.shields.io/packagist/v/toolkit/arr-utils.svg)](https://packagist.org/packages/toolkit/arr-utils) - -Some useful array utils for php - -## Install - -```bash -composer require toolkit/arr-utils -``` - -## License - -MIT diff --git a/libs/arr-utils/composer.json b/libs/arr-utils/composer.json deleted file mode 100644 index 92e1e00..0000000 --- a/libs/arr-utils/composer.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "toolkit/arr-utils", - "type": "library", - "description": "some array tool library of the php", - "keywords": [ - "library", - "tool", - "php" - ], - "homepage": "https://github.com/php-toolkit/arr-utils", - "license": "MIT", - "authors": [ - { - "name": "inhere", - "email": "in.798@qq.com", - "homepage": "http://www.yzone.net/" - } - ], - "require": { - "php": ">7.1.0" - }, - "autoload": { - "psr-4": { - "Toolkit\\ArrUtil\\": "src/" - } - }, - "suggest": { - "inhere/php-validate": "Very lightweight data validate tool", - "inhere/console": "a lightweight php console application library." - } -} diff --git a/libs/arr-utils/src/Arr.php b/libs/arr-utils/src/Arr.php deleted file mode 100644 index 5cd13cb..0000000 --- a/libs/arr-utils/src/Arr.php +++ /dev/null @@ -1,19 +0,0 @@ -body[] = $content; - } - } - - /** - * @param string $content - */ - public function write(string $content): void - { - $this->body[] = $content; - } - - /** - * @param string $content - */ - public function append(string $content): void - { - $this->write($content); - } - - /** - * @param string $content - */ - public function prepend(string $content): void - { - array_unshift($this->body, $content); - } - - /** - * clear - */ - public function clear(): void - { - $this->body = []; - } - - /** - * @return string[] - */ - public function getBody(): array - { - return $this->body; - } - - /** - * @param string[] $body - */ - public function setBody(array $body): void - { - $this->body = $body; - } - - /** - * @return string - */ - public function toString(): string - { - return implode($this->delimiter, $this->body); - } - - /** - * @return string - */ - public function __toString() - { - return $this->toString(); - } - - /** - * @return string - */ - public function getDelimiter(): string - { - return $this->delimiter; - } - - /** - * @param string $delimiter - */ - public function setDelimiter(string $delimiter): void - { - $this->delimiter = $delimiter; - } -} diff --git a/libs/arr-utils/src/ArrayHelper.php b/libs/arr-utils/src/ArrayHelper.php deleted file mode 100644 index 51c8bae..0000000 --- a/libs/arr-utils/src/ArrayHelper.php +++ /dev/null @@ -1,1105 +0,0 @@ - $value) { - $name = trim($name); - - if (!$name || is_numeric($name)) { - continue; - } - - $object->$name = is_array($value) ? self::toObject($value) : $value; - } - - return $object; - } - - /** - * Get Multi - 获取多个, 可以设置默认值 - * - * @param array $data array data - * @param array $needKeys - * $needKeys = [ - * 'name', - * 'password', - * 'status' => '1' - * ] - * @param bool|false $unsetKey - * - * @return array - */ - public static function gets(array &$data, array $needKeys = [], $unsetKey = false): array - { - $needed = []; - - foreach ($needKeys as $key => $default) { - if (is_int($key)) { - $key = $default; - $default = null; - } - - if (isset($data[$key])) { - $value = $data[$key]; - - if (is_int($default)) { - $value = (int)$value; - } elseif (is_string($default)) { - $value = trim($value); - } elseif (is_array($default)) { - $value = (array)$value; - } - - $needed[$key] = $value; - - if ($unsetKey) { - unset($data[$key]); - } - } else { - $needed[$key] = $default; - } - } - - return $needed; - } - - /** - * 递归合并两个多维数组,后面的值将会递归覆盖原来的值 - * - * @param array|null $src - * @param array $new - * - * @return array - */ - public static function merge($src, array $new): array - { - if (!$src || !is_array($src)) { - return $new; - } - - if (!$new) { - return $src; - } - - foreach ($new as $key => $value) { - if (is_int($key)) { - if (isset($src[$key])) { - $src[] = $value; - } else { - $src[$key] = $value; - } - } elseif (array_key_exists($key, $src) && is_array($value)) { - $src[$key] = self::merge($src[$key], $new[$key]); - } else { - $src[$key] = $value; - } - } - - return $src; - } - - /** - * 递归合并多个多维数组, - * - * @from yii2 - * Merges two or more arrays into one recursively. - * - * @param array $args - * - * @return array the merged array (the original arrays are not changed.) - */ - public static function merge2(...$args): array - { - /** @var array[] $args */ - $res = array_shift($args); - - while (!empty($args)) { - /** @var array $next */ - $next = array_shift($args); - - foreach ($next as $k => $v) { - if (is_int($k)) { - if (isset($res[$k])) { - $res[] = $v; - } else { - $res[$k] = $v; - } - } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) { - $res[$k] = self::merge2($res[$k], $v); - } else { - $res[$k] = $v; - } - } - } - - return $res; - } - - /** - * 清理数组值的空白 - * - * @param array $data - * - * @return array|string - */ - public static function valueTrim(array $data) - { - if (is_scalar($data)) { - return trim($data); - } - - array_walk_recursive($data, function (&$value) { - $value = trim($value); - }); - - return $data; - } - - /** - * 不区分大小写检测数据键名是否存在 - * - * @param int|string $key - * @param array $arr - * - * @return bool - */ - public static function keyExists($key, array $arr): bool - { - return array_key_exists(strtolower($key), array_change_key_case($arr)); - } - - /** - * @param array $arr - * - * @return array - */ - public static function valueToLower(array $arr): array - { - return self::changeValueCase($arr, 0); - } - - /** - * @param array $arr - * - * @return array - */ - public static function valueToUpper(array $arr): array - { - return self::changeValueCase($arr); - } - - /** - * 将数组中的值全部转为大写或小写 - * - * @param array $arr - * @param int $toUpper 1 值大写 0 值小写 - * - * @return array - */ - public static function changeValueCase($arr, $toUpper = 1): array - { - $function = $toUpper ? 'strtoupper' : 'strtolower'; - $newArr = []; //格式化后的数组 - - foreach ($arr as $k => $v) { - if (is_array($v)) { - $newArr[$k] = self::changeValueCase($v, $toUpper); - } else { - $v = trim($v); - $newArr[$k] = $function($v); - } - } - - return $newArr; - } - - /** - * ******* 检查 一个或多个值是否全部存在数组中 ******* - * 有一个不存在即返回 false - * - * @param string|array $check - * @param array $sampleArr 只能检查一维数组 - * 注: 不分类型, 区分大小写 2 == '2' ‘a' != 'A' - * - * @return bool - */ - public static function valueExistsAll($check, array $sampleArr): bool - { - // 以逗号分隔的会被拆开,组成数组 - if (is_string($check)) { - $check = trim($check, ', '); - $check = strpos($check, ',') !== false ? explode(',', $check) : [$check]; - } - - return !array_diff((array)$check, $sampleArr); - } - - /** - * ******* 检查 一个或多个值是否存在数组中 ******* - * 有一个存在就返回 true 都不存在 return false - * - * @param string|array $check - * @param array $sampleArr 只能检查一维数组 - * - * @return bool - */ - public static function valueExistsOne($check, array $sampleArr): bool - { - // 以逗号分隔的会被拆开,组成数组 - if (is_string($check)) { - $check = trim($check, ', '); - $check = strpos($check, ',') !== false ? explode(',', $check) : [$check]; - } - - return (bool)array_intersect((array)$check, $sampleArr); - } - - /** - * ******* 不区分大小写,检查 一个或多个值是否 全存在数组中 ******* - * 有一个不存在即返回 false - * - * @param string|array $need - * @param array $arr 只能检查一维数组 - * @param bool $type 是否同时验证类型 - * - * @return bool | string 不存在的会返回 检查到的 字段,判断时 请使用 ArrHelper::existsAll($need,$arr)===true 来验证是否全存在 - */ - public static function existsAll($need, $arr, $type = false) - { - if (is_array($need)) { - foreach ((array)$need as $v) { - self::existsAll($v, $arr, $type); - } - - } elseif (strpos($need, ',') !== false) { - $need = explode(',', $need); - self::existsAll($need, $arr, $type); - } else { - $arr = self::valueToLower($arr);//小写 - $need = strtolower(trim($need));//小写 - - if (!in_array($need, $arr, $type)) { - return $need; - } - } - - return true; - } - - /** - * ******* 不区分大小写,检查 一个或多个值是否存在数组中 ******* - * 有一个存在就返回 true 都不存在 return false - * - * @param string|array $need - * @param array $arr 只能检查一维数组 - * @param bool $type 是否同时验证类型 - * - * @return bool - */ - public static function existsOne($need, $arr, $type = false): bool - { - if (is_array($need)) { - foreach ((array)$need as $v) { - $result = self::existsOne($v, $arr, $type); - if ($result) { - return true; - } - } - } else { - if (strpos($need, ',') !== false) { - $need = explode(',', $need); - - return self::existsOne($need, $arr, $type); - } - - $arr = self::changeValueCase($arr);//小写 - $need = strtolower($need);//小写 - - if (in_array($need, $arr, $type)) { - return true; - } - } - - return false; - } - - /** - * get key Max Width - * - * @param array $data - * [ - * 'key1' => 'value1', - * 'key2-test' => 'value2', - * ] - * @param bool $expectInt - * - * @return int - */ - public static function getKeyMaxWidth(array $data, $expectInt = true): int - { - $keyMaxWidth = 0; - - foreach ($data as $key => $value) { - // key is not a integer - if (!$expectInt || !is_numeric($key)) { - $width = mb_strlen($key, 'UTF-8'); - $keyMaxWidth = $width > $keyMaxWidth ? $width : $keyMaxWidth; - } - } - - return $keyMaxWidth; - } - - - /** - * Get data from array or object by path. - * Example: `DataCollector::getByPath($array, 'foo.bar.yoo')` equals to $array['foo']['bar']['yoo']. - * - * @param array|ArrayAccess $data An array or object to get value. - * @param mixed $path The key path. - * @param mixed $default - * @param string $separator Separator of paths. - * - * @return mixed Found value, null if not exists. - */ - public static function getByPath($data, string $path, $default = null, string $separator = '.') - { - if (isset($data[$path])) { - return $data[$path]; - } - - // Error: will clear '0'. eg 'some-key.0' - // if (!$nodes = array_filter(explode($separator, $path))) { - if (!$nodes = explode($separator, $path)) { - return $default; - } - - $dataTmp = $data; - - foreach ($nodes as $arg) { - if (is_object($dataTmp) && isset($dataTmp->$arg)) { - $dataTmp = $dataTmp->$arg; - } elseif ((is_array($dataTmp) || $dataTmp instanceof ArrayAccess) && isset($dataTmp[$arg])) { - $dataTmp = $dataTmp[$arg]; - } else { - return $default; - } - } - - return $dataTmp; - } - - /** - * findValueByNodes - * - * @param array $data - * @param array $nodes - * @param mixed $default - * - * @return mixed - */ - public static function getValueByNodes(array $data, array $nodes, $default = null) - { - $temp = $data; - - foreach ($nodes as $name) { - if (isset($temp[$name])) { - $temp = $temp[$name]; - } else { - $temp = $default; - break; - } - } - - return $temp; - } - - /** - * setByPath - * - * @param array|ArrayAccess &$data - * @param string $path - * @param mixed $value - * @param string $separator - */ - public static function setByPath(&$data, string $path, $value, string $separator = '.'): void - { - if (false === strpos($path, $separator)) { - $data[$path] = $value; - return; - } - - if (!$nodes = array_filter(explode($separator, $path))) { - return; - } - - $dataTmp = &$data; - - foreach ($nodes as $node) { - if (is_array($dataTmp)) { - if (empty($dataTmp[$node])) { - $dataTmp[$node] = []; - } - - $dataTmp = &$dataTmp[$node]; - } else { - // If a node is value but path is not go to the end, we replace this value as a new store. - // Then next node can insert new value to this store. - $dataTmp = []; - } - } - - // Now, path go to the end, means we get latest node, set value to this node. - $dataTmp = $value; - } - - //////////////////////////////////////////////////////////// - /// from laravel - //////////////////////////////////////////////////////////// - - /** - * Collapse an array of arrays into a single array. - * - * @param array $array - * - * @return array - */ - public static function collapse(array $array): array - { - $results = []; - - foreach ($array as $values) { - if ($values instanceof CollectionInterface) { - $values = $values->all(); - } elseif (!is_array($values)) { - continue; - } - - // $results = \array_merge($results, $values); - $results[] = $values; - } - - return array_merge(...$results); - } - - /** - * Cross join the given arrays, returning all possible permutations. - * - * @param array ...$arrays - * - * @return array - */ - public static function crossJoin(...$arrays): array - { - return array_reduce($arrays, function ($results, $array) { - return static::collapse(array_map(function ($parent) use ($array) { - return array_map(function ($item) use ($parent) { - return array_merge($parent, [$item]); - }, $array); - }, $results)); - }, [[]]); - } - - /** - * Divide an array into two arrays. One with keys and the other with values. - * - * @param array $array - * - * @return array - */ - public static function divide($array): array - { - return [array_keys($array), array_values($array)]; - } - - /** - * Flatten a multi-dimensional associative array with dots. - * - * @param array $array - * @param string $prepend - * - * @return array - */ - public static function dot(array $array, string $prepend = ''): array - { - $results = []; - - foreach ($array as $key => $value) { - if (is_array($value) && !empty($value)) { - $results = array_merge($results, static::dot($value, $prepend . $key . '.')); - } else { - $results[$prepend . $key] = $value; - } - } - - return $results; - } - - /** - * Get all of the given array except for a specified array of items. - * - * @param array $array - * @param array|string $keys - * - * @return array - */ - public static function except(array $array, $keys): array - { - static::forget($array, $keys); - - return $array; - } - - /** - * Determine if the given key exists in the provided array. - * - * @param ArrayAccess|array $array - * @param string|int $key - * - * @return bool - */ - public static function exists(array $array, $key): bool - { - if ($array instanceof ArrayAccess) { - return $array->offsetExists($key); - } - - return array_key_exists($key, $array); - } - - /** - * Add an element to an array using "dot" notation if it doesn't exist. - * - * @param array $array - * @param string $key - * @param mixed $value - * - * @return array - */ - public static function add(array $array, $key, $value): array - { - if (static::has($array, $key)) { - static::set($array, $key, $value); - } - - return $array; - } - - /** - * Get an item from an array using "dot" notation. - * - * @param ArrayAccess|array $array - * @param string $key - * @param mixed $default - * - * @return mixed - */ - public static function get($array, $key, $default = null) - { - if (!static::accessible($array)) { - return value($default); - } - - if (null === $key) { - return $array; - } - - if (static::exists($array, $key)) { - return $array[$key]; - } - - foreach (explode('.', $key) as $segment) { - if (static::accessible($array) && static::exists($array, $segment)) { - $array = $array[$segment]; - } else { - return value($default); - } - } - - return $array; - } - - /** - * Set an array item to a given value using "dot" notation. - * If no key is given to the method, the entire array will be replaced. - * - * @param array $array - * @param string $key - * @param mixed $value - * - * @return array - */ - public static function set(array &$array, $key, $value): array - { - if (null === $key) { - return ($array = $value); - } - - $keys = explode('.', $key); - - while (count($keys) > 1) { - $key = array_shift($keys); - // If the key doesn't exist at this depth, we will just create an empty array - // to hold the next value, allowing us to create the arrays to hold final - // values at the correct depth. Then we'll keep digging into the array. - if (!isset($array[$key]) || !is_array($array[$key])) { - $array[$key] = []; - } - - $array = &$array[$key]; - } - - $array[array_shift($keys)] = $value; - - return $array; - } - - /** - * Flatten a multi-dimensional array into a single level. - * - * @param array $array - * @param int $depth - * - * @return array - */ - public static function flatten($array, $depth = INF): array - { - return array_reduce($array, function ($result, $item) use ($depth) { - $item = $item instanceof CollectionInterface ? $item->all() : $item; - - if (!is_array($item)) { - return array_merge($result, [$item]); - } - - if ($depth === 1) { - return array_merge($result, array_values($item)); - } - - return array_merge($result, static::flatten($item, $depth - 1)); - }, []); - } - - /** - * Remove one or many array items from a given array using "dot" notation. - * - * @param array $array - * @param array|string $keys - * - * @return void - */ - public static function forget(&$array, $keys): void - { - $original = &$array; - $keys = (array)$keys; - - if (count($keys) === 0) { - return; - } - - foreach ($keys as $key) { - - // if the exact key exists in the top-level, remove it - if (static::exists($array, $key)) { - unset($array[$key]); - continue; - } - - $parts = explode('.', $key); - - // clean up before each pass - $array = &$original; - - while (count($parts) > 1) { - $part = array_shift($parts); - - if (isset($array[$part]) && is_array($array[$part])) { - $array = &$array[$part]; - } else { - continue 2; - } - } - - unset($array[array_shift($parts)]); - } - } - - /** - * Check if an item or items exist in an array using "dot" notation. - * - * @param ArrayAccess|array $array - * @param string|array $keys - * - * @return bool - */ - public static function has($array, $keys): bool - { - if (null === $keys) { - return false; - } - - $keys = (array)$keys; - - if (!$array) { - return false; - } - - if ($keys === []) { - return false; - } - - foreach ($keys as $key) { - $subKeyArray = $array; - - if (static::exists($array, $key)) { - continue; - } - - foreach (explode('.', $key) as $segment) { - if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) { - $subKeyArray = $subKeyArray[$segment]; - } else { - - return false; - } - } - } - - return true; - } - - /** - * Push an item onto the beginning of an array. - * - * @param array $array - * @param mixed $value - * @param mixed $key - * - * @return array - */ - public static function prepend($array, $value, $key = null): array - { - if (null === $key) { - array_unshift($array, $value); - } else { - $array = [$key => $value] + $array; - } - - return $array; - } - - /** - * remove the $key of the $arr, and return value. - * - * @param string $key - * @param array $arr - * @param mixed $default - * - * @return mixed - */ - public static function remove(&$arr, $key, $default = null) - { - if (isset($arr[$key])) { - $value = $arr[$key]; - unset($arr[$key]); - } else { - $value = $default; - } - - return $value; - } - - /** - * Get a value from the array, and remove it. - * - * @param array $array - * @param string $key - * @param mixed $default - * - * @return mixed - */ - public static function pull(&$array, $key, $default = null) - { - $value = static::get($array, $key, $default); - - static::forget($array, $key); - - return $value; - } - - /** - * Get a subset of the items from the given array. - * - * @param array $array - * @param array|string $keys - * - * @return array - */ - public static function only($array, $keys): array - { - return array_intersect_key($array, array_flip((array)$keys)); - } - - /** - * Shuffle the given array and return the result. - * - * @param array $array - * - * @return array - */ - public static function shuffle($array): array - { - shuffle($array); - - return $array; - } - - /** - * Filter the array using the given callback. - * - * @param array $array - * @param callable $callback - * - * @return array - */ - public static function where($array, callable $callback): array - { - return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); - } - - /** - * If the given value is not an array, wrap it in one. - * - * @param mixed $value - * - * @return array - */ - public static function wrap($value): array - { - return !is_array($value) ? (array)$value : $value; - } - - //////////////////////////////////////////////////////////// - /// other - //////////////////////////////////////////////////////////// - - /** - * array 递归 转换成 字符串 - * - * @param array $array [大于1200字符 strlen($string)>1200 - * @param int $length - * @param array|int $cycles [至多循环六次 $num >= 6 - * @param bool $showKey - * @param bool $addMark - * @param string $separator - * @param string $string - * - * @return string - */ - public static function toString( - $array, - $length = 800, - $cycles = 6, - $showKey = true, - $addMark = false, - $separator = ', ', - $string = '' - ): string { - if (!is_array($array) || empty($array)) { - return ''; - } - - $mark = $addMark ? '\'' : ''; - $num = 0; - - foreach ($array as $key => $value) { - $num++; - - if ($num >= $cycles || strlen($string) > (int)$length) { - $string .= '... ...'; - break; - } - - $keyStr = $showKey ? $key . '=>' : ''; - - if (is_array($value)) { - $string .= $keyStr . 'Array(' . self::toString($value, $length, $cycles, $showKey, $addMark, $separator, - $string) . ')' . $separator; - } elseif (is_object($value)) { - $string .= $keyStr . 'Object(' . get_class($value) . ')' . $separator; - } elseif (is_resource($value)) { - $string .= $keyStr . 'Resource(' . get_resource_type($value) . ')' . $separator; - } else { - $value = strlen($value) > 150 ? substr($value, 0, 150) : $value; - $string .= $mark . $keyStr . trim(htmlspecialchars($value)) . $mark . $separator; - } - } - - return trim($string, $separator); - } - - public static function toStringNoKey( - $array, - $length = 800, - $cycles = 6, - $showKey = false, - $addMark = true, - $separator = ', ' - ): string { - return static::toString($array, $length, $cycles, $showKey, $addMark, $separator); - } - - /** - * @param array $array - * @param int $length - * - * @return mixed|null|string|string[] - */ - public static function toFormatString($array, $length = 400) - { - $string = var_export($array, true); - - # 将非空格替换为一个空格 - $string = preg_replace('/[\n\r\t]/', ' ', $string); - # 去掉两个空格以上的 - $string = preg_replace('/\s(?=\s)/', '', $string); - $string = trim($string); - - if (strlen($string) > $length) { - $string = substr($string, 0, $length) . '...'; - } - - return $string; - } - - public static function toLimitOut($array): array - { - if (!is_array($array)) { - return $array; - } - - // static $num = 1; - - foreach ($array as $key => $value) { - // if ( $num >= $cycles) { - // break; - // } - - if (is_array($value) || is_object($value)) { - $value = gettype($value) . '(...)'; - } elseif (is_string($value) || is_numeric($value)) { - $value = strlen(trim($value)); - } else { - $value = gettype($value) . "($value)"; - } - - $array[$key] = $value; - } - - // $num++; - - return $array; - } - -} diff --git a/libs/arr-utils/src/FixedArray.php b/libs/arr-utils/src/FixedArray.php deleted file mode 100644 index 67aa6d1..0000000 --- a/libs/arr-utils/src/FixedArray.php +++ /dev/null @@ -1,219 +0,0 @@ - 'int:value index' - * ] - */ - protected $keys; - - /** - * @var SplFixedArray - */ - protected $values; - - /** - * FixedArray constructor. - * - * @param int $size - */ - public function __construct(int $size = 0) - { - $this->keys = []; - $this->values = new SplFixedArray($size); - } - - /** - * reset - * - * @param int $size - */ - public function reset(int $size = 0) - { - $this->keys = []; - $this->values = new SplFixedArray($size); - } - - /** - * @param string $key - * - * @return bool - */ - public function __isset(string $key) - { - return $this->offsetExists($key); - } - - /** - * @param string $key - * @param mixed $value - */ - public function __set(string $key, $value) - { - $this->offsetSet($key, $value); - } - - /** - * @param string $key - * - * @return mixed - */ - public function __get(string $key) - { - return $this->offsetGet($key); - } - - /** - * @return int - */ - public function getSize(): int - { - return $this->values->getSize(); - } - - /** - * @param $key - * - * @return int - */ - public function getKeyIndex($key): int - { - return $this->keys[$key] ?? -1; - } - - /** - * @return array - */ - public function getKeys(): array - { - return $this->keys; - } - - /** - * @param array $keys - */ - public function setKeys(array $keys) - { - $this->keys = $keys; - } - - /** - * @return SplFixedArray - */ - public function getValues(): SplFixedArray - { - return $this->values; - } - - /** - * @param SplFixedArray $values - */ - public function setValues(SplFixedArray $values) - { - $this->values = $values; - } - - /** - * Defined by IteratorAggregate interface - * Returns an iterator for this object, for use with foreach - * - * @return SplFixedArray - */ - public function getIterator(): SplFixedArray - { - return $this->values; - } - - /** - * Checks whether an offset exists in the iterator. - * - * @param mixed $offset The array offset. - * - * @return boolean True if the offset exists, false otherwise. - */ - public function offsetExists($offset): bool - { - return isset($this->keys[$offset]); - } - - /** - * Gets an offset in the iterator. - * - * @param mixed $offset The array offset. - * - * @return mixed The array value if it exists, null otherwise. - */ - public function offsetGet($offset) - { - $index = $this->getKeyIndex($offset); - - if ($index >= 0) { - return $this->values[$index]; - } - - return null; - } - - /** - * Sets an offset in the iterator. - * - * @param mixed $offset The array offset. - * @param mixed $value The array value. - * - * @return void - */ - public function offsetSet($offset, $value): void - { - $index = $this->getSize(); - - // change size. - if ($index <= count($this->keys)) { - $this->values->setSize($index + 10); - } - - $this->values[$index] = $value; - $this->keys[$offset] = $index; - } - - /** - * Unset an offset in the iterator. - * - * @param mixed $offset The array offset. - * - * @return void - */ - public function offsetUnset($offset): void - { - $index = $this->getKeyIndex($offset); - - if ($index >= 0) { - // change size. - $this->values->setSize($index - 1); - - unset($this->keys[$offset], $this->values[$index]); - } - } -} diff --git a/libs/arr-utils/test/boot.php b/libs/arr-utils/test/boot.php deleted file mode 100644 index 96a1e68..0000000 --- a/libs/arr-utils/test/boot.php +++ /dev/null @@ -1,27 +0,0 @@ -highlight(file_get_contents(__FILE__)); - -\Toolkit\Cli\Cli::write($rendered); -``` - -![colors](./example/cli-php-file-highlight.jpg) - -## Console color - -![colors](./example/all-color-style.jpg) - -## Cli downloader - -```php -use Toolkit\Cli\Download; - -$url = 'http://no2.php.net/distributions/php-7.2.5.tar.bz2'; -$down = Download::file($url, ''); - -// $down->setShowType('bar'); -$down->start(); -``` - -### progress bar output: - -```text -Connected... -Mime-type: text/html; charset=utf-8 -Being redirected to: http://no2.php.net/distributions/php-7.2.5.tar.bz2 -Connected... -FileSize: 14280 kb -Mime-type: application/octet-stream -[========================================> ] 40% (3076/7590 kb) -``` - -### progress text output: - -```text -Download: http://no2.php.net/distributions/php-7.2.5.tar.bz2 -Save As: /path/to/php-7.2.5.tar.bz2 - -Connected ... -Got the file size: 14280 kb -Found the mime-type: application/octet-stream -Made some progress, downloaded 641 kb so far -``` - -## License - -MIT diff --git a/libs/cli-utils/composer.json b/libs/cli-utils/composer.json deleted file mode 100644 index 9b49241..0000000 --- a/libs/cli-utils/composer.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "toolkit/cli-utils", - "type": "library", - "description": "some cli tool library of the php", - "keywords": [ - "library", - "tool", - "php" - ], - "homepage": "https://github.com/php-toolkit/cli-utils", - "license": "MIT", - "authors": [ - { - "name": "inhere", - "email": "in.798@qq.com", - "homepage": "http://www.yzone.net/" - } - ], - "require": { - "php": ">7.1.0" - }, - "autoload": { - "psr-4": { - "Toolkit\\Cli\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Toolkit\\CliTest\\": "src/" - } - }, - "suggest": { - "inhere/php-validate": "Very lightweight data validate tool", - "inhere/console": "a lightweight php console application library." - } -} diff --git a/libs/cli-utils/example/all-color-style.jpg b/libs/cli-utils/example/all-color-style.jpg deleted file mode 100644 index 850acae5939d18d2690cb8c0c4e56bf94da98c90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102239 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

    nvOcGH)O?T2xf$|OrJhyFyco4#BC}y4JXANg8Kl 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 z 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 N1lrbu=wknw`9G-w13Ul# diff --git a/libs/cli-utils/example/color.php b/libs/cli-utils/example/color.php deleted file mode 100644 index 0a8058e..0000000 --- a/libs/cli-utils/example/color.php +++ /dev/null @@ -1,15 +0,0 @@ -getOpt('type', 'text'); - -if ($type === 'bar') { - $down->setShowType($type); -} - -$down->start(); diff --git a/libs/cli-utils/example/high.php b/libs/cli-utils/example/high.php deleted file mode 100644 index ae5b5a5..0000000 --- a/libs/cli-utils/example/high.php +++ /dev/null @@ -1,19 +0,0 @@ -highlight(file_get_contents(__FILE__)); - -Cli::write($rendered); diff --git a/libs/cli-utils/example/liteApp b/libs/cli-utils/example/liteApp deleted file mode 100644 index 2855582..0000000 --- a/libs/cli-utils/example/liteApp +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env php -addCommand('test', function ($app) { - echo "args:\n"; - /** @var Toolkit\Cli\App $app */ - print_r($app->getArgs()); - -}, 'the description text for the command: test'); - -$app->addCommand('test1', function () { - echo "hello\n"; -}, 'the description text for the command: test1'); - -// run -$app->run(); diff --git a/libs/cli-utils/src/App.php b/libs/cli-utils/src/App.php deleted file mode 100644 index 97596d5..0000000 --- a/libs/cli-utils/src/App.php +++ /dev/null @@ -1,579 +0,0 @@ - '', - 'usage' => '', - 'help' => '', - ]; - - /** @var string Current dir */ - private $pwd; - - /** - * @var array Parsed from `arg0 name=val var2=val2` - */ - private $args = []; - - /** - * @var array Parsed from `--name=val --var2=val2 -d` - */ - private $opts = []; - - /** - * @var string - */ - private $script; - - /** - * @var string - */ - private $command = ''; - - /** - * @var array User add commands - */ - private $commands = []; - - /** - * @var array Command messages for the commands - */ - private $messages = []; - - /** - * @var int - */ - private $keyWidth = 12; - - /** - * Class constructor. - * - * @param array|null $argv - */ - public function __construct(array $argv = null) - { - // get current dir - $this->pwd = getcwd(); - - // parse cli argv - $argv = $argv ?? (array)$_SERVER['argv']; - - // get script file - $this->script = array_shift($argv); - - // parse flags - [$this->args, $this->opts] = Flags::parseArgv($argv, [ - 'mergeOpts' => true - ]); - } - - /** - * @param bool $exit - * - * @throws InvalidArgumentException - */ - public function run(bool $exit = true): void - { - $this->findCommand(); - - $this->dispatch($exit); - } - - /** - * find command name. it is first argument. - */ - protected function findCommand(): void - { - if (!isset($this->args[0])) { - return; - } - - $newArgs = []; - - foreach ($this->args as $key => $value) { - if ($key === 0) { - $this->command = trim($value); - } elseif (is_int($key)) { - $newArgs[] = $value; - } else { - $newArgs[$key] = $value; - } - } - - $this->args = $newArgs; - } - - /** - * @param bool $exit - * - * @throws InvalidArgumentException - */ - public function dispatch(bool $exit = true): void - { - if (!$command = $this->command) { - $this->displayHelp(); - return; - } - - if (!isset($this->commands[$command])) { - $this->displayHelp("The command '{$command}' is not exists!"); - return; - } - - if (isset($this->opts['h']) || isset($this->opts['help'])) { - $this->displayCommandHelp($command); - return; - } - - try { - $status = $this->runHandler($command, $this->commands[$command]); - } catch (Throwable $e) { - $status = $this->handleException($e); - } - - if ($exit) { - $this->stop($status); - } - } - - /** - * @param int $code - */ - public function stop($code = 0): void - { - exit((int)$code); - } - - /** - * @param string $command - * @param $handler - * - * @return mixed - * @throws InvalidArgumentException - */ - public function runHandler(string $command, $handler) - { - if (is_string($handler)) { - // function name - if (function_exists($handler)) { - return $handler($this); - } - - if (class_exists($handler)) { - $handler = new $handler; - - // $handler->execute() - if (method_exists($handler, 'execute')) { - return $handler->execute($this); - } - } - } - - // a \Closure OR $handler->__invoke() - if (is_object($handler) && method_exists($handler, '__invoke')) { - return $handler($this); - } - - throw new RuntimeException("Invalid handler of the command: $command"); - } - - /** - * @param Throwable $e - * - * @return int - */ - protected function handleException(Throwable $e): int - { - if ($e instanceof InvalidArgumentException) { - Color::println('ERROR: ' . $e->getMessage(), 'error'); - return 0; - } - - $code = $e->getCode() !== 0 ? $e->getCode() : -1; - $eTpl = "Exception(%d): %s\nFile: %s(Line %d)\nTrace:\n%s\n"; - - // print exception message - printf($eTpl, $code, $e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString()); - - return $code; - } - - /** - * @param callable $handler - * @param array $config - */ - public function addByConfig(callable $handler, array $config): void - { - $this->addCommand($config['name'], $handler, $config); - } - - /** - * @param string $command - * @param callable $handler - * @param null|array|string $config - */ - public function add(string $command, callable $handler, $config = null): void - { - $this->addCommand($command, $handler, $config); - } - - /** - * @param string $command - * @param callable $handler - * @param null|array|string $config - */ - public function addCommand(string $command, callable $handler, $config = null): void - { - if (!$command || !$handler) { - throw new InvalidArgumentException('Invalid arguments for add command'); - } - - if (($len = strlen($command)) > $this->keyWidth) { - $this->keyWidth = $len; - } - - $this->commands[$command] = $handler; - - if (is_string($config)) { - $desc = trim($config); - $config = self::COMMAND_CONFIG; - - // append desc - $config['desc'] = $desc; - - // save - $this->messages[$command] = $config; - } elseif (is_array($config)) { - $this->messages[$command] = array_merge(self::COMMAND_CONFIG, $config); - } - } - - /** - * @param array $commands - * - * @throws InvalidArgumentException - */ - public function commands(array $commands): void - { - foreach ($commands as $command => $handler) { - $desc = ''; - - if (is_array($handler)) { - $conf = array_values($handler); - $handler = $conf[0]; - $desc = $conf[1] ?? ''; - } - - $this->addCommand($command, $handler, $desc); - } - } - - /**************************************************************************** - * helper methods - ****************************************************************************/ - - /** - * @param string $err - */ - public function displayHelp(string $err = ''): void - { - if ($err) { - echo Color::render("ERROR: $err\n\n"); - } - - // help - $len = $this->keyWidth; - $help = "Welcome to the Lite Console Application.\n\nAvailable Commands:\n"; - $data = $this->messages; - ksort($data); - - foreach ($data as $command => $item) { - $command = str_pad($command, $len, ' '); - $desc = $item['desc'] ? ucfirst($item['desc']) : 'No description for the command'; - $help .= " $command $desc\n"; - } - - echo Color::render($help) . PHP_EOL; - exit(0); - } - - /** - * @param string $name - */ - public function displayCommandHelp(string $name): void - { - $checkVar = false; - $fullCmd = $this->script . " $name"; - - $config = $this->messages[$name] ?? []; - $usage = "$fullCmd [args ...] [--opts ...]"; - - if (!$config) { - $nodes = [ - 'No description for the command', - "Usage: \n $usage" - ]; - } else { - $checkVar = true; - $userHelp = $config['help']; - - $nodes = [ - ucfirst($config['desc']), - "Usage: \n " . ($config['usage'] ?: $usage), - $userHelp ? $userHelp . "\n" : '' - ]; - } - - $help = implode("\n", $nodes); - - if ($checkVar && strpos($help, '{{')) { - $help = strtr($help, [ - '{{command}}' => $name, - '{{fullCmd}}' => $fullCmd, - '{{workDir}}' => $this->pwd, - '{{pwdDir}}' => $this->pwd, - '{{script}}' => $this->script, - ]); - } - - echo Color::render($help); - } - - /** - * @param string|int $name - * @param mixed $default - * - * @return mixed|null - */ - public function getArg($name, $default = null) - { - return $this->args[$name] ?? $default; - } - - /** - * @param string|int $name - * @param int $default - * - * @return int - */ - public function getIntArg($name, int $default = 0): int - { - return (int)$this->getArg($name, $default); - } - - /** - * @param string|int $name - * @param string $default - * - * @return string - */ - public function getStrArg($name, string $default = ''): string - { - return (string)$this->getArg($name, $default); - } - - /** - * @param string $name - * @param mixed $default - * - * @return mixed|null - */ - public function getOpt(string $name, $default = null) - { - return $this->opts[$name] ?? $default; - } - - /** - * @param string $name - * @param int $default - * - * @return int - */ - public function getIntOpt(string $name, int $default = 0): int - { - return (int)$this->getOpt($name, $default); - } - - /** - * @param string $name - * @param string $default - * - * @return string - */ - public function getStrOpt(string $name, string $default = ''): string - { - return (string)$this->getOpt($name, $default); - } - - /** - * @param string $name - * @param bool $default - * - * @return bool - */ - public function getBoolOpt(string $name, bool $default = false): bool - { - return (bool)$this->getOpt($name, $default); - } - - /**************************************************************************** - * getter/setter methods - ****************************************************************************/ - - /** - * @return array - */ - public function getArgs(): array - { - return $this->args; - } - - /** - * @param array $args - */ - public function setArgs(array $args): void - { - $this->args = $args; - } - - /** - * @return array - */ - public function getOpts(): array - { - return $this->opts; - } - - /** - * @param array $opts - */ - public function setOpts(array $opts): void - { - $this->opts = $opts; - } - - /** - * @return string - */ - public function getScript(): string - { - return $this->script; - } - - /** - * @param string $script - */ - public function setScript(string $script): void - { - $this->script = $script; - } - - /** - * @return string - */ - public function getCommand(): string - { - return $this->command; - } - - /** - * @param string $command - */ - public function setCommand(string $command): void - { - $this->command = $command; - } - - /** - * @return array - */ - public function getCommands(): array - { - return $this->commands; - } - - /** - * @param array $commands - */ - public function setCommands(array $commands): void - { - $this->commands = $commands; - } - - /** - * @return array - */ - public function getMessages(): array - { - return $this->messages; - } - - /** - * @param array $messages - */ - public function setMessages(array $messages): void - { - $this->messages = $messages; - } - - /** - * @return int - */ - public function getKeyWidth(): int - { - return $this->keyWidth; - } - - /** - * @param int $keyWidth - */ - public function setKeyWidth(int $keyWidth): void - { - $this->keyWidth = $keyWidth > 1 ? $keyWidth : 12; - } - - /** - * @return string - */ - public function getPwd(): string - { - return $this->pwd; - } - -} diff --git a/libs/cli-utils/src/Cli.php b/libs/cli-utils/src/Cli.php deleted file mode 100644 index d6ba2d0..0000000 --- a/libs/cli-utils/src/Cli.php +++ /dev/null @@ -1,266 +0,0 @@ - 'info', - 'warn' => 'warning', - 'warning' => 'warning', - 'debug' => 'cyan', - 'notice' => 'notice', - 'error' => 'error', - ]; - - /******************************************************************************* - * read/write message - ******************************************************************************/ - - /** - * @param string $message - * @param bool $nl - * - * @return string - */ - public static function read(string $message = '', bool $nl = false): string - { - if ($message) { - self::write($message, $nl); - } - - return trim(fgets(STDIN)); - } - - /** - * @param string $format - * @param mixed ...$args - */ - public static function writef(string $format, ...$args): void - { - self::write(sprintf($format, ...$args)); - } - - /** - * Write message to console - * - * @param string|array $messages - * @param bool $nl - * @param bool|int $quit - */ - public static function write($messages, bool $nl = true, $quit = false): void - { - if (is_array($messages)) { - $messages = implode($nl ? PHP_EOL : '', $messages); - } - - self::stdout(Color::parseTag($messages), $nl, $quit); - } - - /** - * Logs data to stdout - * - * @param string $message - * @param bool $nl - * @param bool|int $quit - */ - public static function stdout(string $message, bool $nl = true, $quit = false): void - { - fwrite(STDOUT, $message . ($nl ? PHP_EOL : '')); - fflush(STDOUT); - - if (($isTrue = true === $quit) || is_int($quit)) { - $code = $isTrue ? 0 : $quit; - exit($code); - } - } - - /** - * Logs data to stderr - * - * @param string $message - * @param bool $nl - * @param bool|int $quit - */ - public static function stderr(string $message, $nl = true, $quit = -1): void - { - fwrite(STDERR, self::color('[ERROR] ', 'red') . $message . ($nl ? PHP_EOL : '')); - fflush(STDOUT); - - if (($isTrue = true === $quit) || is_int($quit)) { - $code = $isTrue ? 0 : $quit; - exit($code); - } - } - - /******************************************************************************* - * color render - ******************************************************************************/ - - /** - * @param $text - * @param string|int|array $style - * - * @return string - */ - public static function color(string $text, $style = null): string - { - return Color::render($text, $style); - } - - /** - * print log to console - * - * @param string $msg - * @param array $data - * @param string $type - * @param array $opts - * [ - * '_category' => 'application', - * 'process' => 'work', - * 'pid' => 234, - * 'coId' => 12, - * ] - */ - public static function log(string $msg, array $data = [], string $type = 'info', array $opts = []): void - { - if (isset(self::LOG_LEVEL2TAG[$type])) { - $type = ColorTag::add(strtoupper($type), self::LOG_LEVEL2TAG[$type]); - } - - $userOpts = []; - - foreach ($opts as $n => $v) { - if (is_numeric($n) || strpos($n, '_') === 0) { - $userOpts[] = "[$v]"; - } else { - $userOpts[] = "[$n:$v]"; - } - } - - $optString = $userOpts ? ' ' . implode(' ', $userOpts) : ''; - $dataString = $data ? PHP_EOL . json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) : ''; - - self::writef('%s [%s]%s %s %s', date('Y/m/d H:i:s'), $type, $optString, trim($msg), $dataString); - } - - /******************************************************************************* - * some helpers - ******************************************************************************/ - - /** - * @return bool - */ - public static function supportColor(): bool - { - return self::isSupportColor(); - } - - /** - * Returns true if STDOUT supports colorization. - * This code has been copied and adapted from - * \Symfony\Component\Console\Output\OutputStream. - * - * @return boolean - */ - public static function isSupportColor(): bool - { - if (DIRECTORY_SEPARATOR === '\\') { - return '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD || - false !== getenv('ANSICON') || - 'ON' === getenv('ConEmuANSI') || - 'xterm' === getenv('TERM')// || 'cygwin' === getenv('TERM') - ; - } - - if (!defined('STDOUT')) { - return false; - } - - return self::isInteractive(STDOUT); - } - - /** - * @return bool - */ - public static function isSupport256Color(): bool - { - return DIRECTORY_SEPARATOR === '/' && strpos(getenv('TERM'), '256color') !== false; - } - - /** - * @return bool - */ - public static function isAnsiSupport(): bool - { - if (DIRECTORY_SEPARATOR === '\\') { - return getenv('ANSICON') === true || getenv('ConEmuANSI') === 'ON'; - } - - return true; - } - - /** - * Returns if the file descriptor is an interactive terminal or not. - * - * @param int|resource $fileDescriptor - * - * @return boolean - */ - public static function isInteractive($fileDescriptor): bool - { - /** @noinspection PhpComposerExtensionStubsInspection */ - return function_exists('posix_isatty') && @posix_isatty($fileDescriptor); - } - - /** - * clear Ansi Code - * - * @param string $string - * - * @return string - */ - public static function stripAnsiCode(string $string): string - { - return preg_replace('/\033\[[\d;?]*\w/', '', $string); - } -} diff --git a/libs/cli-utils/src/Color.php b/libs/cli-utils/src/Color.php deleted file mode 100644 index c583648..0000000 --- a/libs/cli-utils/src/Color.php +++ /dev/null @@ -1,340 +0,0 @@ - '0;31', - 'blue' => '0;34', - 'cyan' => '0;36', - 'black' => '0;30', - 'green' => '0;32', - 'brown' => '0;33', - 'white' => '1;37', - 'normal' => '39',// no color - 'yellow' => '1;33', - 'magenta' => '1;35', - - // alert - 'suc' => '1;32',// same 'green' and 'bold' - 'success' => '1;32', - 'info' => '0;32',// same 'green' - 'comment' => '0;33',// same 'brown' - 'note' => '36;1', - 'notice' => '36;4', - 'warn' => '0;30;43', - 'warning' => '0;30;43', - 'danger' => '0;31',// same 'red' - 'err' => '97;41', - 'error' => '97;41', - - // more - 'lightRed' => '1;31', - 'light_red' => '1;31', - 'lightGreen' => '1;32', - 'light_green' => '1;32', - 'lightBlue' => '1;34', - 'light_blue' => '1;34', - 'lightCyan' => '1;36', - 'light_cyan' => '1;36', - 'lightDray' => '37', - 'light_gray' => '37', - - 'darkDray' => '90', - 'dark_gray' => '90', - 'lightYellow' => '93', - 'light_yellow' => '93', - 'lightMagenta' => '95', - 'light_magenta' => '95', - - // extra - 'lightRedEx' => '91', - 'light_red_ex' => '91', - 'lightGreenEx' => '92', - 'light_green_ex' => '92', - 'lightBlueEx' => '94', - 'light_blue_ex' => '94', - 'lightCyanEx' => '96', - 'light_cyan_ex' => '96', - 'whiteEx' => '97', - 'white_ex' => '97', - - // option - 'bold' => '1', - 'underscore' => '4', - 'reverse' => '7', - ]; - - // Regex to match color tags - public const COLOR_TAG = '/<([a-z=;]+)>(.*?)<\/\\1>/s'; - - // CLI color template - public const COLOR_TPL = "\033[%sm%s\033[0m"; - - /** - * @param string $method - * @param array $args - * - * @return string - */ - public static function __callStatic(string $method, array $args) - { - if (isset(self::STYLES[$method])) { - return self::render($args[0], $method); - } - - return ''; - } - - /** - * Apply style for text - * - * @param string $style - * @param string $text - * - * @return string - */ - public static function apply(string $style, string $text): string - { - return self::render($text, $style); - } - - /** - * Format and print to STDOUT - * - * @param string $format - * @param mixed ...$args - */ - public static function printf(string $format, ...$args): void - { - echo self::render(sprintf($format, ...$args)); - } - - /** - * Print colored message to STDOUT - * - * @param string|array $messages - * @param string $style - */ - public static function println($messages, string $style = 'info'): void - { - $string = is_array($messages) ? implode("\n", $messages) : (string)$messages; - - echo self::render($string . "\n", $style); - } - - /******************************************************************************* - * color render - ******************************************************************************/ - - /** - * Render text, apply color code - * - * @param string $text - * @param string|array $style - * - string: 'green', 'blue' - * - array: [Color::FG_GREEN, Color::BG_WHITE, Color::UNDERSCORE] - * - * @return string - */ - public static function render(string $text, $style = null): string - { - if (!$text) { - return $text; - } - - if (!Cli::isSupportColor()) { - return self::clearColor($text); - } - - // use defined style: 'green' - if (is_string($style)) { - $color = self::STYLES[$style] ?? '0'; - - // custom style: [self::FG_GREEN, self::BG_WHITE, self::UNDERSCORE] - } elseif (is_array($style)) { - $color = implode(';', $style); - - // user color tag: message - } elseif (strpos($text, ' 0) { - return self::parseTag($text); - } else { - return $text; - } - - // $result = chr(27). "$color{$text}" . chr(27) . chr(27) . "[0m". chr(27); - return sprintf(self::COLOR_TPL, $color, $text); - } - - /** - * parse color tag e.g: message - * - * @param string $text - * - * @return mixed|string - */ - public static function parseTag(string $text) - { - if (!$text || false === strpos($text, ' $m) { - if ($style = self::STYLES[$matches[1][$i]] ?? null) { - $tag = $matches[1][$i]; - $match = $matches[2][$i]; - - $repl = sprintf("\033[%sm%s\033[0m", $style, $match); - $text = str_replace("<$tag>$match", $repl, $text); - } - } - - return $text; - } - - /** - * @param string $text - * @param bool $stripTag - * - * @return string - */ - public static function clearColor(string $text, bool $stripTag = true): string - { - // return preg_replace('/\033\[(?:\d;?)+m/', '' , "\033[0;36mtext\033[0m"); - return preg_replace('/\033\[(?:\d;?)+m/', '', $stripTag ? strip_tags($text) : $text); - } - - /** - * @param string $style - * - * @return bool - */ - public static function hasStyle(string $style): bool - { - return isset(self::STYLES[$style]); - } - - /** - * get all style names - * - * @return array - */ - public static function getStyles(): array - { - return array_filter(array_keys(self::STYLES), function ($style) { - return !strpos($style, '_'); - }); - } -} diff --git a/libs/cli-utils/src/ColorTag.php b/libs/cli-utils/src/ColorTag.php deleted file mode 100644 index bc6f6c1..0000000 --- a/libs/cli-utils/src/ColorTag.php +++ /dev/null @@ -1,117 +0,0 @@ -/'; - - // Regex to match tags/ - public const MATCH_TAG = '/<([a-zA-Z=;_]+)>(.*?)<\/\\1>/s'; - - /** - * Alias of the wrap() - * - * @param string $text - * @param string $tag - * - * @return string - */ - public static function add(string $text, string $tag): string - { - return self::wrap($text, $tag); - } - - /** - * wrap a color style tag - * - * @param string $text - * @param string $tag - * - * @return string - */ - public static function wrap(string $text, string $tag): string - { - if (!$text || !$tag) { - return $text; - } - - return "<$tag>$text"; - } - - /** - * @param string $text - * - * @return array - */ - public static function matchAll(string $text): array - { - if (!preg_match_all(self::MATCH_TAG, $text, $matches)) { - return []; - } - - return $matches; - } - - public static function parse(string $text): string - { - return ''; - } - - /** - * Exists color tags - * - * @param string $text - * - * @return bool - */ - public static function exists(string $text): bool - { - return strpos($text, ' 0; - } - - /** - * Alias of the strip() - * - * @param string $text - * - * @return string - */ - public static function clear(string $text): string - { - return self::strip($text); - } - - /** - * Strip color tags from a string. - * - * @param string $text - * - * @return mixed - */ - public static function strip(string $text): string - { - if (false === strpos($text, ' - * - * @param string $url - * @param string $saveAs - * @param string $type - * - * @return Download - * @throws RuntimeException - */ - public static function file(string $url, string $saveAs = '', string $type = self::PROGRESS_TEXT): Download - { - $d = new self($url, $saveAs, $type); - - return $d->start(); - } - - /** - * Download constructor. - * - * @param string $url - * @param string $saveAs - * @param string $type - */ - public function __construct(string $url, string $saveAs = '', $type = self::PROGRESS_TEXT) - { - $this->setUrl($url); - $this->setSaveAs($saveAs); - - $this->showType = $type === self::PROGRESS_BAR ? self::PROGRESS_BAR : self::PROGRESS_TEXT; - } - - /** - * start download - * - * @return $this - * @throws RuntimeException - */ - public function start(): self - { - if (!$this->url) { - throw new RuntimeException("Please the property 'url' and 'saveAs'.", -1); - } - - // default save to current dir. - if (!$save = $this->saveAs) { - $save = getcwd() . '/' . basename($this->url); - // reset - $this->saveAs = $save; - } - - $ctx = stream_context_create(); - - // register stream notification callback - stream_context_set_params($ctx, [ - 'notification' => [$this, 'progressShow'] - ]); - - Cli::write("Download: {$this->url}\nSave As: {$save}\n"); - - $fp = fopen($this->url, 'rb', false, $ctx); - - if (is_resource($fp) && file_put_contents($save, $fp)) { - Cli::write("\nDone!"); - } else { - $err = error_get_last(); - Cli::stderr("\nErr.rrr..orr...\n {$err['message']}\n", true, -1); - } - - // close resource - if (is_resource($fp)) { - fclose($fp); - } - - $this->fileSize = null; - return $this; - } - - /** - * @param int $notifyCode stream notify code - * @param int $severity severity code - * @param string $message Message text - * @param int $messageCode Message code - * @param int $transferredBytes Have been transferred bytes - * @param int $maxBytes Target max length bytes - */ - public function progressShow($notifyCode, $severity, $message, $messageCode, $transferredBytes, $maxBytes): void - { - $msg = ''; - - switch ($notifyCode) { - case STREAM_NOTIFY_RESOLVE: - case STREAM_NOTIFY_AUTH_REQUIRED: - case STREAM_NOTIFY_COMPLETED: - case STREAM_NOTIFY_FAILURE: - case STREAM_NOTIFY_AUTH_RESULT: - $msg = "NOTIFY: $message(NO: $messageCode, Severity: $severity)"; - /* Ignore */ - break; - - case STREAM_NOTIFY_REDIRECTED: - $msg = "Being redirected to: $message"; - break; - - case STREAM_NOTIFY_CONNECT: - $msg = 'Connected ...'; - break; - - case STREAM_NOTIFY_FILE_SIZE_IS: - $this->fileSize = $maxBytes; - // print size - $size = sprintf('%2d', $maxBytes / 1024); - $msg = "Got the file size: $size kb"; - break; - - case STREAM_NOTIFY_MIME_TYPE_IS: - $msg = "Found the mime-type: $message"; - break; - - case STREAM_NOTIFY_PROGRESS: - if ($transferredBytes > 0) { - $this->showProgressByType($transferredBytes); - } - break; - } - - $msg && Cli::write($msg); - } - - /** - * @param $transferredBytes - * - * @return string - */ - public function showProgressByType($transferredBytes): string - { - if ($transferredBytes <= 0) { - return ''; - } - - $tfKb = $transferredBytes / 1024; - - if ($this->showType === self::PROGRESS_BAR) { - $size = $this->fileSize; - - if ($size === null) { - printf("\rUnknown file size... %2d kb done..", $tfKb); - } else { - $length = ceil(($transferredBytes / $size) * 100); // ■ = - printf("\r[%-100s] %d%% (%2d/%2d kb)", str_repeat('=', $length) . '>', $length, $tfKb, $size / 1024); - } - } else { - printf("\r\rMade some progress, downloaded %2d kb so far", $tfKb); - //$msg = "Made some progress, downloaded $transferredBytes so far"; - } - - return ''; - } - - /** - * @return string - */ - public function getShowType(): string - { - return $this->showType; - } - - /** - * @param string $showType - */ - public function setShowType(string $showType): void - { - $this->showType = $showType; - } - - /** - * @return string - */ - public function getUrl(): string - { - return $this->url; - } - - /** - * @param string $url - */ - public function setUrl(string $url): void - { - $this->url = trim($url); - } - - /** - * @return string - */ - public function getSaveAs(): string - { - return $this->saveAs; - } - - /** - * @param string $saveAs - */ - public function setSaveAs(string $saveAs): void - { - $this->saveAs = trim($saveAs); - } -} diff --git a/libs/cli-utils/src/Flags.php b/libs/cli-utils/src/Flags.php deleted file mode 100644 index c23ef9d..0000000 --- a/libs/cli-utils/src/Flags.php +++ /dev/null @@ -1,327 +0,0 @@ - $value) { - // opts - if (strpos($value, '-') === 0) { - $value = trim($value, '-'); - - // invalid - if (!$value) { - continue; - } - - if (strpos($value, '=')) { - [$n, $v] = explode('=', $value); - $opts[$n] = $v; - } else { - $opts[$value] = true; - } - } elseif (strpos($value, '=')) { - [$n, $v] = explode('=', $value); - $args[$n] = $v; - } else { - $args[] = $value; - } - } - - return [$args, $opts]; - } - - /** - * Parses $GLOBALS['argv'] for parameters and assigns them to an array. - * eg: - * - * ``` - * php cli.php run name=john city=chengdu -s=test --page=23 -d -rf --debug --task=off -y=false -D -e dev -v vvv - * ``` - * - * ```php - * $argv = $_SERVER['argv']; - * // notice: must shift first element. - * $script = \array_shift($argv); - * $result = Flags::parseArgv($argv); - * ``` - * - * Supports args: - * - * arg= - * Supports opts: - * -e - * -e - * -e= - * --long-opt - * --long-opt - * --long-opt= - * - * @link http://php.net/manual/zh/function.getopt.php#83414 - * - * @param array $params - * @param array $config - * - * @return array [args, short-opts, long-opts] - * If 'mergeOpts' is True, will return [args, opts] - */ - public static function parseArgv(array $params, array $config = []): array - { - if (!$params) { - return [[], [], []]; - } - - $config = array_merge([ - // List of parameters without values(bool option keys) - 'boolOpts' => [], // ['debug', 'h'] - // Whether merge short-opts and long-opts - 'mergeOpts' => false, - // want parsed options. if not empty, will ignore no matched - 'wantParsedOpts' => [], - // list of option allow array values. - 'arrayOpts' => [], // ['names', 'status'] - ], $config); - - $args = $sOpts = $lOpts = []; - // config - $boolOpts = array_flip((array)$config['boolOpts']); - $arrayOpts = array_flip((array)$config['arrayOpts']); - - // each() will deprecated at 7.2. so,there use current and next instead it. - // while (list(,$p) = each($params)) { - while (false !== ($p = current($params))) { - next($params); - - // is options - if ($p[0] === '-') { - $value = true; - $option = substr($p, 1); - $isLong = false; - - // long-opt: (--) - if (strpos($option, '-') === 0) { - $option = substr($option, 1); - $isLong = true; - - // long-opt: value specified inline (--=) - if (strpos($option, '=') !== false) { - [$option, $value] = explode('=', $option, 2); - } - - // short-opt: value specified inline (-=) - } elseif (isset($option[1]) && $option[1] === '=') { - [$option, $value] = explode('=', $option, 2); - } - - // check if next parameter is a descriptor or a value - $nxt = current($params); - - // next elem is value. fix: allow empty string '' - if ($value === true && !isset($boolOpts[$option]) && self::nextIsValue($nxt)) { - // list(,$val) = each($params); - $value = $nxt; - next($params); - - // short-opt: bool opts. like -e -abc - } elseif (!$isLong && $value === true) { - foreach (str_split($option) as $char) { - $sOpts[$char] = true; - } - continue; - } - - $value = self::filterBool($value); - $isArray = isset($arrayOpts[$option]); - - if ($isLong) { - if ($isArray) { - $lOpts[$option][] = $value; - } else { - $lOpts[$option] = $value; - } - } elseif ($isArray) { // short - $sOpts[$option][] = $value; - } else { // short - $sOpts[$option] = $value; - } - - continue; - } - - // parse arguments: - // - param doesn't belong to any option, define it is args - - // value specified inline (=) - if (strpos($p, '=') !== false) { - [$name, $value] = explode('=', $p, 2); - $args[$name] = self::filterBool($value); - } else { - $args[] = $p; - } - } - - if ($config['mergeOpts']) { - return [$args, array_merge($sOpts, $lOpts)]; - } - - return [$args, $sOpts, $lOpts]; - } - - /** - * parse custom array params - * ```php - * $result = Flags::parseArray([ - * 'arg' => 'val', - * '--lp' => 'val2', - * '--s' => 'val3', - * '-h' => true, - * ]); - * ``` - * - * @param array $params - * - * @return array - */ - public static function parseArray(array $params): array - { - $args = $sOpts = $lOpts = []; - - foreach ($params as $key => $val) { - if (is_int($key)) { // as argument - $args[$key] = $val; - continue; - } - - $cleanKey = trim((string)$key, '-'); - - if ('' === $cleanKey) { // as argument - $args[] = $val; - continue; - } - - if (0 === strpos($key, '--')) { // long option - $lOpts[$cleanKey] = $val; - } elseif (0 === strpos($key, '-')) { // short option - $sOpts[$cleanKey] = $val; - } else { - $args[$key] = $val; - } - } - - return [$args, $sOpts, $lOpts]; - } - - /** - * parse flags from a string - * - * ```php - * $result = Flags::parseString('foo --bar="foobar"'); - * ``` - * - * @param string $string - * - * @todo ... - */ - public static function parseString(string $string): void - { - - } - - /** - * @param string|bool $val - * @param bool $enable - * - * @return bool|mixed - */ - public static function filterBool($val, $enable = true) - { - if ($enable) { - if (is_bool($val) || is_numeric($val)) { - return $val; - } - - // check it is a bool value. - if (false !== stripos(self::TRUE_WORDS, "|$val|")) { - return true; - } - - if (false !== stripos(self::FALSE_WORDS, "|$val|")) { - return false; - } - } - - return $val; - } - - /** - * @param mixed $val - * - * @return bool - */ - public static function nextIsValue($val): bool - { - // current() fetch error, will return FALSE - if ($val === false) { - return false; - } - - // if is: '', 0 - if (!$val) { - return true; - } - - // it isn't option or named argument - return $val[0] !== '-' && false === strpos($val, '='); - } - - /** - * Escapes a token through escapeshellarg if it contains unsafe chars. - * - * @param string $token - * - * @return string - */ - public static function escapeToken(string $token): string - { - return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); - } -} diff --git a/libs/cli-utils/src/Highlighter.php b/libs/cli-utils/src/Highlighter.php deleted file mode 100644 index 2dc8f80..0000000 --- a/libs/cli-utils/src/Highlighter.php +++ /dev/null @@ -1,380 +0,0 @@ - 'red', - self::TOKEN_COMMENT => 'yellow', - self::TOKEN_KEYWORD => 'info', - self::TOKEN_DEFAULT => 'normal', - self::TOKEN_HTML => 'cyan', - self::ACTUAL_LINE_MARK => 'red', - self::LINE_NUMBER => 'darkGray', - ]; - - /** @var bool */ - private $hasTokenFunc; - - /** - * @return Highlighter - */ - public static function create(): self - { - if (!self::$instance) { - self::$instance = new self(); - } - - return self::$instance; - } - - public function __construct() - { - $this->hasTokenFunc = function_exists('token_get_all'); - } - - /** - * highlight a full php file content - * - * @param string $source - * @param bool $withLineNumber with line number - * - * @return string - */ - public function highlight(string $source, bool $withLineNumber = false): string - { - $tokenLines = $this->getHighlightedLines($source); - $lines = $this->colorLines($tokenLines); - - if ($withLineNumber) { - return $this->lineNumbers($lines); - } - - return implode(PHP_EOL, $lines); - } - - /** - * @param string $file - * @param bool $withLineNumber - * - * @return string - */ - public function highlightFile(string $file, bool $withLineNumber = false): string - { - if (!file_exists($file)) { - throw new InvalidArgumentException("the target file is not exist! file: $file"); - } - - $source = file_get_contents($file); - - return $this->highlight($source, $withLineNumber); - } - - /** - * @param string $source - * @param int $lineNumber - * @param int $linesBefore - * @param int $linesAfter - * - * @return string - * @throws InvalidArgumentException - */ - public function snippet(string $source, int $lineNumber, int $linesBefore = 2, int $linesAfter = 2): string - { - return $this->highlightSnippet($source, $lineNumber, $linesBefore, $linesAfter); - } - - /** - * @param string $source - * @param int $lineNumber - * @param int $linesBefore - * @param int $linesAfter - * - * @return string - * @throws InvalidArgumentException - */ - public function highlightSnippet($source, $lineNumber, $linesBefore = 2, $linesAfter = 2): string - { - $tokenLines = $this->getHighlightedLines($source); - - $offset = $lineNumber - $linesBefore - 1; - $offset = max($offset, 0); - $length = $linesAfter + $linesBefore + 1; - $tokenLines = array_slice($tokenLines, $offset, $length, $preserveKeys = true); - - $lines = $this->colorLines($tokenLines); - - return $this->lineNumbers($lines, $lineNumber); - } - - /** - * @param string $source - * - * @return array - */ - private function getHighlightedLines(string $source): array - { - $source = str_replace(["\r\n", "\r"], "\n", $source); - - if ($this->hasTokenFunc) { - $tokens = $this->tokenize($source); - return $this->splitToLines($tokens); - } - - // if no func: token_get_all - return explode("\n", $source); - } - - /** - * @param string $source - * - * @return array - */ - private function tokenize(string $source): array - { - $buffer = ''; - $output = []; - $tokens = token_get_all($source); - $newType = $currentType = null; - - foreach ($tokens as $token) { - if (is_array($token)) { - switch ($token[0]) { - case T_INLINE_HTML: - $newType = self::TOKEN_HTML; - break; - case T_COMMENT: - case T_DOC_COMMENT: - $newType = self::TOKEN_COMMENT; - break; - case T_ENCAPSED_AND_WHITESPACE: - case T_CONSTANT_ENCAPSED_STRING: - $newType = self::TOKEN_STRING; - break; - case T_WHITESPACE: - break; - case T_OPEN_TAG: - case T_OPEN_TAG_WITH_ECHO: - case T_CLOSE_TAG: - case T_STRING: - case T_VARIABLE: - // Constants - case T_DIR: - case T_FILE: - case T_METHOD_C: - case T_DNUMBER: - case T_LNUMBER: - case T_NS_C: - case T_LINE: - case T_CLASS_C: - case T_FUNC_C: - //case T_TRAIT_C: - $newType = self::TOKEN_DEFAULT; - break; - default: - // Compatibility with PHP 5.3 - if (defined('T_TRAIT_C') && $token[0] === T_TRAIT_C) { - $newType = self::TOKEN_DEFAULT; - } else { - $newType = self::TOKEN_KEYWORD; - } - } - } else { - $newType = $token === '"' ? self::TOKEN_STRING : self::TOKEN_KEYWORD; - } - - if ($currentType === null) { - $currentType = $newType; - } - - if ($currentType !== $newType) { - $output[] = [$currentType, $buffer]; - $buffer = ''; - $currentType = $newType; - } - - $buffer .= is_array($token) ? $token[1] : $token; - } - - if (null !== $newType) { - $output[] = [$newType, $buffer]; - } - - return $output; - } - - /** - * @param array $tokens - * - * @return array - */ - private function splitToLines(array $tokens): array - { - $lines = $line = []; - - foreach ($tokens as $token) { - foreach (explode("\n", $token[1]) as $count => $tokenLine) { - if ($count > 0) { - $lines[] = $line; - $line = []; - } - if ($tokenLine === '') { - continue; - } - - $line[] = [$token[0], $tokenLine]; - } - } - $lines[] = $line; - - return $lines; - } - - /** - * @param array[] $tokenLines - * - * @return array - * @throws InvalidArgumentException - */ - private function colorLines(array $tokenLines): array - { - if (!$this->hasTokenFunc) { - return $tokenLines; - } - - $lines = []; - - foreach ($tokenLines as $lineCount => $tokenLine) { - $line = ''; - foreach ($tokenLine as [$tokenType, $tokenValue]) { - $style = $this->defaultTheme[$tokenType]; - - if (Color::hasStyle($style)) { - $line .= Color::apply($style, $tokenValue); - } else { - $line .= $tokenValue; - } - } - - $lines[$lineCount] = $line; - } - - return $lines; - } - - /** - * @param array $lines - * @param null|int $markLine - * - * @return string - */ - private function lineNumbers(array $lines, $markLine = null): string - { - end($lines); - - $snippet = ''; - $lineLen = strlen(key($lines) + 1); - $lmStyle = $this->defaultTheme[self::ACTUAL_LINE_MARK]; - $lnStyle = $this->defaultTheme[self::LINE_NUMBER]; - - foreach ($lines as $i => $line) { - if ($markLine !== null) { - $snippet .= ($markLine === $i + 1 ? Color::apply($lmStyle, ' > ') : ' '); - $snippet .= Color::apply($markLine === $i + 1 ? $lmStyle : $lnStyle, - str_pad($i + 1, $lineLen, ' ', STR_PAD_LEFT) . '| '); - } else { - $snippet .= Color::apply($lnStyle, str_pad($i + 1, $lineLen, ' ', STR_PAD_LEFT) . '| '); - } - - $snippet .= $line . PHP_EOL; - } - - return $snippet; - } - - /** - * @return array - */ - public function getDefaultTheme(): array - { - return $this->defaultTheme; - } - - /** - * @param array $defaultTheme - */ - public function setDefaultTheme(array $defaultTheme): void - { - $this->defaultTheme = array_merge($this->defaultTheme, $defaultTheme); - } -} diff --git a/libs/cli-utils/src/Terminal.php b/libs/cli-utils/src/Terminal.php deleted file mode 100644 index f33212d..0000000 --- a/libs/cli-utils/src/Terminal.php +++ /dev/null @@ -1,243 +0,0 @@ - '?25l', - - // Will show a cursor again when it has been hidden by [hide] - 'show' => '?25h', - - // Saves the current cursor position, Position can then be restored with [restorePosition]. - // - 保存当前光标位置,然后可以使用[restorePosition]恢复位置 - 'savePosition' => 's', - - // Restores the cursor position saved with [savePosition] - 恢复[savePosition]保存的光标位置 - 'restorePosition' => 'u', - - // Moves the terminal cursor up - 'up' => '%dA', - - // Moves the terminal cursor down - 'down' => '%B', - - // Moves the terminal cursor forward - 移动终端光标前进多远 - 'forward' => '%dC', - - // Moves the terminal cursor backward - 移动终端光标后退多远 - 'backward' => '%dD', - - // Moves the terminal cursor to the beginning of the previous line - 移动终端光标到前一行的开始 - 'prevLine' => '%dF', - - // Moves the terminal cursor to the beginning of the next line - 移动终端光标到下一行的开始 - 'nextLine' => '%dE', - - // Moves the cursor to an absolute position given as column and row - // $column 1-based column number, 1 is the left edge of the screen. - // $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. - 'coordinate' => '%dG|%d;%dH' // only column: '%dG', column and row: '%d;%dH'. - ]; - - /** - * Control screen code list - * - * @var array - */ - private static $ctrlScreenCodes = [ - // Clears entire screen content - 清除整个屏幕内容 - 'clear' => '2J', // "\033[2J" - - // Clears text from cursor to the beginning of the screen - 从光标清除文本到屏幕的开头 - 'clearBeforeCursor' => '1J', - - // Clears the line - 清除此行 - 'clearLine' => '2K', - - // Clears text from cursor position to the beginning of the line - 清除此行从光标位置开始到开始的字符 - 'clearLineBeforeCursor' => '1K', - - // Clears text from cursor position to the end of the line - 清除此行从光标位置开始到结束的字符 - 'clearLineAfterCursor' => '0K', - - // Scrolls whole page up. e.g "\033[2S" scroll up 2 line. - 上移多少行 - 'scrollUp' => '%dS', - - // Scrolls whole page down.e.g "\033[2T" scroll down 2 line. - 下移多少行 - 'scrollDown' => '%dT', - ]; - - public static function make(): Terminal - { - if (!self::$instance) { - self::$instance = new self; - } - - return self::$instance; - } - - /** - * build ansi code string - * - * ``` - * Terminal::build(null, 'u'); // "\033[s" Saves the current cursor position - * Terminal::build(0); // "\033[0m" Build end char, Resets any ANSI format - * ``` - * - * @param mixed $format - * @param string $type - * - * @return string - */ - public static function build($format, $type = 'm'): string - { - $format = null === $format ? '' : implode(';', (array)$format); - - return "\033[" . implode(';', (array)$format) . $type; - } - - /** - * control cursor - * - * @param string $typeName - * @param int $arg1 - * @param null $arg2 - * - * @return $this - */ - public function cursor($typeName, $arg1 = 1, $arg2 = null): self - { - if (!isset(self::$ctrlCursorCodes[$typeName])) { - Cli::stderr("The [$typeName] is not supported cursor control."); - } - - $code = self::$ctrlCursorCodes[$typeName]; - - // allow argument - if (false !== strpos($code, '%')) { - // The special code: ` 'coordinate' => '%dG|%d;%dH' ` - if ($typeName === self::CUR_COORDINATE) { - $codes = explode('|', $code); - - if (null === $arg2) { - $code = sprintf($codes[0], $arg1); - } else { - $code = sprintf($codes[1], $arg1, $arg2); - } - - } else { - $code = sprintf($code, $arg1); - } - } - - echo self::build($code, ''); - - return $this; - } - - /** - * control screen - * - * @param $typeName - * @param null $arg - * - * @return $this - */ - public function screen(string $typeName, $arg = null): self - { - if (!isset(self::$ctrlScreenCodes[$typeName])) { - Cli::stderr("The [$typeName] is not supported cursor control."); - } - - $code = self::$ctrlScreenCodes[$typeName]; - - // allow argument - if (false !== strpos($code, '%')) { - $code = sprintf($code, $arg); - } - - echo self::build($code, ''); - - return $this; - } - - public function reset(): void - { - echo self::END_CHAR; - } - - /** - * @return array - */ - public static function supportedCursorCtrl(): array - { - return array_keys(self::$ctrlCursorCodes); - } - - /** - * @return array - */ - public static function supportedScreenCtrl(): array - { - return array_keys(self::$ctrlScreenCodes); - } -} diff --git a/libs/cli-utils/test/ColorTagTest.php b/libs/cli-utils/test/ColorTagTest.php deleted file mode 100644 index af9366e..0000000 --- a/libs/cli-utils/test/ColorTagTest.php +++ /dev/null @@ -1,75 +0,0 @@ -text0 or text1'); - $this->assertCount(3, $ret); - // tag - $this->assertSame('tag', $ret[1][0]); - $this->assertSame('info', $ret[1][1]); - // content - $this->assertSame('text0', $ret[2][0]); - - $ret = ColorTag::matchAll('text'); - $this->assertCount(3, $ret); - // tag - $this->assertSame('some_tag', $ret[1][0]); - // content - $this->assertSame('text', $ret[2][0]); - - $ret = ColorTag::matchAll('text'); - $this->assertCount(3, $ret); - // tag - $this->assertSame('someTag', $ret[1][0]); - // content - $this->assertSame('text', $ret[2][0]); - } - - public function testStrip(): void - { - $text = ColorTag::strip('text'); - $this->assertSame('text', $text); - - // no close - $text = ColorTag::clear('text'); - $this->assertSame('text', $text); - } - - public function testWrap(): void - { - $text = ColorTag::wrap('text', 'tag'); - $this->assertSame('text', $text); - - $text = ColorTag::add('text', ''); - $this->assertSame('text', $text); - - $text = ColorTag::add('', 'tag'); - $this->assertSame('', $text); - } - - public function testExists(): void - { - $this->assertTrue(ColorTag::exists('text')); - $this->assertFalse(ColorTag::exists('text')); - $this->assertFalse(ColorTag::exists('text')); - $this->assertFalse(ColorTag::exists('text')); - } -} diff --git a/libs/cli-utils/test/ColorTest.php b/libs/cli-utils/test/ColorTest.php deleted file mode 100644 index 5f5fb3d..0000000 --- a/libs/cli-utils/test/ColorTest.php +++ /dev/null @@ -1,43 +0,0 @@ -assertStringContainsString(Color::STYLES['info'], $text); - - $text = Color::render('text', [Color::RESET, Color::FG_CYAN]); - $this->assertStringContainsString(Color::STYLES['cyan'], $text); - - $text = Color::render('text'); - $this->assertStringContainsString(Color::STYLES['info'], $text); - - $text = Color::render('text'); - $this->assertStringContainsString(Color::STYLES['light_blue'], $text); - - $text = Color::render('text'); - $this->assertStringContainsString(Color::STYLES['lightBlue'], $text); - } - - public function testApply(): void - { - $text = Color::apply('info', 'text'); - $this->assertStringContainsString(Color::STYLES['info'], $text); - - foreach (Color::STYLES as $name => $code) { - $text = Color::apply($name, 'text'); - $this->assertStringContainsString($code, $text); - } - } -} diff --git a/libs/cli-utils/test/FlagsTest.php b/libs/cli-utils/test/FlagsTest.php deleted file mode 100644 index f4d2f9c..0000000 --- a/libs/cli-utils/test/FlagsTest.php +++ /dev/null @@ -1,38 +0,0 @@ -assertNotEmpty($args); - $this->assertSame('git:tag', $args[0]); - $this->assertSame('arg0', $args[1]); - - $this->assertSame('../view', $sOpts['d']); - $this->assertTrue($lOpts['only-tag']); - - [$args, $opts] = Flags::parseArgv($rawArgv, ['mergeOpts' => true]); - - $this->assertNotEmpty($args); - $this->assertSame('git:tag', $args[0]); - $this->assertSame('arg0', $args[1]); - - $this->assertSame('../view', $opts['d']); - $this->assertTrue($opts['only-tag']); - } -} diff --git a/libs/cli-utils/test/boot.php b/libs/cli-utils/test/boot.php deleted file mode 100644 index 8afcdc7..0000000 --- a/libs/cli-utils/test/boot.php +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - test/ - - - - - - src - - - diff --git a/libs/di/phpunit.xml.dist b/libs/di/phpunit.xml.dist deleted file mode 100644 index 26bd767..0000000 --- a/libs/di/phpunit.xml.dist +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - test/ - - - - - - src - - - diff --git a/libs/obj-utils/LICENSE b/libs/obj-utils/LICENSE deleted file mode 100644 index d839cdc..0000000 --- a/libs/obj-utils/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 inhere - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/libs/obj-utils/README.md b/libs/obj-utils/README.md deleted file mode 100644 index 1f2a6a2..0000000 --- a/libs/obj-utils/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# object utils - -[![License](https://img.shields.io/packagist/l/toolkit/obj-utils.svg?style=flat-square)](LICENSE) -[![Php Version](https://img.shields.io/badge/php-%3E=7.1.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/toolkit/obj-utils) -[![Latest Stable Version](http://img.shields.io/packagist/v/toolkit/obj-utils.svg)](https://packagist.org/packages/toolkit/obj-utils) - -Some useful object utils for the php. - -## Install - -```bash -composer require toolkit/obj-utils -``` - -## License - -MIT diff --git a/libs/obj-utils/composer.json b/libs/obj-utils/composer.json deleted file mode 100644 index ec77810..0000000 --- a/libs/obj-utils/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "toolkit/obj-utils", - "type": "library", - "description": "some object tool library of the php", - "keywords": [ - "library", - "tool", - "php" - ], - "homepage": "https://github.com/php-toolkit/obj-utils", - "license": "MIT", - "authors": [ - { - "name": "inhere", - "email": "in.798@qq.com", - "homepage": "http://www.yzone.net/" - } - ], - "require": { - "php": ">7.1.0" - }, - "autoload": { - "psr-4": { - "Toolkit\\ObjUtil\\": "src/" - } - }, - "suggest": { - "inhere/php-validate": "Very lightweight data validate tool", - "inhere/console": "a lightweight php console application library." - }, - "scripts": { - "test": "phpunit test" - } -} diff --git a/libs/obj-utils/src/Configurable.php b/libs/obj-utils/src/Configurable.php deleted file mode 100644 index f6d8176..0000000 --- a/libs/obj-utils/src/Configurable.php +++ /dev/null @@ -1,43 +0,0 @@ -init(); - } - - /** - * init - */ - protected function init(): void - { - // init something ... - } -} diff --git a/libs/obj-utils/src/Exception/GetPropertyException.php b/libs/obj-utils/src/Exception/GetPropertyException.php deleted file mode 100644 index b829696..0000000 --- a/libs/obj-utils/src/Exception/GetPropertyException.php +++ /dev/null @@ -1,20 +0,0 @@ - $value) { - if (is_numeric($property)) { - continue; - } - - $setter = 'set' . ucfirst($property); - - // has setter - if (method_exists($object, $setter)) { - $object->$setter($value); - } elseif (property_exists($object, $property)) { - $object->$property = $value; - } - } - - return $object; - } - - /** - * 给对象设置属性值 - * - * @param $object - * @param array $options - */ - public static function configure($object, array $options): void - { - foreach ($options as $property => $value) { - if (property_exists($object, $property)) { - $object->$property = $value; - } - } - } - - /** - * 给对象设置属性值 - * - * @param $object - * @param array $options - */ - public static function setAttrs($object, array $options): void - { - self::configure($object, $options); - } - - /** - * 定义一个用来序列化数据的函数 - * - * @param mixed $obj - * - * @return string - */ - public static function encode($obj): string - { - return base64_encode(gzcompress(serialize($obj))); - } - - /** - * 反序列化 - * - * @param string $txt - * @param bool|array $allowedClasses - * - * @return mixed - */ - public static function decode(string $txt, $allowedClasses = false) - { - return unserialize(gzuncompress(base64_decode($txt)), ['allowed_classes' => $allowedClasses]); - } - - /** - * php对象转换成为数组 - * - * @param iterable|array|Traversable $data - * @param bool $recursive - * - * @return array|bool - */ - public static function toArray($data, bool $recursive = false) - { - $arr = []; - - // Ensure the input data is an array. - if (is_object($data)) { - if ($data instanceof Traversable) { - $arr = iterator_to_array($data); - } elseif (method_exists($data, 'toArray')) { - $arr = $data->toArray(); - } - } else { - $arr = (array)$data; - } - - if ($recursive) { - foreach ($arr as $key => $value) { - if (is_array($value) || is_object($value)) { - $arr[$key] = static::toArray($value, $recursive); - } - } - } - - return $arr; - } - - /** - * @param mixed $object - * @param bool $unique - * - * @return string - */ - public static function hash($object, $unique = true): string - { - if (is_object($object)) { - $hash = spl_object_hash($object); - - if ($unique) { - $hash = md5($hash); - } - - return $hash; - } - - // a class - return is_string($object) ? md5($object) : ''; - } - - /** - * @from https://github.com/ventoviro/windwalker - * Build an array of constructor parameters. - * - * @param ReflectionMethod $method Method for which to build the argument array. - * @param array $extraArgs - * - * @return array - * @throws RuntimeException - * @throws ReflectionException - */ - public static function getMethodArgs(ReflectionMethod $method, array $extraArgs = []): array - { - $methodArgs = []; - - foreach ($method->getParameters() as $idx => $param) { - // if user have been provide arg - if (isset($extraArgs[$idx])) { - $methodArgs[] = $extraArgs[$idx]; - continue; - } - - $dependencyClass = $param->getClass(); - - // If we have a dependency, that means it has been type-hinted. - if ($dependencyClass && ($depClass = $dependencyClass->getName()) !== Closure::class) { - $depClass = $dependencyClass->getName(); - $depObject = self::create($depClass); - - if ($depObject instanceof $depClass) { - $methodArgs[] = $depObject; - continue; - } - } - - // Finally, if there is a default parameter, use it. - if ($param->isOptional()) { - $methodArgs[] = $param->getDefaultValue(); - continue; - } - - // $dependencyVarName = $param->getName(); - // Couldn't resolve dependency, and no default was provided. - throw new RuntimeException(sprintf('Could not resolve dependency: %s for the %dth parameter', - $param->getPosition(), $param->getName())); - } - - return $methodArgs; - } - - /** - * 从类名创建服务实例对象,会尽可能自动补完构造函数依赖 - * - * @from windWalker https://github.com/ventoviro/windwalker - * - * @param string $class a className - * - * @return mixed - * @throws RuntimeException - */ - public static function create(string $class) - { - try { - $reflection = new ReflectionClass($class); - } catch (ReflectionException $e) { - return false; - } - - $constructor = $reflection->getConstructor(); - - // If there are no parameters, just return a new object. - if (null === $constructor) { - return new $class; - } - - $newInstanceArgs = self::getMethodArgs($constructor); - - // Create a callable for the dataStorage - return $reflection->newInstanceArgs($newInstanceArgs); - } - - /** - * @param string|array $config - * - * @return mixed - */ - public static function smartCreate($config) - { - if (is_string($config)) { - return new $config; - } - - if (is_array($config) && !empty($config['class'])) { - $class = $config['class']; - $args = $config[0] ?? []; - - $obj = new $class(...$args); - - unset($config['class'], $config[0]); - return self::init($obj, $config); - } - - return null; - } -} diff --git a/libs/obj-utils/src/Traits/ArrayAccessByGetterSetterTrait.php b/libs/obj-utils/src/Traits/ArrayAccessByGetterSetterTrait.php deleted file mode 100644 index c23bed0..0000000 --- a/libs/obj-utils/src/Traits/ArrayAccessByGetterSetterTrait.php +++ /dev/null @@ -1,83 +0,0 @@ -$getter(); - } - - return null; - } - - /** - * Sets an offset in the iterator. - * - * @param mixed $offset The array offset. - * @param mixed $value The array value. - */ - public function offsetSet($offset, $value): void - { - $setter = 'set' . ucfirst($offset); - - if (method_exists($this, $setter)) { - $this->$setter($value); - } - } - - /** - * Unset an offset in the iterator. - * - * @param mixed $offset The array offset. - * - * @return void - */ - public function offsetUnset($offset): void - { - // unset($this->$offset); - } -} diff --git a/libs/obj-utils/src/Traits/ArrayAccessByPropertyTrait.php b/libs/obj-utils/src/Traits/ArrayAccessByPropertyTrait.php deleted file mode 100644 index dfb8194..0000000 --- a/libs/obj-utils/src/Traits/ArrayAccessByPropertyTrait.php +++ /dev/null @@ -1,74 +0,0 @@ -$offset; - } - - /** - * Sets an offset in the iterator. - * - * @param mixed $offset The array offset. - * @param mixed $value The array value. - * - * @return void - */ - public function offsetSet($offset, $value): void - { - $this->$offset = $value; - } - - /** - * Unset an offset in the iterator. - * - * @param mixed $offset The array offset. - * - * @return void - */ - public function offsetUnset($offset): void - { - // unset($this->$offset); - } -} diff --git a/libs/obj-utils/src/Traits/ObjectPoolTrait.php b/libs/obj-utils/src/Traits/ObjectPoolTrait.php deleted file mode 100644 index d91c041..0000000 --- a/libs/obj-utils/src/Traits/ObjectPoolTrait.php +++ /dev/null @@ -1,128 +0,0 @@ - \SplStack] - */ - private static $pool = []; - - /** - * @param string $class - * - * @return mixed - */ - public static function get(string $class) - { - $stack = self::getStack($class); - - if (!$stack->isEmpty()) { - return $stack->shift(); - } - - return new $class; - } - - /** - * @param stdClass|string $object - */ - public static function put($object): void - { - if (is_string($object)) { - $object = new $object; - } - - self::getStack($object)->push($object); - } - - /** - * @param string $class - * @param Closure $handler - * - * @return mixed - */ - public static function use($class, Closure $handler) - { - $obj = self::get($class); - - $ret = $handler($obj); - - self::put($obj); - - return $ret; - } - - /** - * @param string|stdClass $class - * - * @return SplStack - */ - public static function getStack($class): SplStack - { - $class = is_string($class) ? $class : get_class($class); - - if (!isset(self::$pool[$class])) { - self::$pool[$class] = new SplStack(); - } - - return self::$pool[$class]; - } - - /** - * @param null $class - * - * @return int - * @throws InvalidArgumentException - */ - public static function count($class = null): int - { - if ($class) { - if (!isset(self::$pool[$class])) { - throw new InvalidArgumentException("The object is never created of the class: $class"); - } - - return self::$pool[$class]->count(); - } - - return count(self::$pool); - } - - /** - * @param null $class - * - * @throws InvalidArgumentException - */ - public static function destroy($class = null): void - { - if ($class) { - if (!isset(self::$pool[$class])) { - throw new InvalidArgumentException("The object is never created of the class: $class"); - } - - unset(self::$pool[$class]); - } else { - self::$pool = []; - } - } -} diff --git a/libs/obj-utils/src/Traits/PropertyAccessByGetterSetterTrait.php b/libs/obj-utils/src/Traits/PropertyAccessByGetterSetterTrait.php deleted file mode 100644 index f709109..0000000 --- a/libs/obj-utils/src/Traits/PropertyAccessByGetterSetterTrait.php +++ /dev/null @@ -1,108 +0,0 @@ -$setter($value); - } elseif (method_exists($this, 'get' . ucfirst($name))) { - throw new SetPropertyException('Setting a Read-only property! ' . get_class($this) . "::{$name}"); - } else { - throw new SetPropertyException('Setting a Unknown property! ' . get_class($this) . "::{$name}"); - } - } - - /** - * @reference yii2 yii\base\Object::__set() - * - * @param $name - * - * @return mixed - * @throws GetPropertyException - */ - public function __get($name) - { - $getter = 'get' . ucfirst($name); - - if (method_exists($this, $getter)) { - return $this->$getter(); - } - - if (method_exists($this, 'set' . ucfirst($name))) { - throw new GetPropertyException('Getting a Write-only property! ' . get_class($this) . "::{$name}"); - } - - throw new GetPropertyException('Getting a Unknown property! ' . get_class($this) . "::{$name}"); - } - - /** - * @param $name - * - * @return bool - */ - public function __isset($name) - { - $getter = 'get' . ucfirst($name); - - if (method_exists($this, $getter)) { - return $this->$getter() !== null; - } - - return false; - } - - /** - * @param $name - * - * @throws PropertyException - */ - public function __unset($name) - { - $setter = 'set' . ucfirst($name); - - if (method_exists($this, $setter)) { - $this->$setter(null); - - return; - } - - throw new PropertyException('Unset an unknown or read-only property: ' . get_class($this) . '::' . $name); - } - -} diff --git a/libs/obj-utils/src/Traits/SingletonTrait.php b/libs/obj-utils/src/Traits/SingletonTrait.php deleted file mode 100644 index 68e282a..0000000 --- a/libs/obj-utils/src/Traits/SingletonTrait.php +++ /dev/null @@ -1,31 +0,0 @@ -init(); - } - - /** - * init - */ - protected function init(): void - { - // init something ... - } - - /** - * @param string $method - * @param $args - * - * @return mixed - * @throws InvalidArgumentException - */ - public function __call($method, array $args) - { - // if (method_exists($this, $method) && $this->isAllowCall($method) ) { - // return call_user_func_array( array($this, $method), (array) $args); - // } - - throw new InvalidArgumentException('Called a Unknown method! ' . get_class($this) . "->{$method}()"); - } - - /** - * @param string $method - * @param $args - * - * @return mixed - * @throws InvalidArgumentException - */ - public static function __callStatic(string $method, $args) - { - if (method_exists(self::class, $method)) { - return call_user_func_array([self::class, $method], (array)$args); - } - - throw new InvalidArgumentException('Called a Unknown static method! [ ' . self::class . "::{$method}()]"); - } -} diff --git a/libs/obj-utils/test/boot.php b/libs/obj-utils/test/boot.php deleted file mode 100644 index 01ff8b9..0000000 --- a/libs/obj-utils/test/boot.php +++ /dev/null @@ -1,27 +0,0 @@ -7.1.0", - "ext-mbstring": "*" - }, - "autoload": { - "psr-4": { - "Toolkit\\StrUtil\\": "src/" - } - }, - "suggest": { - "inhere/php-validate": "Very lightweight data validate tool" - } -} diff --git a/libs/str-utils/phpunit.xml.dist b/libs/str-utils/phpunit.xml.dist deleted file mode 100644 index 26bd767..0000000 --- a/libs/str-utils/phpunit.xml.dist +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - test/ - - - - - - src - - - diff --git a/libs/str-utils/src/HtmlHelper.php b/libs/str-utils/src/HtmlHelper.php deleted file mode 100644 index 55da156..0000000 --- a/libs/str-utils/src/HtmlHelper.php +++ /dev/null @@ -1,241 +0,0 @@ - $value) { - if (is_string($key)) { - $key = htmlspecialchars($key, ENT_QUOTES, $charset); - } - - if (is_string($value)) { - $value = htmlspecialchars($value, ENT_QUOTES, $charset); - } elseif (is_array($value)) { - $value = static::encodeArray($value); - } - - $d[$key] = $value; - } - - return $d; - } - - - /** - * html代码转义 - * htmlspecialchars 只转化这几个html [ & ' " < > ] 代码 --> [ & " ], - * 而 htmlentities 却会转化所有的html代码,连同里面的它无法识别的中文字符也会转化。 - * 一般使用 htmlspecialchars 就足够了,要使用 htmlentities 时,要注意为第三个参数传递正确的编码。 - * htmlentities() <--> html_entity_decode() — 将特殊的 HTML 实体转换回普通字符 - * htmlspecialchars() <--> htmlspecialchars_decode() — 将特殊的 HTML 实体转换回普通字符 - * ENT_COMPAT ENT_QUOTES ENT_NOQUOTES ENT_HTML401 ENT_XML1 ENT_XHTML ENT_HTML5 - * - * @param $data - * @param int $type - * @param string $encoding - * - * @return array|mixed|string - */ - public static function escape($data, int $type = 0, $encoding = 'UTF-8') - { - if (is_array($data)) { - foreach ($data as $k => $v) { - $data[$k] = self::escape($data, $type, $encoding); - } - - return $data; - } - - // 默认使用 htmlspecialchars() - if (!$type) { - $data = htmlspecialchars($data, ENT_QUOTES, $encoding); - } else { - $data = htmlentities($data, ENT_QUOTES, $encoding); - } - - //如‘志’这样的16进制的html字符,为了防止这样的字符被错误转译,使用正则进行匹配,把这样的字符又转换回来。 - if (strpos($data, '&#')) { - $data = preg_replace('/&((#(\d{3,5}|x[a-fA-F0-9]{4}));)/', '&\\1', $data); - } - - return $data; - } - - /** - * 去掉html转义 - * - * @param $data - * @param int $type - * @param string $encoding - * - * @return array|string - */ - public static function unescap($data, $type = 0, $encoding = 'UTF-8') - { - if (is_array($data)) { - foreach ($data as $k => $v) { - $data[$k] = self::unescap($data, $type, $encoding); - } - - } elseif (!$type) {//默认使用 htmlspecialchars_decode() - $data = htmlspecialchars_decode($data, ENT_QUOTES); - } else { - $data = html_entity_decode($data, ENT_QUOTES, $encoding); - } - - return $data; - } - - /** - * Strip img-tags from string - * - * @param string $string Sting to be cleaned. - * - * @return string Cleaned string - */ - public static function stripImages(string $string): string - { - return preg_replace('#(<[/]?img.*>)#U', '', $string); - } - - /** - * Strip iframe-tags from string - * - * @param string $string Sting to be cleaned. - * - * @return string Cleaned string - */ - public static function stripIframes(string $string): string - { - return preg_replace('#(<[/]?iframe.*>)#U', '', $string); - } - - /** - * stripScript - * - * @param string $string - * - * @return string - */ - public static function stripScript(string $string): string - { - return preg_replace('/]*>.*?/si', '', $string); - } - - /** - * stripStyle - * - * @param string $string - * - * @return string - */ - public static function stripStyle(string $string): string - { - return preg_replace('/]*>.*?/si', '', $string); - } - - /** - * @param string $html - * @param bool|true $onlySrc - * - * @return array - */ - public static function matchImages(string $html, bool $onlySrc = true): array - { - // $preg = '//i'; - $preg = '//i'; - - if (!preg_match_all($preg, trim($html), $images)) { - return []; - } - - if ($onlySrc) { - return array_key_exists(1, $images) ? $images[1] : []; - } - - return $images; - } - - /** - * @param string $html - * - * @return string - */ - public static function minify(string $html): string - { - $search = [ - '/(?:(?:\/\*(?:[^*]|(?:\*+[^*\/]))*\*+\/)|(?:(?[^\S ]+/s', - '/[^\S ]+\', '<', '\\1']; - - return preg_replace($search, $replace, $html); - } -} diff --git a/libs/str-utils/src/Json.php b/libs/str-utils/src/Json.php deleted file mode 100644 index fccc524..0000000 --- a/libs/str-utils/src/Json.php +++ /dev/null @@ -1,18 +0,0 @@ - 'min' // 输出数据类型 min 压缩过的 raw 正常的 - * 'file' => 'xx.json' // 输出文件路径;仅是文件名,则会取输入路径 - * ] - * - * @return string | bool - */ - public static function format($input, $output = false, array $options = []) - { - if (!is_string($input)) { - return false; - } - - $data = trim($input); - - if (file_exists($input)) { - $data = file_get_contents($input); - } - - if (!$data) { - return false; - } - - $data = preg_replace([ - // 去掉所有多行注释/* .... */ - '/\/\*.*?\*\/\s*/is', - // 去掉所有单行注释//.... - '/\/\/.*?[\r\n]/is', - // 去掉空白行 - "/(\n[\r])+/is" - ], ['', '', "\n"], $data); - - if (!$output) { - return $data; - } - - $default = ['type' => 'min']; - $options = array_merge($default, $options); - - if (file_exists($input) && (empty($options['file']) || !is_file($options['file']))) { - $dir = dirname($input); - $name = basename($input, '.json'); - $file = $dir . '/' . $name . '.' . $options['type'] . '.json'; - // save to options - $options['file'] = $file; - } - - static::saveAs($data, $options['file'], $options['type']); - return $data; - } - - /** - * @param string $data - * @param string $output - * @param array $options - * - * @return bool|int - */ - public static function saveAs(string $data, string $output, array $options = []) - { - $default = ['type' => 'min', 'file' => '']; - $options = array_merge($default, $options); - $saveDir = dirname($output); - - if (!file_exists($saveDir)) { - throw new RuntimeException('设置的json文件输出' . $saveDir . '目录不存在!'); - } - - $name = basename($output, '.json'); - $file = $saveDir . '/' . $name . '.' . $options['type'] . '.json'; - - // 去掉空白 - if ($options['type '] === 'min') { - $data = preg_replace('/(?!\w)\s*?(?!\w)/i', '', $data); - } - - return file_put_contents($file, $data); - } -} diff --git a/libs/str-utils/src/Str.php b/libs/str-utils/src/Str.php deleted file mode 100644 index e24b930..0000000 --- a/libs/str-utils/src/Str.php +++ /dev/null @@ -1,18 +0,0 @@ -body = $content; - } - - /** - * @param string $content - */ - public function write(string $content): void - { - $this->body .= $content; - } - - /** - * @param string $content - */ - public function append(string $content): void - { - $this->write($content); - } - - /** - * @param string $content - */ - public function prepend(string $content): void - { - $this->body = $content . $this->body; - } - - /** - * clear data - */ - public function clear(): string - { - $string = $this->body; - // clear - $this->body = ''; - - return $string; - } - - /** - * @return string - */ - public function getBody(): string - { - return $this->body; - } - - /** - * @param string $body - */ - public function setBody(string $body): void - { - $this->body = $body; - } - - /** - * @return string - */ - public function toString(): string - { - return $this->body; - } - - /** - * @return string - */ - public function __toString() - { - return $this->toString(); - } -} diff --git a/libs/str-utils/src/StringHelper.php b/libs/str-utils/src/StringHelper.php deleted file mode 100644 index 27d7959..0000000 --- a/libs/str-utils/src/StringHelper.php +++ /dev/null @@ -1,1078 +0,0 @@ - '/\S+/', - 'email' => '/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/', - // 'url' => '/^http(s?):\/\/(?:[A-za-z0-9-]+\.)+[A-za-z]{2,4}(?:[\/\?#][\/=\?%\-&~`@[\]\':+!\.#\w]*)?$/', - 'url' => '/^(http|https|ftp):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i', - 'currency' => '/^\d+(\.\d+)?$/', - # 货币 - 'number' => '/^\d+$/', - 'zip' => '/^\d{6}$/', - 'integer' => '/^[-\+]?\d+$/', - 'double' => '/^[-\+]?\d+(\.\d+)?$/', - 'english' => '/^[A-Za-z]+$/', - ]; - - $value = trim($value); - $name = strtolower($rule); - - // 检查是否有内置的正则表达式 - if (isset($validate[$name])) { - $rule = $validate[$name]; - } - - return preg_match($rule, $value) === 1; - } - - //////////////////////////////////////////////////////////////////////// - /// Check Length - //////////////////////////////////////////////////////////////////////// - - /** - * from Symfony - * - * @param string $string - * - * @return int - */ - public static function len(string $string): int - { - if (false === $encoding = mb_detect_encoding($string, null, true)) { - return strlen($string); - } - - return mb_strwidth($string, $encoding); - } - - public static function strlen(string $str, string $encoding = 'UTF-8'): int - { - $str = html_entity_decode($str, ENT_COMPAT, 'UTF-8'); - - return function_exists('mb_strlen') ? mb_strlen($str, $encoding) : strlen($str); - } - - /** - * @param string $string - * - * @return int - */ - public static function utf8Len(string $string): int - { - // strlen: one chinese is 3 char. - // mb_strlen: one chinese is 1 char. - // mb_strwidth: one chinese is 2 char. - return mb_strlen($string, 'utf-8'); - } - - /** - * 计算字符长度 - * - * @param string $str - * - * @return int - */ - public static function length(string $str): int - { - if ($str === '') { - return 0; - } - - if (function_exists('mb_strlen')) { - return mb_strlen($str, 'utf-8'); - } - - preg_match_all('/./u', $str, $arr); - - return count($arr[0]); - } - - /** - * @from web - * 可以统计中文字符串长度的函数 - * - * @param string $str 要计算长度的字符串 - * - * @return int - * @internal param bool $type 计算长度类型,0(默认)表示一个中文算一个字符,1表示一个中文算两个字符 - */ - public static function absLen(string $str): int - { - if (empty($str)) { - return 0; - } - - if (function_exists('mb_strwidth')) { - return mb_strwidth($str, 'utf-8'); - } - - if (function_exists('mb_strlen')) { - return mb_strlen($str, 'utf-8'); - } - - preg_match_all('/./u', $str, $ar); - - return count($ar[0]); - } - - //////////////////////////////////////////////////////////// - /// Security - //////////////////////////////////////////////////////////// - - /** - * ********************** 生成一定长度的随机字符串函数 ********************** - * - * @param int $length - 随机字符串长度 - * @param array|string $param - - * - * @return string - * @throws Exception - * @internal param string $chars - */ - public static function random(int $length, array $param = []): string - { - $param = array_merge([ - 'prefix' => '', - 'suffix' => '', - 'chars' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' - ], $param); - - $chars = $param['chars']; - $max = strlen($chars) - 1; //strlen($chars) 计算字符串的长度 - $str = ''; - - for ($i = 0; $i < $length; $i++) { - $str .= $chars[random_int(0, $max)]; - } - - return $param['prefix'] . $str . $param['suffix']; - } - - /** - * @param int $length - * - * @return string - */ - public static function genSalt(int $length = 32): string - { - return substr(str_replace('+', '.', base64_encode(hex2bin(random_token($length)))), 0, 44); - } - - /** - * @param int $length - * - * @return bool|string - */ - public static function genUid(int $length = 7): string - { - if (!is_int($length) || $length > 32 || $length < 1) { - $length = 7; - } - - return substr(hash('md5', uniqid('', true)), 0, $length); - } - - /** - * @param string $string - * @param int $padLen - * @param string $padStr - * @param int $padType - * - * @return string - */ - public static function pad(string $string, int $padLen, string $padStr = ' ', int $padType = STR_PAD_RIGHT): string - { - return $padLen > 0 ? str_pad($string, $padLen, $padStr, $padType) : $string; - } - - public static function padLeft(string $string, int $padLen, string $padStr = ' '): string - { - return $padLen > 0 ? str_pad($string, $padLen, $padStr, STR_PAD_LEFT) : $string; - } - - public static function padRight(string $string, int $padLen, string $padStr = ' '): string - { - return $padLen > 0 ? str_pad($string, $padLen, $padStr) : $string; - } - - /** - * gen UUID - * - * @param int $version - * @param null $node - * @param null $ns - * - * @return UUID - * @throws InvalidArgumentException - */ - // public static function genUUID($version = 1, $node = null, $ns = null) - // { - // return UUID::generate($version, $node, $ns); - // } - - //////////////////////////////////////////////////////////////////////// - /// Case Convert - //////////////////////////////////////////////////////////////////////// - - /** - * Convert \n and \r\n and \r to
    - * - * @param string $str String to transform - * - * @return string New string - */ - public static function nl2br(string $str): string - { - return str_replace(["\r\n", "\r", "\n"], '
    ', $str); - } - - public static function lower(string $str): string - { - return static::strtolower($str); - } - - /** - * @param string $str - * - * @return bool|string - */ - public static function strtolower(string $str): string - { - return function_exists('mb_strtolower') ? mb_strtolower($str, 'utf-8') : strtolower($str); - } - - public static function upper(string $str): string - { - return static::strtoupper($str); - } - - /** - * @param $str - * - * @return bool|string - */ - public static function strtoupper(string $str) - { - if (!is_string($str)) { - return $str; - } - - return function_exists('mb_strtoupper') ? mb_strtoupper($str, 'utf-8') : strtoupper($str); - } - - /** - * @param $str - * - * @return string - */ - public static function ucfirst(string $str): string - { - return self::strtoupper(self::substr($str, 0, 1)) . self::substr($str, 1); - } - - /** - * @param $str - * - * @return string - */ - public static function ucwords(string $str): string - { - return function_exists('mb_convert_case') ? mb_convert_case($str, MB_CASE_TITLE) : - ucwords(self::strtolower($str)); - } - - /** - * @param string $str - * @param bool $upperFirstChar - * - * @return mixed - */ - public static function camel(string $str, bool $upperFirstChar = false): string - { - return self::toCamelCase($str, $upperFirstChar); - } - - /** - * @param string $str - * @param bool $upperFirstChar - * - * @return mixed - */ - public static function toCamel(string $str, bool $upperFirstChar = false): string - { - return self::toCamelCase($str, $upperFirstChar); - } - - /** - * to camel - * - * @param string $name - * @param bool $upperFirst - * - * @return string - */ - public static function camelCase(string $name, bool $upperFirst = false): string - { - $name = trim($name, '-_'); - - // convert 'first-second' to 'firstSecond' - if (strpos($name, '-')) { - $name = ucwords(str_replace('-', ' ', $name)); - $name = str_replace(' ', '', lcfirst($name)); - } - - return $upperFirst ? ucfirst($name) : $name; - } - - /** - * Translates a string with underscores into camel case (e.g. first_name -> firstName) - * - * @param string $str - * @param bool $upperFirst - * - * @return mixed - */ - public static function toCamelCase(string $str, bool $upperFirst = false): string - { - $str = (string)self::strtolower($str); - - if ($upperFirst) { - $str = self::ucfirst($str); - } - - return preg_replace_callback('/_+([a-z])/', function ($c) { - return strtoupper($c[1]); - }, $str); - } - - public static function snake(string $str, string $sep = '_'): string - { - return self::toSnakeCase($str, $sep); - } - - public static function toSnake(string $str, string $sep = '_'): string - { - return self::toSnakeCase($str, $sep); - } - - /** - * Transform a CamelCase string to underscore_case string - * - * @param string $str - * @param string $sep - * - * @return string - */ - public static function toSnakeCase(string $str, string $sep = '_'): string - { - // 'CMSCategories' => 'cms_categories' - // 'RangePrice' => 'range_price' - return self::lower(trim(preg_replace('/([A-Z][a-z])/', $sep . '$1', $str), $sep)); - } - - /** - * 驼峰式 <=> 下划线式 - * - * @param string $str [description] - * @param bool $toCamelCase - * true : 驼峰式 => 下划线式 - * false : 驼峰式 <= 下划线式 - * - * @return string - */ - public static function nameChange(string $str, bool $toCamelCase = true): string - { - $str = trim($str); - - // 默认 :下划线式 =>驼峰式 - if ($toCamelCase) { - if (strpos($str, '_') === false) { - return $str; - } - - $arr_char = explode('_', strtolower($str)); - $newString = array_shift($arr_char); - - foreach ($arr_char as $val) { - $newString .= ucfirst($val); - } - - return $newString; - } - - // 驼峰式 => 下划线式 - return strtolower(preg_replace('/((?<=[a-z])(?=[A-Z]))/', '_', $str)); - } - - //////////////////////////////////////////////////////////////////////// - /// Convert to array - //////////////////////////////////////////////////////////////////////// - - /** - * var_dump(str2array('34,56,678, 678, 89, ')); - * - * @param string $str - * @param string $sep - * - * @return array - */ - public static function str2array(string $str, string $sep = ','): array - { - $str = trim($str, "$sep "); - - if (!$str) { - return []; - } - - return preg_split("/\s*$sep\s*/", $str, -1, PREG_SPLIT_NO_EMPTY); - } - - public static function toArray(string $string, string $delimiter = ',', int $limit = 0): array - { - $string = trim($string, "$delimiter "); - if ($string === '') { - return []; - } - - $values = []; - $rawList = $limit < 1 ? explode($delimiter, $string) : explode($delimiter, $string, $limit); - - foreach ($rawList as $val) { - if (($val = trim($val)) !== '') { - $values[] = $val; - } - } - - return $values; - } - - public static function explode(string $str, string $separator = '.', int $limit = 0): array - { - return static::split2Array($str, $separator, $limit); - } - - /** - * @param string $string - * @param string $delimiter - * @param int $limit - * - * @return array - */ - public static function split2Array(string $string, string $delimiter = ',', int $limit = 0): array - { - $string = trim($string, "$delimiter "); - - if (!strpos($string, $delimiter)) { - return [$string]; - } - - if ($limit < 1) { - $list = explode($delimiter, $string); - } else { - $list = explode($delimiter, $string, $limit); - } - - return array_values(array_filter(array_map('trim', $list), 'strlen')); - } - - /** - * @param string $string - * @param int $width - * - * @return array - */ - public static function splitByWidth(string $string, int $width): array - { - // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. - // additionally, array_slice() is not enough as some character has doubled width. - // we need a function to split string not by character count but by string width - if (false === $encoding = mb_detect_encoding($string, null, true)) { - return str_split($string, $width); - } - - $utf8String = mb_convert_encoding($string, 'utf8', $encoding); - $lines = []; - $line = ''; - - foreach (preg_split('//u', $utf8String) as $char) { - // test if $char could be appended to current line - if (mb_strwidth($line . $char, 'utf8') <= $width) { - $line .= $char; - continue; - } - - // if not, push current line to array and make new line - $lines[] = str_pad($line, $width); - $line = $char; - } - - if ('' !== $line) { - $lines[] = count($lines) ? str_pad($line, $width) : $line; - } - - mb_convert_variables($encoding, 'utf8', $lines); - - return $lines; - } - - //////////////////////////////////////////////////////////////////////// - /// Truncate - //////////////////////////////////////////////////////////////////////// - - /** - * @param string $str - * @param int $start - * @param int|null $length - * @param string $encoding - * - * @return bool|string - */ - public static function substr(string $str, int $start, int $length = null, string $encoding = 'utf-8') - { - if (function_exists('mb_substr')) { - return mb_substr($str, $start, ($length === null ? self::strlen($str) : (int)$length), $encoding); - } - - return substr($str, $start, ($length === null ? self::strlen($str) : (int)$length)); - } - - /** - * @from web - * utf-8编码下截取中文字符串,参数可以参照substr函数 - * - * @param string $str 要进行截取的字符串 - * @param int $start 要进行截取的开始位置,负数为反向截取 - * @param int $end 要进行截取的长度 - * - * @return string - */ - public static function utf8SubStr(string $str, int $start = 0, int $end = null): string - { - if (empty($str)) { - return false; - } - - if (function_exists('mb_substr')) { - if (func_num_args() >= 3) { - $end = func_get_arg(2); - - return mb_substr($str, $start, $end, 'utf-8'); - } - - mb_internal_encoding('UTF-8'); - - return mb_substr($str, $start); - - } - - $null = ''; - preg_match_all('/./u', $str, $ar); - - if (func_num_args() >= 3) { - $end = func_get_arg(2); - return implode($null, array_slice($ar[0], $start, $end)); - } - - return implode($null, array_slice($ar[0], $start)); - } - - - /** - * @from web - * 中文截取,支持gb2312,gbk,utf-8,big5 * - * - * @param string $str 要截取的字串 - * @param int $start 截取起始位置 - * @param int $length 截取长度 - * @param string $charset utf-8|gb2312|gbk|big5 编码 - * @param bool $suffix 是否加尾缀 - * - * @return string - */ - public static function zhSubStr($str, $start = 0, $length = 0, $charset = 'utf-8', $suffix = true): string - { - if (function_exists('mb_substr')) { - if (mb_strlen($str, $charset) <= $length) { - return $str; - } - - $slice = mb_substr($str, $start, $length, $charset); - } else { - $re['utf-8'] = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/"; - $re['gb2312'] = "/[\x01-\x7f]|[\xb0-\xf7][\xa0-\xfe]/"; - $re['gbk'] = "/[\x01-\x7f]|[\x81-\xfe][\x40-\xfe]/"; - $re['big5'] = "/[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|\xa1-\xfe])/"; - - preg_match_all($re[$charset], $str, $match); - if (count($match[0]) <= $length) { - return $str; - } - - $slice = implode('', array_slice($match[0], $start, $length)); - } - - return (bool)$suffix ? $slice . '…' : $slice; - } - - /** - * Truncate strings - * - * @param string $str - * @param int $maxLength Max length - * @param string $suffix Suffix optional - * - * @return string $str truncated - */ - /* CAUTION : Use it only on module hookEvents. - ** For other purposes use the smarty function instead */ - public static function truncate(string $str, $maxLength, $suffix = '...'): string - { - if (self::strlen($str) <= $maxLength) { - return $str; - } - - $str = utf8_decode($str); - - return utf8_encode(substr($str, 0, $maxLength - self::strlen($suffix)) . $suffix); - } - - /** - * 字符截断输出 - * - * @param string $str - * @param int $start - * @param null|int $length - * - * @return string - */ - public static function truncate2(string $str, int $start, int $length = null): string - { - if (!$length) { - $length = $start; - $start = 0; - } - - if (strlen($str) <= $length) { - return $str; - } - - if (function_exists('mb_substr')) { - $str = mb_substr(strip_tags($str), $start, $length, 'utf-8'); - } else { - $str = substr($str, $start, $length) . '...'; - } - - return $str; - } - - /** - * Copied from CakePHP String utility file - * - * @param string $text - * @param int $length - * @param array $options - * - * @return bool|string - */ - public static function truncate3(string $text, int $length = 120, array $options = []) - { - $default = [ - 'ellipsis' => '...', - 'exact' => true, - 'html' => true - ]; - - $options = array_merge($default, $options); - $ellipsis = $options['ellipsis']; - $exact = $options['exact']; - $html = $options['html']; - - /** - * @var string $ellipsis - * @var bool $exact - * @var bool $html - */ - if ($html) { - if (self::strlen(preg_replace('/<.*?>/', '', $text)) <= $length) { - return $text; - } - - $total_length = self::strlen(strip_tags($ellipsis)); - $open_tags = $tags = []; - $truncate = ''; - preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER); - - foreach ($tags as $tag) { - if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/', $tag[2])) { - if (preg_match('/<[\w]+[^>]*>/', $tag[0])) { - array_unshift($open_tags, $tag[2]); - } elseif (preg_match('/<\/([\w]+)[^>]*>/', $tag[0], $close_tag)) { - $pos = array_search($close_tag[1], $open_tags, true); - if ($pos !== false) { - array_splice($open_tags, $pos, 1); - } - } - } - $truncate .= $tag[1]; - $content_length = self::strlen(preg_replace('/&[0-9a-z]{2,8};|&#[\d]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', - $tag[3])); - - if ($content_length + $total_length > $length) { - $left = $length - $total_length; - $entities_length = 0; - - if (preg_match_all('/&[0-9a-z]{2,8};|&#[\d]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, - PREG_OFFSET_CAPTURE) - ) { - foreach ((array)$entities[0] as $entity) { - if ($entity[1] + 1 - $entities_length <= $left) { - $left--; - $entities_length += self::strlen($entity[0]); - } else { - break; - } - } - } - - $truncate .= self::substr($tag[3], 0, $left + $entities_length); - break; - } - - $truncate .= $tag[3]; - $total_length += $content_length; - - if ($total_length >= $length) { - break; - } - } - } else { - if (self::strlen($text) <= $length) { - return $text; - } - - $truncate = self::substr($text, 0, $length - self::strlen($ellipsis)); - } - - $open_tags = null; - - if (!$exact) { - $spacepos = self::strrpos($truncate, ' '); - if ($html) { - $truncate_check = self::substr($truncate, 0, $spacepos); - $last_open_tag = self::strrpos($truncate_check, '<'); - $last_close_tag = self::strrpos($truncate_check, '>'); - - if ($last_open_tag > $last_close_tag) { - preg_match_all('/<[\w]+[^>]*>/', $truncate, $last_tag_matches); - $last_tag = array_pop($last_tag_matches[0]); - $spacepos = self::strrpos($truncate, $last_tag) + self::strlen($last_tag); - } - - $bits = self::substr($truncate, $spacepos); - preg_match_all('/<\/([a-z]+)>/', $bits, $dropped_tags, PREG_SET_ORDER); - - /** @var array $dropped_tags */ - if (!empty($dropped_tags)) { - if (!empty($open_tags)) { - foreach ($dropped_tags as $closing_tag) { - if (!in_array($closing_tag[1], $open_tags, true)) { - array_unshift($open_tags, $closing_tag[1]); - } - } - } else { - foreach ($dropped_tags as $closing_tag) { - $open_tags[] = $closing_tag[1]; - } - } - } - } - - $truncate = self::substr($truncate, 0, $spacepos); - } - - $truncate .= $ellipsis; - - if ($html && $open_tags) { - foreach ((array)$open_tags as $tag) { - $truncate .= ''; - } - } - - return $truncate; - } - - //////////////////////////////////////////////////////////////////////// - /// Format - //////////////////////////////////////////////////////////////////////// - - /** - * [format description] - * - * @param $str - * @param array $replaceParams 用于 str_replace('search','replace',$str ) - * @param array $pregParams 用于 preg_replace('pattern','replace',$str) - * - * @return string [type] [description] - * @example - * $pregParams = [ - * 'xx', //'pattern' - * 'yy', //'replace' - * ] - * * $pregParams = [ - * ['xx','xx2'], //'pattern' - * ['yy','yy2'], //'replace' - * ] - * @example - * $replaceParams = [ - * 'xx', //'search' - * 'yy', //'replace' - * ] - * $replaceParams = [ - * ['xx','xx2'], //'search' - * ['yy','yy2'], //'replace' - * ] - */ - public static function format($str, array $replaceParams = [], array $pregParams = []): string - { - if (!is_string($str) || !$str || (!$replaceParams && !$pregParams)) { - return $str; - } - - if ($replaceParams && count($replaceParams) === 2) { - [$search, $replace] = $replaceParams; - $str = str_replace($search, $replace, $str); - } - - if ($pregParams && count($pregParams) === 2) { - [$pattern, $replace] = $pregParams; - $str = preg_replace($pattern, $replace, $str); - } - - return trim($str); - } - - /** - * 格式化,用空格分隔各个词组 - * - * @param string $keyword 字符串 - * - * @return string 格式化后的字符串 - */ - public static function wordFormat($keyword): string - { - // 将全角角逗号换为空格 - $keyword = str_replace([',', ','], ' ', $keyword); - - return preg_replace([ - // 去掉两个空格以上的 - '/\s(?=\s)/', - // 将非空格替换为一个空格 - '/[\n\r\t]/' - ], ['', ' '], trim($keyword)); - } - - /** - * 缩进格式化内容,去空白/注释 - * - * @param $fileName - * @param int $type - * - * @return mixed - */ - public static function deleteStripSpace($fileName, $type = 0) - { - $data = trim(file_get_contents($fileName)); - $data = 0 === strpos($data, '' ? substr($data, 0, -2) : $data; - - //去掉所有注释 换行空白保留 - if ((int)$type === 1) { - $preg_arr = [ - '/\/\*.*?\*\/\s*/is' // 去掉所有多行注释/* .... */ - , - '/\/\/.*?[\r\n]/is' // 去掉所有单行注释//.... - , - '/\#.*?[\r\n]/is' // 去掉所有单行注释 #.... - ]; - - return preg_replace($preg_arr, '', $data); - } - - $preg_arr = [ - '/\/\*.*?\*\/\s*/is' // 去掉所有多行注释 /* .... */ - , - '/\/\/.*?[\r\n]/is' // 去掉所有单行注释 //.... - , - '/\#.*?[\r\n]/is' // 去掉所有单行注释 #.... - , - '/(?!\w)\s*?(?!\w)/is' //去掉空白行 - ]; - - return preg_replace($preg_arr, '', $data); - } -} diff --git a/libs/str-utils/src/Token.php b/libs/str-utils/src/Token.php deleted file mode 100644 index b4de0a7..0000000 --- a/libs/str-utils/src/Token.php +++ /dev/null @@ -1,210 +0,0 @@ -query(['name' => $_POST['name'] ]); - * 1. - * gen: - * $password = Token::gen('123456'); - * verify: - * Token::verify($user['password'], '123456'); - * 2. - * gen: - * $password = Token::hash('123456'); - * verify: - * Token::verifyHash($user['password'], '123456'); - */ -class Token -{ - /** - * 指明应该使用的算法 - * $2a BLOWFISH算法。 - * $5 SHA-256 - * $6 SHA-512 - * - * @var string - */ - private static $algo = '$2y'; - - /** - * cost parameter 就是成本参数 - * $10 这是以2为底的对数,指示计算循环迭代的次数(10 => 2^10 = 1024),取值可以从04到31。 - * - * @var string - */ - private static $cost = '$10'; - - /** - * *******生成唯一序列号******* - * - * @param $var array || obj - * - * @return string - */ - public static function md5($var): string - { - //serialize()序列化,串行化 - return md5(md5(serialize($var))); - } - - /** - * @return string - */ - public static function uniqueSalt(): string - { - return (string)substr(sha1(mt_rand()), 0, 22); - } - - /** - * @param string $pwd - * @param string $algo - * @param array $opts - * - * @return bool|string - */ - public static function pwdHash(string $pwd, string $algo, array $opts = []) - { - $opts = array_merge([ - 'cost' => 9 - ], $opts); - - return password_hash($pwd, $algo, $opts); - } - - /** - * @param string $pwd - * @param string $hash - * - * @return bool|string - */ - public static function pwdVerify(string $pwd, string $hash) - { - return password_verify($pwd, $hash); - } - - /** - * this will be used to generate a hash - * - * @param $password - * - * @return string - */ - public static function gen(string $password): string - { - return crypt($password, self::$algo . self::$cost . '$' . self::uniqueSalt()); - } - - /** - * this will be used to compare a password against a hash - * - * @param string $hash - * @param string $password the user input - * - * @return bool - */ - public static function verify(string $hash, string $password): bool - { - return hash_equals($hash, crypt($password, $hash)); - } - - /** - * 2 生成 - * - * @param $password - * @param int $cost - * - * @return string - * @throws RuntimeException - * @todo from php.net - */ - public static function hash(string $password, int $cost = 11): string - { - // $bytes = \random_bytes(17); - $bytes = openssl_random_pseudo_bytes(17, $cStrong); - - if (false === $bytes || false === $cStrong) { - throw new RuntimeException('exec gen hash error!'); - } - - /* To generate the salt, first generate enough random bytes. Because - * base64 returns one character for each 6 bits, the we should generate - * at least 22*6/8=16.5 bytes, so we generate 17. Then we get the first - * 22 base64 characters - */ - $salt = substr(base64_encode($bytes), 0, 22); - /* As blowfish takes a salt with the alphabet ./A-Za-z0-9 we have to - * replace any '+' in the base64 string with '.'. We don't have to do - * anything about the '=', as this only occurs when the b64 string is - * padded, which is always after the first 22 characters. - */ - $salt = str_replace('+', '.', $salt); - /* Next, create a string that will be passed to crypt, containing all - * of the settings, separated by dollar signs - */ - $param = '$' . implode('$', [ - '2x', //select the most secure version of blowfish (>=PHP 5.3.7) - str_pad($cost, 2, '0', STR_PAD_LEFT), //add the cost in two digits - $salt //add the salt - ]); - - //now do the actual hashing - return crypt($password, $param); - } - - /** - * 2 验证 - * Check the password against a hash generated by the generate_hash - * function. - * - * @param $hash - * @param $password - * - * @return bool - */ - public static function verifyHash(string $hash, string $password): bool - { - /* Regenerating the with an available hash as the options parameter should - * produce the same hash if the same password is passed. - */ - return crypt($password, $hash) === $hash; - } - - /** - * 生成guid - * - * @return string - */ - public static function GUID(): string - { - mt_srand((double)microtime() * 10000); - - $charId = strtolower(md5(uniqid(mt_rand(), true))); - // $hyphen = chr(45); - $uuid = substr($charId, 0, 8) . substr($charId, 8, 4) . substr($charId, 12, 4) . substr($charId, 16, - 4) . substr($charId, 20, 12); - - return $uuid; - } - -} diff --git a/libs/str-utils/src/UUID.php b/libs/str-utils/src/UUID.php deleted file mode 100644 index d0198bd..0000000 --- a/libs/str-utils/src/UUID.php +++ /dev/null @@ -1,472 +0,0 @@ -bytes = $uuid; - - // Optimize the most common use - $this->string = bin2hex(substr($uuid, 0, 4)) . '-' . bin2hex(substr($uuid, 4, 2)) . '-' . bin2hex(substr($uuid, - 6, 2)) . '-' . bin2hex(substr($uuid, 8, 2)) . '-' . bin2hex(substr($uuid, 10, 6)); - } - - /** - * Generates a Version 1 UUID. - * These are derived from the time at which they were generated. - * - * @param string $node - * - * @return string - * @throws Exception - */ - protected static function mintTime(string $node = null): string - { - /** Get time since Gregorian calendar reform in 100ns intervals - * This is exceedingly difficult because of PHP's (and pack()'s) - * integer size limits. - * Note that this will never be more accurate than to the microsecond. - */ - $time = microtime(1) * 10000000 + static::INTERVAL; - - // Convert to a string representation - $time = sprintf('%F', $time); - - //strip decimal point - preg_match("/^\d+/", $time, $time); - - // And now to a 64-bit binary representation - $time = base_convert($time[0], 10, 16); - $time = pack('H*', str_pad($time, 16, '0', STR_PAD_LEFT)); - - // Reorder bytes to their proper locations in the UUID - $uuid = $time[4] . $time[5] . $time[6] . $time[7] . $time[2] . $time[3] . $time[0] . $time[1]; - - // Generate a random clock sequence - $uuid .= static::randomBytes(2); - - // set variant - $uuid[8] = chr(ord($uuid[8]) & static::CLEAR_VAR | static::VAR_RFC); - - // set version - $uuid[6] = chr(ord($uuid[6]) & static::CLEAR_VER | static::VERSION_1); - - // Set the final 'node' parameter, a MAC address - if (null !== $node) { - $node = static::makeBin($node, 6); - } - - // If no node was provided or if the node was invalid, - // generate a random MAC address and set the multicast bit - if (null === $node) { - $node = static::randomBytes(6); - $node[0] = pack('C', ord($node[0]) | 1); - } - - $uuid .= $node; - return $uuid; - } - - /** - * Randomness is returned as a string of bytes - * - * @param int $bytes - * - * @return string - * @throws Exception - */ - public static function randomBytes($bytes): string - { - return random_bytes($bytes); - } - - /** - * Insure that an input string is either binary or hexadecimal. - * Returns binary representation, or false on failure. - * - * @param string|self $str - * @param integer $len - * - * @return string|null - */ - protected static function makeBin($str, $len): ?string - { - if ($str instanceof self) { - return $str->bytes; - } - - if (strlen($str) === $len) { - return $str; - } - - $str = (string)preg_replace([ - // strip URN scheme and namespace - '/^urn:uuid:/is', - // strip non-hex characters - '/[^a-f0-9]/is', - ], '', $str); - - if (strlen($str) !== ($len * 2)) { - return null; - } - - return pack('H*', $str); - } - - /** - * Generates a Version 3 or Version 5 UUID. - * These are derived from a hash of a name and its namespace, in binary form. - * - * @param int $ver - * @param string $node - * @param string|null $ns - * - * @return string - * @throws InvalidArgumentException - */ - protected static function mintName($ver, $node, $ns): string - { - if (empty($node)) { - throw new InvalidArgumentException('A name-string is required for Version 3 or 5 UUIDs.'); - } - - // if the namespace UUID isn't binary, make it so - $ns = static::makeBin($ns, 16); - if (null === $ns) { - throw new InvalidArgumentException('A binary namespace is required for Version 3 or 5 UUIDs.'); - } - - $version = $uuid = null; - - switch ($ver) { - case static::MD5: - $version = static::VERSION_3; - $uuid = md5($ns . $node, 1); - break; - case static::SHA1: - $version = static::VERSION_5; - $uuid = substr(sha1($ns . $node, 1), 0, 16); - break; - default: - // no default really required here - } - - // set variant - $uuid[8] = chr(ord($uuid[8]) & static::CLEAR_VAR | static::VAR_RFC); - - // set version - $uuid[6] = chr(ord($uuid[6]) & static::CLEAR_VER | $version); - - return $uuid; - } - - /** - * Generate a Version 4 UUID. - * These are derived solely from random numbers. - * generate random fields - * - * @return string - * @throws Exception - */ - protected static function mintRand(): string - { - $uuid = static::randomBytes(16); - // set variant - $uuid[8] = chr(ord($uuid[8]) & static::CLEAR_VAR | static::VAR_RFC); - // set version - $uuid[6] = chr(ord($uuid[6]) & static::CLEAR_VER | static::VERSION_4); - - return $uuid; - } - - /** - * Import an existing UUID - * - * @param string $uuid - * - * @return Uuid - * @throws InvalidArgumentException - */ - public static function import(string $uuid): UUID - { - return new static(static::makeBin($uuid, 16)); - } - - /** - * Compares the binary representations of two UUIDs. - * The comparison will return true if they are bit-exact, - * or if neither is valid. - * - * @param string $a - * @param string $b - * - * @return string|string - */ - public static function compare(string $a, string $b): string - { - return static::makeBin($a, 16) === static::makeBin($b, 16); - } - - /** - * Import and validate an UUID - * - * @param Uuid|string $uuid - * - * @return boolean - * @throws InvalidArgumentException - */ - public static function validate(string $uuid): bool - { - return (boolean)preg_match('~' . static::VALID_UUID_REGEX . '~', static::import($uuid)->string); - } - - public function __isset($var) - { - // - } - - public function __set($var, $val) - { - // - } - - /** - * @param string $var - * - * @return string|int|NULL - */ - public function __get(string $var) - { - switch ($var) { - case 'bytes': - return $this->bytes; - break; - case 'hex': - return bin2hex($this->bytes); - break; - case 'node': - if (ord($this->bytes[6]) >> 4 === 1) { - return bin2hex(substr($this->bytes, 10)); - } - - return null; - break; - case 'string': - return $this->__toString(); - break; - case 'time': - if (ord($this->bytes[6]) >> 4 === 1) { - // Restore contiguous big-endian byte order - $time = bin2hex($this->bytes[6] . $this->bytes[7] . $this->bytes[4] . $this->bytes[5] . $this->bytes[0] . $this->bytes[1] . $this->bytes[2] . $this->bytes[3]); - // Clear version flag - $time[0] = '0'; - - // Do some reverse arithmetic to get a Unix timestamp - return (hexdec($time) - static::INTERVAL) / 10000000; - } - - break; - case 'urn': - return 'urn:uuid:' . $this->__toString(); - break; - case 'variant': - $byte = ord($this->bytes[8]); - if ($byte >= static::VAR_RES) { - return 3; - } - - if ($byte >= static::VAR_MS) { - return 2; - } - - if ($byte >= static::VAR_RFC) { - return 1; - } - - return 0; - break; - case 'version': - return ord($this->bytes[6]) >> 4; - break; - default: - return null; - break; - } - - return null; - } - - /** - * Return the UUID - * - * @return string - */ - public function __toString() - { - return $this->string; - } -} diff --git a/libs/str-utils/src/UrlHelper.php b/libs/str-utils/src/UrlHelper.php deleted file mode 100644 index 7450510..0000000 --- a/libs/str-utils/src/UrlHelper.php +++ /dev/null @@ -1,242 +0,0 @@ - 0; - } else { - $opts = [ - 'http' => ['timeout' => 5,] - ]; - $context = stream_context_create($opts); - $resource = file_get_contents($url, false, $context); - - return (bool)$resource; - } - - return false; - } - - // Build arrays of values we need to decode before parsing - protected static $entities = [ - '%21', - '%2A', - '%27', - '%28', - '%29', - '%3B', - '%3A', - '%40', - '%26', - '%3D', - '%24', - '%2C', - '%2F', - '%3F', - '%23', - '%5B', - '%5D' - ]; - - protected static $replacements = [ - '!', - '*', - "'", - '(', - ')', - ';', - ':', - '@', - '&', - '=', - '$', - ',', - '/', - '?', - '#', - '[', - ']' - ]; - - public static function parseUrl(string $url): array - { - $result = []; - - // Create encoded URL with special URL characters decoded so it can be parsed - // All other characters will be encoded - $encodedURL = str_replace(self::$entities, self::$replacements, urlencode($url)); - - // Parse the encoded URL - $encodedParts = parse_url($encodedURL); - - // Now, decode each value of the resulting array - if ($encodedParts) { - foreach ((array)$encodedParts as $key => $value) { - $result[$key] = urldecode(str_replace(self::$replacements, self::$entities, $value)); - } - } - - return $result; - } - - /** - * url_encode form urlencode(),但是 : / ? & = ...... 几个符号不会被转码为 %3A %2F %3F %26 %3D ...... - * $url="ftp://ud03:password@www.xxx.net/中文/中文.rar"; - * $url1 = url_encode1($url); - * //ftp://ud03:password@www.xxx.net/%E4%B8%AD%E6%96%87/%E4%B8%AD%E6%96%87.rar - * $url2 = urldecode($url); - * echo $url1.PHP_EOL.$url2.PHP_EOL; - * - * @param $url - * - * @return mixed|string [type] [description] - */ - public static function encode(string $url) - { - $url = trim($url); - - if ($url === '') { - return $url; - } - - // 若已被编码的url,将被解码,再继续重新编码 - $url = urldecode($url); - $encodeUrl = urlencode($url); - $encodeUrl = str_replace(self::$entities, self::$replacements, $encodeUrl); - - return $encodeUrl; - } - - /** - * [urlEncode 会先转换编码] - * $url="ftp://ud03:password@www.xxx.net/中文/中文.rar"; - * $url1 = url_encode($url); - * //ftp://ud03:password@www.xxx.net/%C3%A4%C2%B8%C2%AD%C3%A6%C2%96%C2%87/%C3%A4%C2%B8%C2%AD%C3%A6%C2%96%C2%87.rar - * $url2 = urldecode($url); - * echo $url1.PHP_EOL.$url2; - * - * @param string $url [description] - * - * @return mixed|string [type] [description] - */ - public static function encode2(string $url) - { - if (!$url = trim($url)) { - return $url; - } - - // 若已被编码的url,将被解码,再继续重新编码 - $url = urldecode($url); - $encodeUrl = rawurlencode(mb_convert_encoding($url, 'utf-8')); - // $url = rawurlencode($url); - $encodeUrl = str_replace(self::$entities, self::$replacements, $encodeUrl); - - return $encodeUrl; - } -} diff --git a/libs/str-utils/test/boot.php b/libs/str-utils/test/boot.php deleted file mode 100644 index b00f6f4..0000000 --- a/libs/str-utils/test/boot.php +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - test/ - - - - - - src - - - diff --git a/libs/autoload.php b/src/autoload.php similarity index 100% rename from libs/autoload.php rename to src/autoload.php diff --git a/libs/arr-utils/.gitignore b/src/collection/.gitignore similarity index 100% rename from libs/arr-utils/.gitignore rename to src/collection/.gitignore diff --git a/libs/arr-utils/LICENSE b/src/collection/LICENSE similarity index 100% rename from libs/arr-utils/LICENSE rename to src/collection/LICENSE diff --git a/libs/collection/README.md b/src/collection/README.md similarity index 100% rename from libs/collection/README.md rename to src/collection/README.md diff --git a/libs/collection/composer.json b/src/collection/composer.json similarity index 100% rename from libs/collection/composer.json rename to src/collection/composer.json diff --git a/libs/arr-utils/phpunit.xml.dist b/src/collection/phpunit.xml.dist similarity index 100% rename from libs/arr-utils/phpunit.xml.dist rename to src/collection/phpunit.xml.dist diff --git a/libs/collection/src/ActiveData.php b/src/collection/src/ActiveData.php similarity index 100% rename from libs/collection/src/ActiveData.php rename to src/collection/src/ActiveData.php diff --git a/libs/collection/src/Collection.php b/src/collection/src/Collection.php similarity index 100% rename from libs/collection/src/Collection.php rename to src/collection/src/Collection.php diff --git a/libs/collection/src/CollectionInterface.php b/src/collection/src/CollectionInterface.php similarity index 100% rename from libs/collection/src/CollectionInterface.php rename to src/collection/src/CollectionInterface.php diff --git a/libs/collection/src/Configuration.php b/src/collection/src/Configuration.php similarity index 100% rename from libs/collection/src/Configuration.php rename to src/collection/src/Configuration.php diff --git a/libs/collection/src/JsonMessage.php b/src/collection/src/JsonMessage.php similarity index 100% rename from libs/collection/src/JsonMessage.php rename to src/collection/src/JsonMessage.php diff --git a/libs/collection/src/Language.php b/src/collection/src/Language.php similarity index 100% rename from libs/collection/src/Language.php rename to src/collection/src/Language.php diff --git a/libs/collection/src/LiteCollection.php b/src/collection/src/LiteCollection.php similarity index 100% rename from libs/collection/src/LiteCollection.php rename to src/collection/src/LiteCollection.php diff --git a/libs/collection/src/SimpleCollection.php b/src/collection/src/SimpleCollection.php similarity index 100% rename from libs/collection/src/SimpleCollection.php rename to src/collection/src/SimpleCollection.php diff --git a/libs/collection/test/LanguageTest.php b/src/collection/test/LanguageTest.php similarity index 100% rename from libs/collection/test/LanguageTest.php rename to src/collection/test/LanguageTest.php diff --git a/libs/collection/test/boot.php b/src/collection/test/boot.php similarity index 100% rename from libs/collection/test/boot.php rename to src/collection/test/boot.php diff --git a/libs/collection/test/testdata/en/response.php b/src/collection/test/testdata/en/response.php similarity index 100% rename from libs/collection/test/testdata/en/response.php rename to src/collection/test/testdata/en/response.php diff --git a/libs/collection/test/testdata/zh-CN/response.php b/src/collection/test/testdata/zh-CN/response.php similarity index 100% rename from libs/collection/test/testdata/zh-CN/response.php rename to src/collection/test/testdata/zh-CN/response.php diff --git a/libs/collection/.gitignore b/src/data-parser/.gitignore similarity index 100% rename from libs/collection/.gitignore rename to src/data-parser/.gitignore diff --git a/libs/cli-utils/LICENSE b/src/data-parser/LICENSE similarity index 100% rename from libs/cli-utils/LICENSE rename to src/data-parser/LICENSE diff --git a/libs/data-parser/README.md b/src/data-parser/README.md similarity index 100% rename from libs/data-parser/README.md rename to src/data-parser/README.md diff --git a/libs/data-parser/composer.json b/src/data-parser/composer.json similarity index 100% rename from libs/data-parser/composer.json rename to src/data-parser/composer.json diff --git a/libs/data-parser/phpunit.xml.dist b/src/data-parser/phpunit.xml.dist similarity index 100% rename from libs/data-parser/phpunit.xml.dist rename to src/data-parser/phpunit.xml.dist diff --git a/libs/data-parser/src/AbstractDataParser.php b/src/data-parser/src/AbstractDataParser.php similarity index 100% rename from libs/data-parser/src/AbstractDataParser.php rename to src/data-parser/src/AbstractDataParser.php diff --git a/libs/data-parser/src/DataParserAwareTrait.php b/src/data-parser/src/DataParserAwareTrait.php similarity index 100% rename from libs/data-parser/src/DataParserAwareTrait.php rename to src/data-parser/src/DataParserAwareTrait.php diff --git a/libs/data-parser/src/DataParserInterface.php b/src/data-parser/src/DataParserInterface.php similarity index 100% rename from libs/data-parser/src/DataParserInterface.php rename to src/data-parser/src/DataParserInterface.php diff --git a/libs/data-parser/src/JsonParser.php b/src/data-parser/src/JsonParser.php similarity index 100% rename from libs/data-parser/src/JsonParser.php rename to src/data-parser/src/JsonParser.php diff --git a/libs/data-parser/src/MsgPackParser.php b/src/data-parser/src/MsgPackParser.php similarity index 100% rename from libs/data-parser/src/MsgPackParser.php rename to src/data-parser/src/MsgPackParser.php diff --git a/libs/data-parser/src/PhpParser.php b/src/data-parser/src/PhpParser.php similarity index 100% rename from libs/data-parser/src/PhpParser.php rename to src/data-parser/src/PhpParser.php diff --git a/libs/data-parser/src/SwooleParser.php b/src/data-parser/src/SwooleParser.php similarity index 100% rename from libs/data-parser/src/SwooleParser.php rename to src/data-parser/src/SwooleParser.php diff --git a/libs/data-parser/test/JsonParserTest.php b/src/data-parser/test/JsonParserTest.php similarity index 100% rename from libs/data-parser/test/JsonParserTest.php rename to src/data-parser/test/JsonParserTest.php diff --git a/libs/data-parser/test/PhpParserTest.php b/src/data-parser/test/PhpParserTest.php similarity index 100% rename from libs/data-parser/test/PhpParserTest.php rename to src/data-parser/test/PhpParserTest.php diff --git a/libs/data-parser/test/boot.php b/src/data-parser/test/boot.php similarity index 100% rename from libs/data-parser/test/boot.php rename to src/data-parser/test/boot.php diff --git a/libs/dev-helper/Console/DevController.php b/src/dev-helper/Console/DevController.php similarity index 100% rename from libs/dev-helper/Console/DevController.php rename to src/dev-helper/Console/DevController.php diff --git a/libs/data-parser/.gitignore b/src/di/.gitignore similarity index 100% rename from libs/data-parser/.gitignore rename to src/di/.gitignore diff --git a/libs/collection/LICENSE b/src/di/LICENSE similarity index 100% rename from libs/collection/LICENSE rename to src/di/LICENSE diff --git a/libs/di/README.md b/src/di/README.md similarity index 100% rename from libs/di/README.md rename to src/di/README.md diff --git a/libs/di/composer.json b/src/di/composer.json similarity index 100% rename from libs/di/composer.json rename to src/di/composer.json diff --git a/libs/di/example/di.php b/src/di/example/di.php similarity index 100% rename from libs/di/example/di.php rename to src/di/example/di.php diff --git a/libs/cli-utils/phpunit.xml.dist b/src/di/phpunit.xml.dist similarity index 100% rename from libs/cli-utils/phpunit.xml.dist rename to src/di/phpunit.xml.dist diff --git a/libs/di/src/CallableResolver.php b/src/di/src/CallableResolver.php similarity index 100% rename from libs/di/src/CallableResolver.php rename to src/di/src/CallableResolver.php diff --git a/libs/di/src/CallableResolverAwareTrait.php b/src/di/src/CallableResolverAwareTrait.php similarity index 100% rename from libs/di/src/CallableResolverAwareTrait.php rename to src/di/src/CallableResolverAwareTrait.php diff --git a/libs/di/src/Container.php b/src/di/src/Container.php similarity index 100% rename from libs/di/src/Container.php rename to src/di/src/Container.php diff --git a/libs/di/src/DIManager.php b/src/di/src/DIManager.php similarity index 100% rename from libs/di/src/DIManager.php rename to src/di/src/DIManager.php diff --git a/libs/di/src/Exception/DependencyResolutionException.php b/src/di/src/Exception/DependencyResolutionException.php similarity index 100% rename from libs/di/src/Exception/DependencyResolutionException.php rename to src/di/src/Exception/DependencyResolutionException.php diff --git a/libs/di/src/Exception/NotFoundException.php b/src/di/src/Exception/NotFoundException.php similarity index 100% rename from libs/di/src/Exception/NotFoundException.php rename to src/di/src/Exception/NotFoundException.php diff --git a/libs/di/src/NameAliasTrait.php b/src/di/src/NameAliasTrait.php similarity index 100% rename from libs/di/src/NameAliasTrait.php rename to src/di/src/NameAliasTrait.php diff --git a/libs/di/src/ObjectItem.php b/src/di/src/ObjectItem.php similarity index 100% rename from libs/di/src/ObjectItem.php rename to src/di/src/ObjectItem.php diff --git a/libs/di/src/ServiceProviderInterface.php b/src/di/src/ServiceProviderInterface.php similarity index 100% rename from libs/di/src/ServiceProviderInterface.php rename to src/di/src/ServiceProviderInterface.php diff --git a/libs/di/test/ContainerTest.php b/src/di/test/ContainerTest.php similarity index 100% rename from libs/di/test/ContainerTest.php rename to src/di/test/ContainerTest.php diff --git a/libs/di/test/MakeByMethod.php b/src/di/test/MakeByMethod.php similarity index 100% rename from libs/di/test/MakeByMethod.php rename to src/di/test/MakeByMethod.php diff --git a/libs/di/test/MakeByStatic.php b/src/di/test/MakeByStatic.php similarity index 100% rename from libs/di/test/MakeByStatic.php rename to src/di/test/MakeByStatic.php diff --git a/libs/di/test/SomeClass.php b/src/di/test/SomeClass.php similarity index 100% rename from libs/di/test/SomeClass.php rename to src/di/test/SomeClass.php diff --git a/libs/di/test/boot.php b/src/di/test/boot.php similarity index 100% rename from libs/di/test/boot.php rename to src/di/test/boot.php diff --git a/libs/di/.gitignore b/src/file-parse/.gitignore similarity index 100% rename from libs/di/.gitignore rename to src/file-parse/.gitignore diff --git a/libs/data-parser/LICENSE b/src/file-parse/LICENSE similarity index 100% rename from libs/data-parser/LICENSE rename to src/file-parse/LICENSE diff --git a/libs/file-parse/README.md b/src/file-parse/README.md similarity index 100% rename from libs/file-parse/README.md rename to src/file-parse/README.md diff --git a/libs/file-parse/composer.json b/src/file-parse/composer.json similarity index 100% rename from libs/file-parse/composer.json rename to src/file-parse/composer.json diff --git a/libs/file-parse/phpunit.xml.dist b/src/file-parse/phpunit.xml.dist similarity index 100% rename from libs/file-parse/phpunit.xml.dist rename to src/file-parse/phpunit.xml.dist diff --git a/libs/file-parse/src/BaseParser.php b/src/file-parse/src/BaseParser.php similarity index 100% rename from libs/file-parse/src/BaseParser.php rename to src/file-parse/src/BaseParser.php diff --git a/libs/file-parse/src/IniParser.php b/src/file-parse/src/IniParser.php similarity index 100% rename from libs/file-parse/src/IniParser.php rename to src/file-parse/src/IniParser.php diff --git a/libs/file-parse/src/JsonParser.php b/src/file-parse/src/JsonParser.php similarity index 100% rename from libs/file-parse/src/JsonParser.php rename to src/file-parse/src/JsonParser.php diff --git a/libs/file-parse/src/YmlParser.php b/src/file-parse/src/YmlParser.php similarity index 100% rename from libs/file-parse/src/YmlParser.php rename to src/file-parse/src/YmlParser.php diff --git a/libs/file-parse/test/IniParserTest.php b/src/file-parse/test/IniParserTest.php similarity index 100% rename from libs/file-parse/test/IniParserTest.php rename to src/file-parse/test/IniParserTest.php diff --git a/libs/file-parse/test/boot.php b/src/file-parse/test/boot.php similarity index 100% rename from libs/file-parse/test/boot.php rename to src/file-parse/test/boot.php diff --git a/libs/file-parse/test/data/include.ini b/src/file-parse/test/data/include.ini similarity index 100% rename from libs/file-parse/test/data/include.ini rename to src/file-parse/test/data/include.ini diff --git a/libs/file-parse/test/data/test.ini b/src/file-parse/test/data/test.ini similarity index 100% rename from libs/file-parse/test/data/test.ini rename to src/file-parse/test/data/test.ini diff --git a/libs/file-parse/.gitignore b/src/file-utils/.gitignore similarity index 100% rename from libs/file-parse/.gitignore rename to src/file-utils/.gitignore diff --git a/libs/di/LICENSE b/src/file-utils/LICENSE similarity index 100% rename from libs/di/LICENSE rename to src/file-utils/LICENSE diff --git a/libs/file-utils/README.md b/src/file-utils/README.md similarity index 100% rename from libs/file-utils/README.md rename to src/file-utils/README.md diff --git a/libs/file-utils/composer.json b/src/file-utils/composer.json similarity index 100% rename from libs/file-utils/composer.json rename to src/file-utils/composer.json diff --git a/libs/file-utils/example/dir-watcher.php b/src/file-utils/example/dir-watcher.php similarity index 100% rename from libs/file-utils/example/dir-watcher.php rename to src/file-utils/example/dir-watcher.php diff --git a/libs/file-utils/example/file-finder.php b/src/file-utils/example/file-finder.php similarity index 100% rename from libs/file-utils/example/file-finder.php rename to src/file-utils/example/file-finder.php diff --git a/libs/file-utils/phpunit.xml.dist b/src/file-utils/phpunit.xml.dist similarity index 100% rename from libs/file-utils/phpunit.xml.dist rename to src/file-utils/phpunit.xml.dist diff --git a/libs/file-utils/src/Directory.php b/src/file-utils/src/Directory.php similarity index 100% rename from libs/file-utils/src/Directory.php rename to src/file-utils/src/Directory.php diff --git a/libs/file-utils/src/Exception/FileNotFoundException.php b/src/file-utils/src/Exception/FileNotFoundException.php similarity index 100% rename from libs/file-utils/src/Exception/FileNotFoundException.php rename to src/file-utils/src/Exception/FileNotFoundException.php diff --git a/libs/file-utils/src/Exception/FileReadException.php b/src/file-utils/src/Exception/FileReadException.php similarity index 100% rename from libs/file-utils/src/Exception/FileReadException.php rename to src/file-utils/src/Exception/FileReadException.php diff --git a/libs/file-utils/src/Exception/FileSystemException.php b/src/file-utils/src/Exception/FileSystemException.php similarity index 100% rename from libs/file-utils/src/Exception/FileSystemException.php rename to src/file-utils/src/Exception/FileSystemException.php diff --git a/libs/file-utils/src/Exception/IOException.php b/src/file-utils/src/Exception/IOException.php similarity index 100% rename from libs/file-utils/src/Exception/IOException.php rename to src/file-utils/src/Exception/IOException.php diff --git a/libs/file-utils/src/File.php b/src/file-utils/src/File.php similarity index 100% rename from libs/file-utils/src/File.php rename to src/file-utils/src/File.php diff --git a/libs/file-utils/src/FileFinder.php b/src/file-utils/src/FileFinder.php similarity index 100% rename from libs/file-utils/src/FileFinder.php rename to src/file-utils/src/FileFinder.php diff --git a/libs/file-utils/src/FileSystem.php b/src/file-utils/src/FileSystem.php similarity index 100% rename from libs/file-utils/src/FileSystem.php rename to src/file-utils/src/FileSystem.php diff --git a/libs/file-utils/src/ModifyWatcher.php b/src/file-utils/src/ModifyWatcher.php similarity index 100% rename from libs/file-utils/src/ModifyWatcher.php rename to src/file-utils/src/ModifyWatcher.php diff --git a/libs/file-utils/src/ReadTrait.php b/src/file-utils/src/ReadTrait.php similarity index 100% rename from libs/file-utils/src/ReadTrait.php rename to src/file-utils/src/ReadTrait.php diff --git a/libs/file-utils/test/boot.php b/src/file-utils/test/boot.php similarity index 100% rename from libs/file-utils/test/boot.php rename to src/file-utils/test/boot.php diff --git a/libs/file-utils/.gitignore b/src/helper-utils/.gitignore similarity index 100% rename from libs/file-utils/.gitignore rename to src/helper-utils/.gitignore diff --git a/libs/file-parse/LICENSE b/src/helper-utils/LICENSE similarity index 100% rename from libs/file-parse/LICENSE rename to src/helper-utils/LICENSE diff --git a/libs/helper-utils/README.md b/src/helper-utils/README.md similarity index 100% rename from libs/helper-utils/README.md rename to src/helper-utils/README.md diff --git a/libs/helper-utils/composer.json b/src/helper-utils/composer.json similarity index 100% rename from libs/helper-utils/composer.json rename to src/helper-utils/composer.json diff --git a/libs/helper-utils/phpunit.xml.dist b/src/helper-utils/phpunit.xml.dist similarity index 100% rename from libs/helper-utils/phpunit.xml.dist rename to src/helper-utils/phpunit.xml.dist diff --git a/libs/helper-utils/src/Helper/AssertHelper.php b/src/helper-utils/src/Helper/AssertHelper.php similarity index 100% rename from libs/helper-utils/src/Helper/AssertHelper.php rename to src/helper-utils/src/Helper/AssertHelper.php diff --git a/libs/helper-utils/src/Helper/DataHelper.php b/src/helper-utils/src/Helper/DataHelper.php similarity index 100% rename from libs/helper-utils/src/Helper/DataHelper.php rename to src/helper-utils/src/Helper/DataHelper.php diff --git a/libs/helper-utils/src/Helper/DateHelper.php b/src/helper-utils/src/Helper/DateHelper.php similarity index 100% rename from libs/helper-utils/src/Helper/DateHelper.php rename to src/helper-utils/src/Helper/DateHelper.php diff --git a/libs/helper-utils/src/Helper/DsnHelper.php b/src/helper-utils/src/Helper/DsnHelper.php similarity index 100% rename from libs/helper-utils/src/Helper/DsnHelper.php rename to src/helper-utils/src/Helper/DsnHelper.php diff --git a/libs/helper-utils/src/Helper/FormatHelper.php b/src/helper-utils/src/Helper/FormatHelper.php similarity index 100% rename from libs/helper-utils/src/Helper/FormatHelper.php rename to src/helper-utils/src/Helper/FormatHelper.php diff --git a/libs/helper-utils/src/Helper/Http.php b/src/helper-utils/src/Helper/Http.php similarity index 100% rename from libs/helper-utils/src/Helper/Http.php rename to src/helper-utils/src/Helper/Http.php diff --git a/libs/helper-utils/src/Helper/IntHelper.php b/src/helper-utils/src/Helper/IntHelper.php similarity index 100% rename from libs/helper-utils/src/Helper/IntHelper.php rename to src/helper-utils/src/Helper/IntHelper.php diff --git a/libs/helper-utils/src/Helper/SslHelper.php b/src/helper-utils/src/Helper/SslHelper.php similarity index 100% rename from libs/helper-utils/src/Helper/SslHelper.php rename to src/helper-utils/src/Helper/SslHelper.php diff --git a/libs/helper-utils/src/Helper/UtilHelper.php b/src/helper-utils/src/Helper/UtilHelper.php similarity index 100% rename from libs/helper-utils/src/Helper/UtilHelper.php rename to src/helper-utils/src/Helper/UtilHelper.php diff --git a/libs/helper-utils/src/Traits/AopProxyAwareTrait.php b/src/helper-utils/src/Traits/AopProxyAwareTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/AopProxyAwareTrait.php rename to src/helper-utils/src/Traits/AopProxyAwareTrait.php diff --git a/libs/helper-utils/src/Traits/Config/ConfigTrait.php b/src/helper-utils/src/Traits/Config/ConfigTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/Config/ConfigTrait.php rename to src/helper-utils/src/Traits/Config/ConfigTrait.php diff --git a/libs/helper-utils/src/Traits/Config/LiteConfigTrait.php b/src/helper-utils/src/Traits/Config/LiteConfigTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/Config/LiteConfigTrait.php rename to src/helper-utils/src/Traits/Config/LiteConfigTrait.php diff --git a/libs/helper-utils/src/Traits/Config/LiteOptionsTrait.php b/src/helper-utils/src/Traits/Config/LiteOptionsTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/Config/LiteOptionsTrait.php rename to src/helper-utils/src/Traits/Config/LiteOptionsTrait.php diff --git a/libs/helper-utils/src/Traits/Config/OptionsTrait.php b/src/helper-utils/src/Traits/Config/OptionsTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/Config/OptionsTrait.php rename to src/helper-utils/src/Traits/Config/OptionsTrait.php diff --git a/libs/helper-utils/src/Traits/Event/EventTrait.php b/src/helper-utils/src/Traits/Event/EventTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/Event/EventTrait.php rename to src/helper-utils/src/Traits/Event/EventTrait.php diff --git a/libs/helper-utils/src/Traits/Event/FixedEventStaticTrait.php b/src/helper-utils/src/Traits/Event/FixedEventStaticTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/Event/FixedEventStaticTrait.php rename to src/helper-utils/src/Traits/Event/FixedEventStaticTrait.php diff --git a/libs/helper-utils/src/Traits/Event/FixedEventTrait.php b/src/helper-utils/src/Traits/Event/FixedEventTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/Event/FixedEventTrait.php rename to src/helper-utils/src/Traits/Event/FixedEventTrait.php diff --git a/libs/helper-utils/src/Traits/Event/LiteEventStaticTrait.php b/src/helper-utils/src/Traits/Event/LiteEventStaticTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/Event/LiteEventStaticTrait.php rename to src/helper-utils/src/Traits/Event/LiteEventStaticTrait.php diff --git a/libs/helper-utils/src/Traits/Event/LiteEventTrait.php b/src/helper-utils/src/Traits/Event/LiteEventTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/Event/LiteEventTrait.php rename to src/helper-utils/src/Traits/Event/LiteEventTrait.php diff --git a/libs/helper-utils/src/Traits/LiteContainerStaticTrait.php b/src/helper-utils/src/Traits/LiteContainerStaticTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/LiteContainerStaticTrait.php rename to src/helper-utils/src/Traits/LiteContainerStaticTrait.php diff --git a/libs/helper-utils/src/Traits/LiteContainerTrait.php b/src/helper-utils/src/Traits/LiteContainerTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/LiteContainerTrait.php rename to src/helper-utils/src/Traits/LiteContainerTrait.php diff --git a/libs/helper-utils/src/Traits/LogProfileTrait.php b/src/helper-utils/src/Traits/LogProfileTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/LogProfileTrait.php rename to src/helper-utils/src/Traits/LogProfileTrait.php diff --git a/libs/helper-utils/src/Traits/LogShortTrait.php b/src/helper-utils/src/Traits/LogShortTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/LogShortTrait.php rename to src/helper-utils/src/Traits/LogShortTrait.php diff --git a/libs/helper-utils/src/Traits/NameAliasStaticTrait.php b/src/helper-utils/src/Traits/NameAliasStaticTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/NameAliasStaticTrait.php rename to src/helper-utils/src/Traits/NameAliasStaticTrait.php diff --git a/libs/helper-utils/src/Traits/NameAliasTrait.php b/src/helper-utils/src/Traits/NameAliasTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/NameAliasTrait.php rename to src/helper-utils/src/Traits/NameAliasTrait.php diff --git a/libs/helper-utils/src/Traits/PathAliasTrait.php b/src/helper-utils/src/Traits/PathAliasTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/PathAliasTrait.php rename to src/helper-utils/src/Traits/PathAliasTrait.php diff --git a/libs/helper-utils/src/Traits/PathResolverTrait.php b/src/helper-utils/src/Traits/PathResolverTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/PathResolverTrait.php rename to src/helper-utils/src/Traits/PathResolverTrait.php diff --git a/libs/helper-utils/src/Traits/RuntimeProfileTrait.php b/src/helper-utils/src/Traits/RuntimeProfileTrait.php similarity index 100% rename from libs/helper-utils/src/Traits/RuntimeProfileTrait.php rename to src/helper-utils/src/Traits/RuntimeProfileTrait.php diff --git a/libs/helper-utils/src/Util/AopProxy.php b/src/helper-utils/src/Util/AopProxy.php similarity index 100% rename from libs/helper-utils/src/Util/AopProxy.php rename to src/helper-utils/src/Util/AopProxy.php diff --git a/libs/helper-utils/src/Util/DataProxy.php b/src/helper-utils/src/Util/DataProxy.php similarity index 100% rename from libs/helper-utils/src/Util/DataProxy.php rename to src/helper-utils/src/Util/DataProxy.php diff --git a/libs/helper-utils/src/Util/DataResult.php b/src/helper-utils/src/Util/DataResult.php similarity index 100% rename from libs/helper-utils/src/Util/DataResult.php rename to src/helper-utils/src/Util/DataResult.php diff --git a/libs/helper-utils/src/Util/DeferredCallable.php b/src/helper-utils/src/Util/DeferredCallable.php similarity index 100% rename from libs/helper-utils/src/Util/DeferredCallable.php rename to src/helper-utils/src/Util/DeferredCallable.php diff --git a/libs/helper-utils/src/Util/Pipeline.php b/src/helper-utils/src/Util/Pipeline.php similarity index 100% rename from libs/helper-utils/src/Util/Pipeline.php rename to src/helper-utils/src/Util/Pipeline.php diff --git a/libs/helper-utils/src/Util/PipelineInterface.php b/src/helper-utils/src/Util/PipelineInterface.php similarity index 100% rename from libs/helper-utils/src/Util/PipelineInterface.php rename to src/helper-utils/src/Util/PipelineInterface.php diff --git a/libs/helper-utils/test/boot.php b/src/helper-utils/test/boot.php similarity index 100% rename from libs/helper-utils/test/boot.php rename to src/helper-utils/test/boot.php diff --git a/libs/helper-utils/.gitignore b/src/php-utils/.gitignore similarity index 100% rename from libs/helper-utils/.gitignore rename to src/php-utils/.gitignore diff --git a/libs/file-utils/LICENSE b/src/php-utils/LICENSE similarity index 100% rename from libs/file-utils/LICENSE rename to src/php-utils/LICENSE diff --git a/libs/php-utils/README.md b/src/php-utils/README.md similarity index 100% rename from libs/php-utils/README.md rename to src/php-utils/README.md diff --git a/libs/php-utils/composer.json b/src/php-utils/composer.json similarity index 100% rename from libs/php-utils/composer.json rename to src/php-utils/composer.json diff --git a/libs/php-utils/example/property_exists.php b/src/php-utils/example/property_exists.php similarity index 100% rename from libs/php-utils/example/property_exists.php rename to src/php-utils/example/property_exists.php diff --git a/libs/obj-utils/phpunit.xml.dist b/src/php-utils/phpunit.xml.dist similarity index 100% rename from libs/obj-utils/phpunit.xml.dist rename to src/php-utils/phpunit.xml.dist diff --git a/libs/php-utils/src/AutoLoader.php b/src/php-utils/src/AutoLoader.php similarity index 100% rename from libs/php-utils/src/AutoLoader.php rename to src/php-utils/src/AutoLoader.php diff --git a/libs/php-utils/src/Php.php b/src/php-utils/src/Php.php similarity index 100% rename from libs/php-utils/src/Php.php rename to src/php-utils/src/Php.php diff --git a/libs/php-utils/src/PhpDoc.php b/src/php-utils/src/PhpDoc.php similarity index 100% rename from libs/php-utils/src/PhpDoc.php rename to src/php-utils/src/PhpDoc.php diff --git a/libs/php-utils/src/PhpDotEnv.php b/src/php-utils/src/PhpDotEnv.php similarity index 100% rename from libs/php-utils/src/PhpDotEnv.php rename to src/php-utils/src/PhpDotEnv.php diff --git a/libs/php-utils/src/PhpEnv.php b/src/php-utils/src/PhpEnv.php similarity index 100% rename from libs/php-utils/src/PhpEnv.php rename to src/php-utils/src/PhpEnv.php diff --git a/libs/php-utils/src/PhpError.php b/src/php-utils/src/PhpError.php similarity index 100% rename from libs/php-utils/src/PhpError.php rename to src/php-utils/src/PhpError.php diff --git a/libs/php-utils/src/PhpException.php b/src/php-utils/src/PhpException.php similarity index 100% rename from libs/php-utils/src/PhpException.php rename to src/php-utils/src/PhpException.php diff --git a/libs/php-utils/src/PhpHelper.php b/src/php-utils/src/PhpHelper.php similarity index 100% rename from libs/php-utils/src/PhpHelper.php rename to src/php-utils/src/PhpHelper.php diff --git a/libs/php-utils/src/Type.php b/src/php-utils/src/Type.php similarity index 100% rename from libs/php-utils/src/Type.php rename to src/php-utils/src/Type.php diff --git a/libs/php-utils/test/PhpDocTest.php b/src/php-utils/test/PhpDocTest.php similarity index 100% rename from libs/php-utils/test/PhpDocTest.php rename to src/php-utils/test/PhpDocTest.php diff --git a/libs/php-utils/test/boot.php b/src/php-utils/test/boot.php similarity index 100% rename from libs/php-utils/test/boot.php rename to src/php-utils/test/boot.php diff --git a/libs/obj-utils/.gitignore b/src/sys-utils/.gitignore similarity index 100% rename from libs/obj-utils/.gitignore rename to src/sys-utils/.gitignore diff --git a/libs/helper-utils/LICENSE b/src/sys-utils/LICENSE similarity index 100% rename from libs/helper-utils/LICENSE rename to src/sys-utils/LICENSE diff --git a/libs/sys-utils/README.md b/src/sys-utils/README.md similarity index 100% rename from libs/sys-utils/README.md rename to src/sys-utils/README.md diff --git a/libs/sys-utils/composer.json b/src/sys-utils/composer.json similarity index 100% rename from libs/sys-utils/composer.json rename to src/sys-utils/composer.json diff --git a/libs/php-utils/phpunit.xml.dist b/src/sys-utils/phpunit.xml.dist similarity index 100% rename from libs/php-utils/phpunit.xml.dist rename to src/sys-utils/phpunit.xml.dist diff --git a/libs/sys-utils/src/ProcessUtil.php b/src/sys-utils/src/ProcessUtil.php similarity index 100% rename from libs/sys-utils/src/ProcessUtil.php rename to src/sys-utils/src/ProcessUtil.php diff --git a/libs/sys-utils/src/Signal.php b/src/sys-utils/src/Signal.php similarity index 100% rename from libs/sys-utils/src/Signal.php rename to src/sys-utils/src/Signal.php diff --git a/libs/sys-utils/src/Sys.php b/src/sys-utils/src/Sys.php similarity index 100% rename from libs/sys-utils/src/Sys.php rename to src/sys-utils/src/Sys.php diff --git a/libs/sys-utils/src/SysEnv.php b/src/sys-utils/src/SysEnv.php similarity index 100% rename from libs/sys-utils/src/SysEnv.php rename to src/sys-utils/src/SysEnv.php diff --git a/libs/sys-utils/test/boot.php b/src/sys-utils/test/boot.php similarity index 100% rename from libs/sys-utils/test/boot.php rename to src/sys-utils/test/boot.php From d3d5e2762d411dfa58be813d8ebab7ccc6960a73 Mon Sep 17 00:00:00 2001 From: inhere Date: Wed, 10 Jun 2020 16:37:04 +0800 Subject: [PATCH 2/3] up deps --- components.inc | 16 ---------------- composer.json | 9 --------- 2 files changed, 25 deletions(-) delete mode 100644 components.inc diff --git a/components.inc b/components.inc deleted file mode 100644 index 54cb868..0000000 --- a/components.inc +++ /dev/null @@ -1,16 +0,0 @@ - Date: Thu, 11 Jun 2020 21:02:19 +0800 Subject: [PATCH 3/3] remove some packages --- src/collection/src/Collection.php | 22 +- src/collection/src/CollectionInterface.php | 2 +- src/collection/src/LiteCollection.php | 2 +- src/collection/src/SimpleCollection.php | 2 +- src/file-parse/.gitignore | 10 - src/file-parse/LICENSE | 20 - src/file-parse/README.md | 36 - src/file-parse/composer.json | 32 - src/file-parse/phpunit.xml.dist | 23 - src/file-parse/src/BaseParser.php | 98 --- src/file-parse/src/IniParser.php | 122 --- src/file-parse/src/JsonParser.php | 121 --- src/file-parse/src/YmlParser.php | 128 --- src/file-parse/test/IniParserTest.php | 37 - src/file-parse/test/boot.php | 27 - src/file-parse/test/data/include.ini | 5 - src/file-parse/test/data/test.ini | 8 - src/file-utils/.gitignore | 10 - src/file-utils/LICENSE | 20 - src/file-utils/README.md | 22 - src/file-utils/composer.json | 31 - src/file-utils/example/dir-watcher.php | 20 - src/file-utils/example/file-finder.php | 27 - src/file-utils/phpunit.xml.dist | 24 - src/file-utils/src/Directory.php | 400 ---------- .../src/Exception/FileNotFoundException.php | 21 - .../src/Exception/FileReadException.php | 19 - .../src/Exception/FileSystemException.php | 21 - src/file-utils/src/Exception/IOException.php | 19 - src/file-utils/src/File.php | 449 ----------- src/file-utils/src/FileFinder.php | 737 ------------------ src/file-utils/src/FileSystem.php | 485 ------------ src/file-utils/src/ModifyWatcher.php | 320 -------- src/file-utils/src/ReadTrait.php | 290 ------- src/file-utils/test/boot.php | 28 - src/php-utils/.gitignore | 10 - src/php-utils/LICENSE | 20 - src/php-utils/README.md | 26 - src/php-utils/composer.json | 31 - src/php-utils/example/property_exists.php | 36 - src/php-utils/phpunit.xml.dist | 24 - src/php-utils/src/AutoLoader.php | 365 --------- src/php-utils/src/Php.php | 18 - src/php-utils/src/PhpDoc.php | 133 ---- src/php-utils/src/PhpDotEnv.php | 132 ---- src/php-utils/src/PhpEnv.php | 232 ------ src/php-utils/src/PhpError.php | 105 --- src/php-utils/src/PhpException.php | 117 --- src/php-utils/src/PhpHelper.php | 208 ----- src/php-utils/src/Type.php | 66 -- src/php-utils/test/PhpDocTest.php | 50 -- src/php-utils/test/boot.php | 27 - src/sys-utils/.gitignore | 10 - src/sys-utils/LICENSE | 20 - src/sys-utils/README.md | 17 - src/sys-utils/composer.json | 31 - src/sys-utils/phpunit.xml.dist | 24 - src/sys-utils/src/ProcessUtil.php | 715 ----------------- src/sys-utils/src/Signal.php | 58 -- src/sys-utils/src/Sys.php | 368 --------- src/sys-utils/src/SysEnv.php | 145 ---- src/sys-utils/test/boot.php | 27 - 62 files changed, 13 insertions(+), 6640 deletions(-) delete mode 100644 src/file-parse/.gitignore delete mode 100644 src/file-parse/LICENSE delete mode 100644 src/file-parse/README.md delete mode 100644 src/file-parse/composer.json delete mode 100644 src/file-parse/phpunit.xml.dist delete mode 100644 src/file-parse/src/BaseParser.php delete mode 100644 src/file-parse/src/IniParser.php delete mode 100644 src/file-parse/src/JsonParser.php delete mode 100644 src/file-parse/src/YmlParser.php delete mode 100644 src/file-parse/test/IniParserTest.php delete mode 100644 src/file-parse/test/boot.php delete mode 100644 src/file-parse/test/data/include.ini delete mode 100644 src/file-parse/test/data/test.ini delete mode 100644 src/file-utils/.gitignore delete mode 100644 src/file-utils/LICENSE delete mode 100644 src/file-utils/README.md delete mode 100644 src/file-utils/composer.json delete mode 100644 src/file-utils/example/dir-watcher.php delete mode 100644 src/file-utils/example/file-finder.php delete mode 100644 src/file-utils/phpunit.xml.dist delete mode 100644 src/file-utils/src/Directory.php delete mode 100644 src/file-utils/src/Exception/FileNotFoundException.php delete mode 100644 src/file-utils/src/Exception/FileReadException.php delete mode 100644 src/file-utils/src/Exception/FileSystemException.php delete mode 100644 src/file-utils/src/Exception/IOException.php delete mode 100644 src/file-utils/src/File.php delete mode 100644 src/file-utils/src/FileFinder.php delete mode 100644 src/file-utils/src/FileSystem.php delete mode 100644 src/file-utils/src/ModifyWatcher.php delete mode 100644 src/file-utils/src/ReadTrait.php delete mode 100644 src/file-utils/test/boot.php delete mode 100644 src/php-utils/.gitignore delete mode 100644 src/php-utils/LICENSE delete mode 100644 src/php-utils/README.md delete mode 100644 src/php-utils/composer.json delete mode 100644 src/php-utils/example/property_exists.php delete mode 100644 src/php-utils/phpunit.xml.dist delete mode 100644 src/php-utils/src/AutoLoader.php delete mode 100644 src/php-utils/src/Php.php delete mode 100644 src/php-utils/src/PhpDoc.php delete mode 100644 src/php-utils/src/PhpDotEnv.php delete mode 100644 src/php-utils/src/PhpEnv.php delete mode 100644 src/php-utils/src/PhpError.php delete mode 100644 src/php-utils/src/PhpException.php delete mode 100644 src/php-utils/src/PhpHelper.php delete mode 100644 src/php-utils/src/Type.php delete mode 100644 src/php-utils/test/PhpDocTest.php delete mode 100644 src/php-utils/test/boot.php delete mode 100644 src/sys-utils/.gitignore delete mode 100644 src/sys-utils/LICENSE delete mode 100644 src/sys-utils/README.md delete mode 100644 src/sys-utils/composer.json delete mode 100644 src/sys-utils/phpunit.xml.dist delete mode 100644 src/sys-utils/src/ProcessUtil.php delete mode 100644 src/sys-utils/src/Signal.php delete mode 100644 src/sys-utils/src/Sys.php delete mode 100644 src/sys-utils/src/SysEnv.php delete mode 100644 src/sys-utils/test/boot.php diff --git a/src/collection/src/Collection.php b/src/collection/src/Collection.php index e6395d0..672afb7 100644 --- a/src/collection/src/Collection.php +++ b/src/collection/src/Collection.php @@ -12,13 +12,13 @@ use RangeException; use RecursiveArrayIterator; use stdClass; -use Toolkit\ArrUtil\Arr; -use Toolkit\File\Exception\FileNotFoundException; -use Toolkit\File\File; -use Toolkit\File\Parse\IniParser; -use Toolkit\File\Parse\JsonParser; -use Toolkit\File\Parse\YmlParser; -use Toolkit\ObjUtil\Obj; +use Toolkit\Stdlib\Arr; +use Toolkit\FsUtil\Exception\FileNotFoundException; +use Toolkit\FsUtil\File; +use Toolkit\FsUtil\Parser\IniParser; +use Toolkit\FsUtil\Parser\JsonParser; +use Toolkit\FsUtil\Parser\YmlParser; +use Toolkit\Stdlib\Obj; use Traversable; use function in_array; use function is_array; @@ -158,7 +158,7 @@ public function has(string $path): bool return $this->exists($path); } - public function reset() + public function reset(): Collection { $this->data = []; @@ -167,12 +167,10 @@ public function reset() /** * Clear all data. - * - * @return static */ - public function clear() + public function clear(): void { - return $this->reset(); + $this->reset(); } /** diff --git a/src/collection/src/CollectionInterface.php b/src/collection/src/CollectionInterface.php index ffb86a9..7a5396b 100644 --- a/src/collection/src/CollectionInterface.php +++ b/src/collection/src/CollectionInterface.php @@ -44,5 +44,5 @@ public function remove($key); /** * clear all data */ - public function clear(); + public function clear(): void; } diff --git a/src/collection/src/LiteCollection.php b/src/collection/src/LiteCollection.php index c6147fb..90a0c42 100644 --- a/src/collection/src/LiteCollection.php +++ b/src/collection/src/LiteCollection.php @@ -177,7 +177,7 @@ public function remove($key) /** * clear all data */ - public function clear() + public function clear(): void { foreach ($this as $key) { unset($this[$key]); diff --git a/src/collection/src/SimpleCollection.php b/src/collection/src/SimpleCollection.php index 3562b39..8014a02 100644 --- a/src/collection/src/SimpleCollection.php +++ b/src/collection/src/SimpleCollection.php @@ -244,7 +244,7 @@ public function remove($key) /** * Remove all items from collection */ - public function clear() + public function clear(): void { $this->data = []; } diff --git a/src/file-parse/.gitignore b/src/file-parse/.gitignore deleted file mode 100644 index 86318a0..0000000 --- a/src/file-parse/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -.idea/ -.phpintel/ -!README.md -!.gitkeep -composer.lock -*.swp -*.log -*.pid -*.patch -.DS_Store diff --git a/src/file-parse/LICENSE b/src/file-parse/LICENSE deleted file mode 100644 index d839cdc..0000000 --- a/src/file-parse/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 inhere - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/file-parse/README.md b/src/file-parse/README.md deleted file mode 100644 index 1f24de0..0000000 --- a/src/file-parse/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# file parse - -[![License](https://img.shields.io/packagist/l/toolkit/file-parse.svg?style=flat-square)](LICENSE) -[![Php Version](https://img.shields.io/badge/php-%3E=7.1.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/toolkit/file-parse) -[![Latest Stable Version](http://img.shields.io/packagist/v/toolkit/file-parse.svg)](https://packagist.org/packages/toolkit/file-parse) - -Some useful file parse utils for the php. - -`ini`, `json`, `yml` 格式的文件解析 - -- json 文件支持去除注释,即是有注释不会导致解析失败 -- 支持特殊关键字 - - `extend` 继承另一个文件的内容 - - `import` 导入另一个文件的内容 - - `reference` 参考另一个key的值 **todo** - -例如在 yml 文件(其他格式的文件类似)可以这样: - -```text -// will parse special keywords -extend: ../parent.yml -debug: true -db: import#../db.yml -cache: - debug: reference#debug -``` - -## Install - -```bash -composer require toolkit/file-parse -``` - -## License - -MIT diff --git a/src/file-parse/composer.json b/src/file-parse/composer.json deleted file mode 100644 index 467baa8..0000000 --- a/src/file-parse/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "toolkit/file-parse", - "type": "library", - "description": "some file parse tool library of the php", - "keywords": [ - "library", - "tool", - "php" - ], - "homepage": "https://github.com/php-toolkit/file-parse", - "license": "MIT", - "authors": [ - { - "name": "inhere", - "email": "in.798@qq.com", - "homepage": "http://www.yzone.net/" - } - ], - "require": { - "php": ">7.1.0", - "toolkit/str-utils": "~1.0" - }, - "autoload": { - "psr-4": { - "Toolkit\\File\\Parse\\": "src/" - } - }, - "suggest": { - "inhere/php-validate": "Very lightweight data validate tool", - "inhere/console": "a lightweight php console application library." - } -} diff --git a/src/file-parse/phpunit.xml.dist b/src/file-parse/phpunit.xml.dist deleted file mode 100644 index 30356d2..0000000 --- a/src/file-parse/phpunit.xml.dist +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - test - - - - - - src - - - diff --git a/src/file-parse/src/BaseParser.php b/src/file-parse/src/BaseParser.php deleted file mode 100644 index d8d1b90..0000000 --- a/src/file-parse/src/BaseParser.php +++ /dev/null @@ -1,98 +0,0 @@ - $item) { - if (!is_string($item)) { - continue; - } - - if (0 === strpos($item, self::IMPORT_KEY . '#')) { - $importFile = trim(substr($item, 7)); - - // if needed custom handle $importFile path. e.g: Maybe it uses custom alias path - if ($pathHandler && is_callable($pathHandler)) { - $importFile = $pathHandler($importFile); - } - - // if $importFile is not exists AND $importFile is not a absolute path AND have $parentFile - if ($fileDir && !file_exists($importFile) && $importFile[0] !== '/') { - $importFile = $fileDir . '/' . trim($importFile, './'); - } - - // $importFile is file - if (is_file($importFile)) { - $data = file_get_contents($importFile); - $array[$key] = parse_ini_string(trim($data), true); - } else { - throw new UnexpectedValueException("needed imported file [$importFile] don't exists!"); - } - } - } - - } - - return $array; - } - -} diff --git a/src/file-parse/src/JsonParser.php b/src/file-parse/src/JsonParser.php deleted file mode 100644 index 20762ba..0000000 --- a/src/file-parse/src/JsonParser.php +++ /dev/null @@ -1,121 +0,0 @@ - $item) { - if (!is_string($item)) { - continue; - } - - if (0 === strpos($item, self::IMPORT_KEY . '#')) { - $importFile = trim(substr($item, 6)); - - // if needed custom handle $importFile path. e.g: Maybe it uses custom alias path - if ($pathHandler && is_callable($pathHandler)) { - $importFile = $pathHandler($importFile); - } - - // if $importFile is not exists AND $importFile is not a absolute path AND have $parentFile - if ($fileDir && !file_exists($importFile) && $importFile[0] !== '/') { - $importFile = $fileDir . '/' . trim($importFile, './'); - } - - // $importFile is file - if (is_file($importFile)) { - $data = file_get_contents($importFile); - $array[$key] = JsonHelper::parse($data); - } else { - throw new UnexpectedValueException("needed imported file [$importFile] don't exists!"); - } - } - } - - } - - return $array; - } -} diff --git a/src/file-parse/src/YmlParser.php b/src/file-parse/src/YmlParser.php deleted file mode 100644 index cadd53c..0000000 --- a/src/file-parse/src/YmlParser.php +++ /dev/null @@ -1,128 +0,0 @@ -parse(trim($string)); - - /* - * Parse special keywords - * - * extend: ../parent.yml - * db: import#../db.yml - * cache: - * debug: reference#debug - */ - if ($enhancement === true) { - if (isset($array[self::EXTEND_KEY]) && ($extendFile = $array[self::EXTEND_KEY])) { - // if needed custom handle $importFile path. e.g: Maybe it uses custom alias path - if ($pathHandler && is_callable($pathHandler)) { - $extendFile = $pathHandler($extendFile); - } - - // if $importFile is not exists AND $importFile is not a absolute path AND have $parentFile - if ($fileDir && !file_exists($extendFile) && $extendFile[0] !== '/') { - $extendFile = $fileDir . '/' . trim($extendFile, './'); - } - - // $importFile is file - if (is_file($extendFile)) { - $data = file_get_contents($extendFile); - $array = array_merge($parser->parse(trim($data)), $array); - } else { - throw new UnexpectedValueException("needed extended file $extendFile don't exists!"); - } - } - - foreach ($array as $key => $item) { - if (!is_string($item)) { - continue; - } - - if (0 === strpos($item, self::IMPORT_KEY . '#')) { - $importFile = trim(substr($item, 6)); - - // if needed custom handle $importFile path. e.g: Maybe it uses custom alias path - if ($pathHandler && is_callable($pathHandler)) { - $importFile = $pathHandler($importFile); - } - - // if $importFile is not exists AND $importFile is not a absolute path AND have $parentFile - if ($fileDir && !file_exists($importFile) && $importFile[0] !== '/') { - $importFile = $fileDir . '/' . trim($importFile, './'); - } - - // $importFile is file - if (is_file($importFile)) { - $data = file_get_contents($importFile); - $array[$key] = $parser->parse(trim($data)); - } else { - throw new UnexpectedValueException("needed imported file $importFile don't exists!"); - } - } - } - - } - - return $array; - } - -} diff --git a/src/file-parse/test/IniParserTest.php b/src/file-parse/test/IniParserTest.php deleted file mode 100644 index b869679..0000000 --- a/src/file-parse/test/IniParserTest.php +++ /dev/null @@ -1,37 +0,0 @@ -assertArrayHasKey('name', $ret); - $this->assertSame('import#include.ini', $ret['include']); - - - $ret = IniParser::parseFile(__DIR__ . '/data/test.ini', true); - - $this->assertArrayHasKey('name', $ret); - $this->assertArrayHasKey('he', $ret['include']); - } -} diff --git a/src/file-parse/test/boot.php b/src/file-parse/test/boot.php deleted file mode 100644 index 117c3f4..0000000 --- a/src/file-parse/test/boot.php +++ /dev/null @@ -1,27 +0,0 @@ -7.1.0" - }, - "autoload": { - "psr-4": { - "Toolkit\\File\\": "src/" - } - }, - "suggest": { - "inhere/php-validate": "Very lightweight data validate tool", - "inhere/console": "a lightweight php console application library." - } -} diff --git a/src/file-utils/example/dir-watcher.php b/src/file-utils/example/dir-watcher.php deleted file mode 100644 index a680e7f..0000000 --- a/src/file-utils/example/dir-watcher.php +++ /dev/null @@ -1,20 +0,0 @@ -setIdFile(__DIR__ . '/tmp/dir.id') - ->watch(dirname(__DIR__))->isChanged(); - -// d41d8cd98f00b204e9800998ecf8427e -// current file: ae4464472e898ba0bba8dc7302b157c0 -var_dump($ret, $mw->getDirMd5(), $mw->getFileCounter()); diff --git a/src/file-utils/example/file-finder.php b/src/file-utils/example/file-finder.php deleted file mode 100644 index 3eead56..0000000 --- a/src/file-utils/example/file-finder.php +++ /dev/null @@ -1,27 +0,0 @@ -files()->name('*.php')// ->ignoreVCS(false) - // ->ignoreDotFiles(false) - // ->exclude('tmp') - ->notPath('tmp')->inDir(dirname(__DIR__)); - -foreach ($finder as $file) { - // var_dump($file);die; - echo "+ {$file->getPathname()}\n"; -} - -// print_r($finder); -// var_dump($finder->count()); diff --git a/src/file-utils/phpunit.xml.dist b/src/file-utils/phpunit.xml.dist deleted file mode 100644 index 3a3155a..0000000 --- a/src/file-utils/phpunit.xml.dist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - test - - - - - - src - - - diff --git a/src/file-utils/src/Directory.php b/src/file-utils/src/Directory.php deleted file mode 100644 index 85f81f7..0000000 --- a/src/file-utils/src/Directory.php +++ /dev/null @@ -1,400 +0,0 @@ -getFilename()[0] === '.') { - * return false; - * } - * if ($current->isDir()) { - * // Only recurse into intended subdirectories. - * return $current->getFilename() !== '.git'; - * } - * // Only consume files of interest. - * return strpos($current->getFilename(), '.php') !== false; - * }; - * - * // $info is instance of \SplFileInfo - * foreach(Directory::getRecursiveIterator($srcDir, $filter) as $info) { - * // $info->getFilename(); ... - * } - * ``` - * - * @param string $srcDir - * @param callable $filter - * - * @return RecursiveIteratorIterator - * @throws LogicException - */ - public static function getRecursiveIterator($srcDir, callable $filter): RecursiveIteratorIterator - { - return self::getIterator($srcDir, $filter); - } - - /** - * 判断文件夹是否为空 - * - * @param $dir - * - * @return bool - * @throws FileSystemException - */ - public static function isEmpty($dir): bool - { - $handler = opendir($dir); - - if (false === $handler) { - throw new FileSystemException("Open the dir failure! DIR: $dir"); - } - - while (($file = readdir($handler)) !== false) { - - if ($file !== '.' && $file !== '..') { - closedir($handler); - - return false; - } - } - - closedir($handler); - - return true; - } - - /** - * 查看一个目录中的所有文件和子目录 - * - * @param $path - * - * @return array - * @throws FileNotFoundException - */ - public static function ls($path): array - { - $list = []; - - try { - /*** class create new DirectoryIterator Object ***/ - foreach (new DirectoryIterator($path) as $item) { - $list[] = $item; - } - /*** if an exception is thrown, catch it here ***/ - } catch (Exception $e) { - throw new FileNotFoundException($path . ' 没有任何内容'); - } - - return $list; - } - - /** - * 只获得目录结构 - * - * @param $path - * @param int $pid - * @param int $son - * @param array $list - * - * @return array - * @throws FileNotFoundException - */ - public static function getList($path, $pid = 0, $son = 0, array $list = []): array - { - $path = self::pathFormat($path); - - if (!is_dir($path)) { - throw new FileNotFoundException("directory not exists! DIR: $path"); - } - - static $id = 0; - - foreach (glob($path . '*') as $v) { - if (is_dir($v)) { - $id++; - - $list[$id]['id'] = $id; - $list[$id]['pid'] = $pid; - $list[$id]['name'] = basename($v); - $list[$id]['path'] = realpath($v); - - //是否遍历子目录 - if ($son) { - $list = self::getList($v, $id, $son, $list); - } - } - } - - return $list; - } - - /** - * @param $path - * @param bool $loop - * @param null $parent - * @param array $list - * - * @return array - * @throws FileNotFoundException - */ - public static function getDirs($path, $loop = false, $parent = null, array $list = []): array - { - $path = self::pathFormat($path); - - if (!is_dir($path)) { - throw new FileNotFoundException("directory not exists! DIR: $path"); - } - - $len = strlen($path); - - foreach (glob($path . '*') as $v) { - if (is_dir($v)) { - $relatePath = substr($v, $len); - $list[] = $parent . $relatePath; - - //是否遍历子目录 - if ($loop) { - $list = self::getDirs($v, $loop, $relatePath . '/', $list); - } - } - } - - return $list; - } - - /** - * 获得目录下的文件,可选择类型、是否遍历子文件夹 - * - * @param string $dir string 目标目录 - * @param string|array $ext array('css','html','php') css|html|php - * @param bool $recursive int|bool 是否包含子目录 - * - * @return array - * @throws FileNotFoundException - */ - public static function simpleInfo(string $dir, $ext = null, $recursive = false): array - { - $list = []; - $dir = self::pathFormat($dir); - $ext = is_array($ext) ? implode('|', $ext) : trim($ext); - - if (!is_dir($dir)) { - throw new FileNotFoundException("directory not exists! DIR: $dir"); - } - - // glob()寻找与模式匹配的文件路径 $file is pull path - foreach (glob($dir . '*') as $file) { - - // 匹配文件 如果没有传入$ext 则全部遍历,传入了则按传入的类型来查找 - if (is_file($file) && (!$ext || preg_match("/\.($ext)$/i", $file))) { - //basename — 返回路径中的 文件名部分 - $list[] = basename($file); - - // is directory - } else { - $list[] = '/' . basename($file); - - if ($recursive) { - $list = array_merge($list, self::simpleInfo($file, $ext, $recursive)); - } - } - } - - return $list; - } - - /** - * 获得目录下的文件,可选择类型、是否遍历子文件夹 - * - * @param string $path string 目标目录 - * @param array|string $ext array('css','html','php') css|html|php - * @param bool $recursive 是否包含子目录 - * @param null|string $parent - * @param array $list - * - * @return array - * @throws FileNotFoundException - */ - public static function getFiles( - string $path, - $ext = null, - $recursive = false, - $parent = null, - array $list = [] - ): array { - $path = self::pathFormat($path); - - if (!is_dir($path)) { - throw new FileNotFoundException("directory not exists! DIR: $path"); - } - - $len = strlen($path); - $ext = is_array($ext) ? implode('|', $ext) : trim($ext); - - foreach (glob($path . '*') as $v) { - $relatePath = substr($v, $len); - - // 匹配文件 如果没有传入$ext 则全部遍历,传入了则按传入的类型来查找 - if (is_file($v) && (!$ext || preg_match("/\.($ext)$/i", $v))) { - $list[] = $parent . $relatePath; - - } elseif ($recursive) { - $list = self::getFiles($v, $ext, $recursive, $relatePath . '/', $list); - } - } - - return $list; - } - - /** - * 获得目录下的文件以及详细信息,可选择类型、是否遍历子文件夹 - * - * @param $path string 目标目录 - * @param array|string $ext array('css','html','php') css|html|php - * @param $recursive int|bool 是否包含子目录 - * @param array $list - * - * @return array - * @throws InvalidArgumentException - * @throws FileNotFoundException - */ - public static function getFilesInfo($path, $ext = null, $recursive = 0, &$list = []): array - { - $path = self::pathFormat($path); - - if (!is_dir($path)) { - throw new FileNotFoundException("directory not exists! DIR: $path"); - } - - $ext = is_array($ext) ? implode('|', $ext) : trim($ext); - - static $id = 0; - - //glob()寻找与模式匹配的文件路径 - foreach (glob($path . '*') as $file) { - $id++; - - // 匹配文件 如果没有传入$ext 则全部遍历,传入了则按传入的类型来查找 - if (is_file($file) && (!$ext || preg_match("/\.($ext)$/i", $file))) { - $list[$id] = File::info($file); - - //是否遍历子目录 - } elseif ($recursive) { - $list = self::getFilesInfo($file, $ext, $recursive, $list); - } - } - - return $list; - } - - /** - * 支持层级目录的创建 - * - * @param $path - * @param int|string $mode - * @param bool $recursive - * - * @return bool - */ - public static function create($path, $mode = 0775, $recursive = true): bool - { - return (is_dir($path) || !(!@mkdir($path, $mode, $recursive) && !is_dir($path))) && is_writable($path); - } - - /** - * 复制目录内容 - * - * @param $oldDir - * @param $newDir - * - * @return bool - * @throws FileNotFoundException - */ - public static function copy($oldDir, $newDir): bool - { - $oldDir = self::pathFormat($oldDir); - $newDir = self::pathFormat($newDir); - - if (!is_dir($oldDir)) { - throw new FileNotFoundException('复制失败:' . $oldDir . ' 不存在!'); - } - - $newDir = self::create($newDir); - - foreach (glob($oldDir . '*') as $v) { - $newFile = $newDir . basename($v);//文件 - - //文件存在,跳过复制它 - if (file_exists($newFile)) { - continue; - } - - if (is_dir($v)) { - self::copy($v, $newFile); - } else { - copy($v, $newFile);//是文件就复制过来 - @chmod($newFile, 0664);// 权限 0777 - } - } - - return true; - } - - /** - * 删除目录及里面的文件 - * - * @param $path - * @param boolean $delSelf 默认最后删掉自己 - * - * @return bool - */ - public static function delete($path, $delSelf = true): bool - { - $dirPath = self::pathFormat($path); - - if (is_file($dirPath)) { - return unlink($dirPath); - } - - foreach (glob($dirPath . '*') as $v) { - is_dir($v) ? self::delete($v) : unlink($v); - } - - $delSelf && rmdir($dirPath);//默认最后删掉自己 - - return true; - } -} diff --git a/src/file-utils/src/Exception/FileNotFoundException.php b/src/file-utils/src/Exception/FileNotFoundException.php deleted file mode 100644 index 473ba29..0000000 --- a/src/file-utils/src/Exception/FileNotFoundException.php +++ /dev/null @@ -1,21 +0,0 @@ - basename($filename), //文件名 - 'type' => filetype($filename), //类型 - 'size' => (filesize($filename) / 1000) . ' Kb', //大小 - 'is_write' => is_writable($filename) ? 'true' : 'false', //可写 - 'is_read' => is_readable($filename) ? 'true' : 'false',//可读 - 'update_time' => filectime($filename), //修改时间 - 'last_visit_time' => fileatime($filename), //文件的上次访问时间 - ]; - } - - /** - * @param $filename - * - * @return array - */ - public static function getStat($filename): array - { - return stat($filename); - } - - /** - * save description - * - * @param mixed $data string array(仅一维数组) 或者是 stream 资源 - * @param string $filename [description], LOCK_EX - * - * @return bool - */ - public static function save(string $filename, string $data): bool - { - return file_put_contents($filename, $data) !== false; - } - - /** - * @param $content - * @param $path - * - * @throws IOException - */ - public static function write($content, $path): void - { - $handler = static::openHandler($path); - - static::writeToFile($handler, $content); - - @fclose($handler); - } - - /** - * @param string $path - * - * @return resource - * @throws IOException - */ - public static function openHandler(string $path) - { - if (($handler = @fopen($path, 'wb')) === false) { - throw new IOException('The file "' . $path . '" could not be opened for writing. Check if PHP has enough permissions.'); - } - - return $handler; - } - - /** - * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions. - * - * @param resource $handler The resource to write to. - * @param string $content The content to write. - * @param string $path The path to the file (for exception printing only). - * - * @throws IOException - */ - public static function writeToFile($handler, string $content, string $path = ''): void - { - if (($result = @fwrite($handler, $content)) === false || ($result < strlen($content))) { - throw new IOException('The file "' . $path . '" could not be written to. Check your disk space and file permissions.'); - } - } - - /** - * ********************** 创建多级目录和多个文件 ********************** - * 结合上两个函数 - * - * @param $fileData - 数组:要创建的多个文件名组成,含文件的完整路径 - * @param $append - 是否以追加的方式写入数据 默认false - * @param $mode =0777 - 权限,默认0775 - * eg: $fileData = array( - * 'file_name' => 'content', - * 'case.html' => 'content' , - * ); - **/ - public static function createAndWrite(array $fileData = [], $append = false, $mode = 0664): void - { - foreach ($fileData as $file => $content) { - //检查目录是否存在,不存在就先创建(多级)目录 - Directory::create(dirname($file), $mode); - - //$fileName = basename($file); //文件名 - - //检查文件是否存在 - if (!is_file($file)) { - file_put_contents($file, $content, LOCK_EX); - @chmod($file, $mode); - } elseif ($append) { - file_put_contents($file, $content, FILE_APPEND | LOCK_EX); - @chmod($file, $mode); - } - } - } - - /** - * @param string $file a file path or url path - * @param bool|false $useIncludePath - * @param null|resource $streamContext - * @param int $curlTimeout - * - * @return bool|mixed|string - * @throws FileNotFoundException - * @throws FileReadException - */ - public static function getContents( - string $file, - $useIncludePath = false, - $streamContext = null, - int $curlTimeout = 5 - ) { - $isUrl = preg_match('/^https?:\/\//', $file); - - if (null === $streamContext && $isUrl) { - $streamContext = @stream_context_create(['http' => ['timeout' => $curlTimeout]]); - } - - if ($isUrl && in_array(ini_get('allow_url_fopen'), ['On', 'on', '1'], true)) { - if (!file_exists($file)) { - throw new FileNotFoundException("File [{$file}] don't exists!"); - } - - if (!is_readable($file)) { - throw new FileReadException("File [{$file}] is not readable!"); - } - - return @file_get_contents($file, $useIncludePath, $streamContext); - } - - // fetch remote content by url - if (function_exists('curl_init')) { - $curl = curl_init(); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_URL, $file); - curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5); - curl_setopt($curl, CURLOPT_TIMEOUT, $curlTimeout); - // curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - - if (null !== $streamContext) { - $opts = stream_context_get_options($streamContext); - - if (isset($opts['http']['method']) && strtolower($opts['http']['method']) === 'post') { - curl_setopt($curl, CURLOPT_POST, true); - - if (isset($opts['http']['content'])) { - parse_str($opts['http']['content'], $post_data); - curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data); - } - } - } - - $content = curl_exec($curl); - curl_close($curl); - - return $content; - } - - return false; - } - - /** - * @param string $file - * @param string $target - * - * @throws FileNotFoundException - * @throws FileSystemException - * @throws IOException - */ - public static function move(string $file, string $target): void - { - Directory::mkdir(dirname($target)); - - if (static::copy($file, $target)) { - unlink($file); - } - } - - /** - * @param $filename - * - * @return bool - * @throws InvalidArgumentException - * @throws FileNotFoundException - */ - public static function delete($filename): bool - { - return self::check($filename) && unlink($filename); - } - - /** - * @param $source - * @param $destination - * @param null $streamContext - * - * @return bool|int - * @throws FileSystemException - * @throws FileNotFoundException - */ - public static function copy($source, $destination, $streamContext = null) - { - if (null === $streamContext && !preg_match('/^https?:\/\//', $source)) { - if (!is_file($source)) { - throw new FileSystemException("Source file don't exists. File: $source"); - } - - return copy($source, $destination); - } - - return @file_put_contents($destination, self::getContents($source, false, $streamContext)); - } - - /** - * @param $inFile - * @param $outFile - * - * @return mixed - * @throws InvalidArgumentException - * @throws FileNotFoundException - */ - public static function combine($inFile, $outFile) - { - self::check($inFile); - - $data = ''; - if (is_array($inFile)) { - foreach ($inFile as $value) { - if (is_file($value)) { - $data .= trim(file_get_contents($value)); - } else { - throw new FileNotFoundException('File: ' . $value . ' not exists!'); - } - } - } - - /*if (is_string($inFile) && is_file($value)) { - $data .= trim( file_get_contents($inFile) ); - } else { - Trigger::error('文件'.$value.'不存在!!'); - }*/ - - $preg_arr = [ - '/\/\*.*?\*\/\s*/is', // 去掉所有多行注释/* .... */ - '/\/\/.*?[\r\n]/is', // 去掉所有单行注释//.... - '/(?!\w)\s*?(?!\w)/is' // 去掉空白行 - ]; - - $data = preg_replace($preg_arr, '', $data); - // $outFile = $outDir . Data::getRandStr(8) . '.' . $fileType; - - $fileData = [ - $outFile => $data - ]; - - self::createAndWrite($fileData); - - return $outFile; - } - - /** - * Removes whitespace from a PHP source string while preserving line numbers. - * - * @param string $source A PHP string - * - * @return string The PHP string with the whitespace removed - */ - public static function stripPhpCode(string $source): string - { - if (!function_exists('token_get_all')) { - return $source; - } - - $output = ''; - foreach (token_get_all($source) as $token) { - if (is_string($token)) { - $output .= $token; - } elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT], true)) { - $output .= str_repeat("\n", substr_count($token[1], "\n")); - } elseif (T_WHITESPACE === $token[0]) { - // reduce wide spaces - $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]); - // normalize newlines to \n - $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); - // trim leading spaces - $whitespace = preg_replace('{\n +}', "\n", $whitespace); - $output .= $whitespace; - } else { - $output .= $token[1]; - } - } - - return $output; - } - - /** - * If you want to download files from a linux server with - * a filesize bigger than 2GB you can use the following - * - * @param string $file - * @param string $as - */ - public static function downBigFile($file, $as): void - { - header('Expires: Mon, 1 Apr 1974 05:00:00 GMT'); - header('Pragma: no-cache'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Content-Description: File Download'); - header('Content-Type: application/octet-stream'); - header('Content-Length: ' . trim(shell_exec('stat -c%s "$file"'))); - header('Content-Disposition: attachment; filename="' . $as . '"'); - header('Content-Transfer-Encoding: binary'); - //@readfile( $file ); - - flush(); - $fp = popen('tail -c ' . trim(shell_exec('stat -c%s "$file"')) . ' ' . $file . ' 2>&1', 'r'); - - while (!feof($fp)) { - // send the current file part to the browser - print fread($fp, 1024); - // flush the content to the browser - flush(); - } - - fclose($fp); - } -} diff --git a/src/file-utils/src/FileFinder.php b/src/file-utils/src/FileFinder.php deleted file mode 100644 index d6809a9..0000000 --- a/src/file-utils/src/FileFinder.php +++ /dev/null @@ -1,737 +0,0 @@ -files() - * ->name('*.php') - * ->notName('some.php') - * ->in('/path/to/project') - * ; - * - * foreach($finder as $file) { - * // something ...... - * } - * ``` - * - * @package Toolkit\File - * @ref \Symfony\Component\Finder\Finder - */ -final class FileFinder implements IteratorAggregate, Countable -{ - public const ONLY_FILE = 1; - public const ONLY_DIR = 2; - public const IGNORE_VCS_FILES = 1; - public const IGNORE_DOT_FILES = 2; - - /** @var array */ - private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; - - /** @var int */ - private $mode = 0; - - /** @var int */ - private $ignore; - - /** @var bool */ - private $ignoreVcsAdded = false; - - /** @var array */ - private $dirs = []; - - /** @var array */ - private $names = []; - - /** @var array */ - private $notNames = []; - - /** @var array */ - private $paths = []; - - /** @var array */ - private $notPaths = []; - - /** @var array */ - private $excludes = []; - - /** @var array */ - private $filters = []; - - /** @var array */ - private $iterators = []; - - /** @var bool */ - private $followLinks = false; - - /** - * @return FileFinder - */ - public static function create(): self - { - return new self(); - } - - /** - * @param array $config - * - * @return FileFinder - */ - public static function fromArray(array $config): self - { - $finder = new self(); - $allowed = [ - 'names' => 'addNames', - 'notNames' => 'addNotNames', - 'paths' => 'addNotPaths', - 'notPaths' => 'addNotPaths', - 'exclude' => 'exclude', - 'excludes' => 'exclude', - ]; - - foreach ($config as $prop => $values) { - if ($values && isset($allowed[$prop])) { - $method = $allowed[$prop]; - $finder->$method($values); - } - } - - return $finder; - } - - /** - * FileFinder constructor. - */ - public function __construct() - { - $this->ignore = self::IGNORE_VCS_FILES | self::IGNORE_DOT_FILES; - } - - /** - * @return $this - */ - public function directories(): self - { - $this->mode = self::ONLY_DIR; - - return $this; - } - - /** - * @return $this - */ - public function dirs(): self - { - $this->mode = self::ONLY_DIR; - - return $this; - } - - /** - * @return FileFinder - */ - public function files(): self - { - $this->mode = self::ONLY_FILE; - - return $this; - } - - /** - * $finder->name('*.php') - * $finder->name('test.php') - * - * @param string $pattern - * - * @return FileFinder - */ - public function name(string $pattern): self - { - $this->names[] = $pattern; - - return $this; - } - - /** - * @param string|array $patterns - * - * @return FileFinder - */ - public function addNames($patterns): self - { - if ($patterns) { - $this->names = array_merge($this->names, (array)$patterns); - } - - return $this; - } - - /** - * @param string $pattern - * - * @return FileFinder - */ - public function notName(string $pattern): self - { - $this->notNames[] = $pattern; - - return $this; - } - - /** - * @param string|array $patterns - * - * @return FileFinder - */ - public function addNotNames($patterns): self - { - if ($patterns) { - $this->notNames = array_merge($this->notNames, (array)$patterns); - } - - return $this; - } - - /** - * $finder->path('some/special/dir') - * - * @param string $pattern - * - * @return FileFinder - */ - public function path(string $pattern): self - { - $this->paths[] = $pattern; - - return $this; - } - - /** - * @param string|array $patterns - * - * @return FileFinder - */ - public function addPaths($patterns): self - { - if ($patterns) { - $this->paths = array_merge($this->paths, (array)$patterns); - } - - return $this; - } - - /** - * @param string $pattern - * - * @return FileFinder - */ - public function notPath(string $pattern): self - { - $this->notPaths[] = $pattern; - - return $this; - } - - /** - * @param string|array $patterns - * - * @return FileFinder - */ - public function addNotPaths($patterns): self - { - if ($patterns) { - $this->notPaths = array_merge($this->notPaths, (array)$patterns); - } - - return $this; - } - - /** - * @param $dirs - * - * @return FileFinder - */ - public function exclude($dirs): self - { - if ($dirs) { - $this->excludes = array_merge($this->excludes, (array)$dirs); - } - - return $this; - } - - /** - * @param bool $ignoreVCS - * - * @return self - */ - public function ignoreVCS($ignoreVCS): self - { - if ($ignoreVCS) { - $this->ignore |= static::IGNORE_VCS_FILES; - } else { - $this->ignore &= ~static::IGNORE_VCS_FILES; - } - - return $this; - } - - /** - * @param bool $ignoreDotFiles - * - * @return FileFinder - */ - public function ignoreDotFiles($ignoreDotFiles): self - { - if ($ignoreDotFiles) { - $this->ignore |= static::IGNORE_DOT_FILES; - } else { - $this->ignore &= ~static::IGNORE_DOT_FILES; - } - return $this; - } - - /** - * @param bool $followLinks - * - * @return FileFinder - */ - public function followLinks($followLinks = true): self - { - $this->followLinks = (bool)$followLinks; - - return $this; - } - - /** - * @param Closure $closure - * - * @return FileFinder - */ - public function filter(Closure $closure): self - { - $this->filters[] = $closure; - - return $this; - } - - /** - * @param string|array $dirs - * - * @return $this - */ - public function in($dirs): self - { - return $this->inDir($dirs); - } - - /** - * alias of the `in()` - * - * @param string|array $dirs - * - * @return FileFinder - */ - public function inDir($dirs): self - { - if ($dirs) { - $this->dirs = array_merge($this->dirs, (array)$dirs); - } - - return $this; - } - - /** - * @param mixed $iterator - * - * @return $this - * @throws InvalidArgumentException - */ - public function append($iterator): self - { - if ($iterator instanceof IteratorAggregate) { - $this->iterators[] = $iterator->getIterator(); - } elseif ($iterator instanceof Iterator) { - $this->iterators[] = $iterator; - } elseif ($iterator instanceof Traversable || is_array($iterator)) { - $it = new ArrayIterator(); - foreach ($iterator as $file) { - $it->append($file instanceof SplFileInfo ? $file : new SplFileInfo($file)); - } - $this->iterators[] = $it; - } else { - throw new InvalidArgumentException('The argument type is error'); - } - - return $this; - } - - /** - * @return int - */ - public function count(): int - { - return iterator_count($this->getIterator()); - } - - /** - * @return bool - */ - public function isFollowLinks(): bool - { - return $this->followLinks; - } - - /** - * Retrieve an external iterator - * - * @link http://php.net/manual/en/iteratoraggregate.getiterator.php - * @return Iterator|SplFileInfo[] An iterator - * @throws LogicException - */ - public function getIterator(): Traversable - { - if (0 === count($this->dirs) && 0 === count($this->iterators)) { - throw new LogicException('You must call one of in() or append() methods before iterating over a Finder.'); - } - - if (!$this->ignoreVcsAdded && self::IGNORE_VCS_FILES === (self::IGNORE_VCS_FILES & $this->ignore)) { - $this->excludes = array_merge($this->excludes, self::$vcsPatterns); - $this->ignoreVcsAdded = true; - } - - if (self::IGNORE_DOT_FILES === (self::IGNORE_DOT_FILES & $this->ignore)) { - $this->notNames[] = '.*'; - } - - if (1 === count($this->dirs) && 0 === count($this->iterators)) { - return $this->findInDirectory($this->dirs[0]); - } - - $iterator = new AppendIterator(); - foreach ($this->dirs as $dir) { - $iterator->append($this->findInDirectory($dir)); - } - - foreach ($this->iterators as $it) { - $iterator->append($it); - } - - return $iterator; - } - - /** - * @param string $dir - * - * @return Iterator - */ - private function findInDirectory(string $dir): Iterator - { - $flags = RecursiveDirectoryIterator::SKIP_DOTS; - - if ($this->followLinks) { - $flags |= RecursiveDirectoryIterator::FOLLOW_SYMLINKS; - } - - $iterator = new class ($dir, $flags) extends RecursiveDirectoryIterator - { - private $rootPath; - private $subPath; - private $rewindable; - private $directorySeparator = '/'; - private $ignoreUnreadableDirs; - - public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false) - { - if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { - throw new RuntimeException('This iterator only support returning current as fileInfo.'); - } - - $this->rootPath = $path; - $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; - parent::__construct($path, $flags); - - if ('/' !== DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { - $this->directorySeparator = DIRECTORY_SEPARATOR; - } - } - - public function current() - { - if (null === $subPathname = $this->subPath) { - $subPathname = $this->subPath = (string)$this->getSubPath(); - } - - if ('' !== $subPathname) { - $subPathname .= $this->directorySeparator; - } - - $subPathname .= $this->getFilename(); - - // $fileInfo = new \SplFileInfo($this->getPathname()); - $fileInfo = new SplFileInfo($this->rootPath . $this->directorySeparator . $subPathname); - $fileInfo->relativePath = $this->subPath; - $fileInfo->relativePathname = $subPathname; - - return $fileInfo; - } - - public function getChildren() - { - try { - $children = parent::getChildren(); - - if ($children instanceof self) { - $children->rootPath = $this->rootPath; - $children->rewindable = &$this->rewindable; - $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; - } - - return $children; - } catch (UnexpectedValueException $e) { - if ($this->ignoreUnreadableDirs) { - return new RecursiveArrayIterator([]); - } - - throw new RuntimeException($e->getMessage(), $e->getCode(), $e); - } - } - - public function rewind(): void - { - if (false === $this->isRewindable()) { - return; - } - - parent::rewind(); - } - - public function isRewindable() - { - if (null !== $this->rewindable) { - return $this->rewindable; - } - - if (false !== $stream = @opendir($this->getPath())) { - $infoS = stream_get_meta_data($stream); - closedir($stream); - - if ($infoS['seekable']) { - return $this->rewindable = true; - } - } - - return $this->rewindable = false; - } - }; - - // exclude directories - if ($this->excludes) { - $iterator = new class ($iterator, $this->excludes) extends FilterIterator implements RecursiveIterator - { - private $excludes; - private $iterator; - - public function __construct(RecursiveIterator $iterator, array $excludes) - { - $this->excludes = array_flip($excludes); - $this->iterator = $iterator; - - parent::__construct($iterator); - } - - public function accept(): bool - { - $name = $this->current()->getFilename(); - return !($this->current()->isDir() && isset($this->excludes[$name])); - } - - public function hasChildren(): bool - { - return $this->iterator->hasChildren(); - } - - public function getChildren() - { - $children = new self($this->iterator->getChildren(), []); - $children->excludes = $this->excludes; - - return $children; - } - }; - } - - // create recursive iterator - $iterator = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST); - - // mode: find files or dirs - if ($this->mode) { - $iterator = new class ($iterator, $this->mode) extends FilterIterator - { - private $mode; - - public function __construct(Iterator $iterator, int $mode) - { - $this->mode = $mode; - parent::__construct($iterator); - } - - public function accept(): bool - { - $info = $this->current(); - if (FileFinder::ONLY_DIR === $this->mode && $info->isFile()) { - return false; - } - - if (FileFinder::ONLY_FILE === $this->mode && $info->isDir()) { - return false; - } - - return true; - } - }; - } - - if ($this->names || $this->notNames) { - $iterator = new class ($iterator, $this->names, $this->notNames) extends FilterIterator - { - private $names; - private $notNames; - - public function __construct(Iterator $iterator, array $names, array $notNames) - { - parent::__construct($iterator); - $this->names = $names; - $this->notNames = $notNames; - } - - public function accept(): bool - { - $pathname = $this->current()->getFilename(); - - foreach ($this->notNames as $not) { - if (fnmatch($not, $pathname)) { - return false; - } - } - - if ($this->names) { - foreach ($this->names as $need) { - if (fnmatch($need, $pathname)) { - return true; - } - } - - return false; - } - - return true; - } - }; - } - - if ($this->filters) { - $iterator = new class ($iterator, $this->filters) extends FilterIterator - { - private $filters; - - public function __construct(Iterator $iterator, array $filters) - { - parent::__construct($iterator); - $this->filters = $filters; - } - - public function accept(): bool - { - $fileInfo = $this->current(); - foreach ($this->filters as $filter) { - if (false === $filter($fileInfo)) { - return false; - } - } - - return true; - } - }; - } - - if ($this->paths || $this->notPaths) { - $iterator = new class ($iterator, $this->paths, $this->notPaths) extends FilterIterator - { - private $paths; - private $notPaths; - - public function __construct(Iterator $iterator, array $paths, array $notPaths) - { - parent::__construct($iterator); - $this->paths = $paths; - $this->notPaths = $notPaths; - } - - public function accept(): bool - { - $pathname = $this->current()->relativePathname; - - if ('\\' === DIRECTORY_SEPARATOR) { - $pathname = str_replace('\\', '/', $pathname); - } - - foreach ($this->notPaths as $not) { - if (fnmatch($not, $pathname)) { - return false; - } - } - - if ($this->paths) { - foreach ($this->paths as $need) { - if (fnmatch($need, $pathname)) { - return true; - } - } - - return false; - } - - return true; - } - }; - } - - return $iterator; - } -} diff --git a/src/file-utils/src/FileSystem.php b/src/file-utils/src/FileSystem.php deleted file mode 100644 index 9d640a0..0000000 --- a/src/file-utils/src/FileSystem.php +++ /dev/null @@ -1,485 +0,0 @@ - 3 && ctype_alpha($file[0]) && $file[1] === ':' && strspn($file, '/\\', 2, - 1)) || null !== parse_url($file, PHP_URL_SCHEME); - } - - /** - * 转换为标准的路径结构 - * - * @param string $dirName - * - * @return string - */ - public static function pathFormat(string $dirName): string - { - $dirName = (string)str_ireplace('\\', '/', trim($dirName)); - - return substr($dirName, -1) === '/' ? $dirName : $dirName . '/'; - } - - /** - * @param string $path e.g phar://E:/workenv/xxx/yyy/app.phar/web - * - * @return string - */ - public function clearPharPath(string $path): string - { - if (strpos($path, 'phar://') === 0) { - $path = (string)substr($path, 7); - - if (strpos($path, '.phar')) { - return preg_replace('//[\w-]+\.phar/', '', $path); - } - } - - return $path; - } - - /** - * 检查文件/夹/链接是否存在 - * - * @param string $file 要检查的目标 - * @param null|string $type - * - * @return array|string - */ - public static function exists(string $file, $type = null) - { - if (!$type) { - return file_exists($file); - } - - $ret = false; - - if ($type === 'file') { - $ret = is_file($file); - } elseif ($type === 'dir') { - $ret = is_dir($file); - } elseif ($type === 'link') { - $ret = is_link($file); - } - - return $ret; - } - - /** - * @param string $file - * @param null|string|array $ext eg: 'jpg|gif' - * - * @throws FileNotFoundException - * @throws InvalidArgumentException - */ - public static function check(string $file, $ext = null): void - { - if (!$file || !file_exists($file)) { - throw new FileNotFoundException("File {$file} not exists!"); - } - - if ($ext) { - if (is_array($ext)) { - $ext = implode('|', $ext); - } - - if (preg_match("/\.($ext)$/i", $file)) { - throw new InvalidArgumentException("{$file} extension is not match: {$ext}"); - } - } - } - - /** - * Renames a file or a directory. - * - * @from Symfony-filesystem - * - * @param string $origin The origin filename or directory - * @param string $target The new filename or directory - * @param bool $overwrite Whether to overwrite the target if it already exists - * - * @throws IOException When target file or directory already exists - * @throws IOException When origin cannot be renamed - */ - public static function rename(string $origin, string $target, bool $overwrite = false): void - { - // we check that target does not exist - if (!$overwrite && static::isReadable($target)) { - throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target)); - } - - if (true !== rename($origin, $target)) { - throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target)); - } - } - - /** - * Tells whether a file exists and is readable. - * - * @from Symfony-filesystem - * - * @param string $filename Path to the file - * - * @return bool - * @throws IOException When windows path is longer than 258 characters - */ - public static function isReadable(string $filename): bool - { - if ('\\' === DIRECTORY_SEPARATOR && strlen($filename) > 258) { - throw new IOException('Could not check if file is readable because path length exceeds 258 characters.'); - } - - return is_readable($filename); - } - - /** - * Creates a directory recursively. - * - * @param string|array|Traversable $dirs The directory path - * @param int $mode The directory mode - * - * @throws IOException On any directory creation failure - */ - public static function mkdir($dirs, $mode = 0777): void - { - foreach (Arr::toIterator($dirs) as $dir) { - if (is_dir($dir)) { - continue; - } - - if (!@mkdir($dir, $mode, true) && !is_dir($dir)) { - $error = error_get_last(); - - if (!is_dir($dir)) { - // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one - if ($error) { - throw new IOException(sprintf('Failed to create "%s": %s.', $dir, $error['message'])); - } - - throw new IOException(sprintf('Failed to create "%s"', $dir)); - } - } - } - } - - /** - * Change mode for an array of files or directories. - * - * @from Symfony-filesystem - * - * @param string|array|Traversable $files A filename, an array of files, or a \Traversable instance to change mode - * @param int $mode The new mode (octal) - * @param int $umask The mode mask (octal) - * @param bool $recursive Whether change the mod recursively or not - * - * @throws IOException When the change fail - */ - public static function chmod($files, $mode, $umask = 0000, $recursive = false): void - { - foreach (Arr::toIterator($files) as $file) { - if (true !== @chmod($file, $mode & ~$umask)) { - throw new IOException(sprintf('Failed to chmod file "%s".', $file)); - } - - if ($recursive && is_dir($file) && !is_link($file)) { - self::chmod(new FilesystemIterator($file), $mode, $umask, true); - } - } - } - - /** - * Change the owner of an array of files or directories. - * - * @from Symfony-filesystem - * - * @param string|array|Traversable $files A filename, an array of files, or a \Traversable instance to change owner - * @param string $user The new owner user name - * @param bool $recursive Whether change the owner recursively or not - * - * @throws IOException When the change fail - */ - public static function chown($files, string $user, $recursive = false): void - { - foreach (Arr::toIterator($files) as $file) { - if ($recursive && is_dir($file) && !is_link($file)) { - self::chown(new FilesystemIterator($file), $user, true); - } - - if (is_link($file) && function_exists('lchown')) { - if (true !== lchown($file, $user)) { - throw new IOException(sprintf('Failed to chown file "%s".', $file)); - } - } elseif (true !== chown($file, $user)) { - throw new IOException(sprintf('Failed to chown file "%s".', $file)); - } - } - } - - /** - * @param string $srcDir - * @param callable $filter - * - * @return RecursiveIteratorIterator - * @throws InvalidArgumentException - */ - public static function getIterator(string $srcDir, callable $filter): RecursiveIteratorIterator - { - if (!$srcDir || !file_exists($srcDir)) { - throw new InvalidArgumentException('Please provide a exists source directory.'); - } - - $directory = new RecursiveDirectoryIterator($srcDir); - $filterIterator = new RecursiveCallbackFilterIterator($directory, $filter); - - return new RecursiveIteratorIterator($filterIterator); - } - - /** - * @param $path - * @param int $mode - * - * @return bool - */ - public static function chmodDir(string $path, $mode = 0664): bool - { - if (!is_dir($path)) { - return @chmod($path, $mode); - } - - $dh = opendir($path); - while (($file = readdir($dh)) !== false) { - if ($file !== '.' && $file !== '..') { - $fullPath = $path . '/' . $file; - if (is_link($fullPath)) { - return false; - } - - if (!is_dir($fullPath) && !@chmod($fullPath, $mode)) { - return false; - } - - if (!self::chmodDir($fullPath, $mode)) { - return false; - } - } - } - - closedir($dh); - - return @chmod($path, $mode) ? true : false; - } - - /** - * @param string $dir - * - * @return string - */ - public static function availableSpace(string $dir = '.'): string - { - $base = 1024; - $bytes = disk_free_space($dir); - $suffix = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - $class = min((int)log($bytes, $base), count($suffix) - 1); - - //echo $bytes . '
    '; - - // pow($base, $class) - return sprintf('%1.2f', $bytes / ($base ** $class)) . ' ' . $suffix[$class]; - } - - /** - * @param string $dir - * - * @return string - */ - public static function countSpace(string $dir = '.'): string - { - $base = 1024; - $bytes = disk_total_space($dir); - $suffix = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - $class = min((int)log($bytes, $base), count($suffix) - 1); - - // pow($base, $class) - return sprintf('%1.2f', $bytes / ($base ** $class)) . ' ' . $suffix[$class]; - } - - /** - * 文件或目录权限检查函数 - * - * @from web - * @access public - * - * @param string $file_path 文件路径 - * - * @return int 返回值的取值范围为{0 <= x <= 15},每个值表示的含义可由四位二进制数组合推出。 - * 返回值在二进制计数法中,四位由高到低分别代表 - * 可执行rename()函数权限 |可对文件追加内容权限 |可写入文件权限|可读取文件权限。 - */ - public static function pathModeInfo(string $file_path): int - { - /* 如果不存在,则不可读、不可写、不可改 */ - if (!file_exists($file_path)) { - return false; - } - - $mark = 0; - - if (0 === stripos(PHP_OS, 'WIN')) { - /* 测试文件 */ - $test_file = $file_path . '/cf_test.txt'; - - /* 如果是目录 */ - if (is_dir($file_path)) { - /* 检查目录是否可读 */ - $dir = @opendir($file_path); - - //如果目录打开失败,直接返回目录不可修改、不可写、不可读 - if ($dir === false) { - return $mark; - } - - //目录可读 001,目录不可读 000 - if (@readdir($dir) !== false) { - $mark ^= 1; - } - - @closedir($dir); - - /* 检查目录是否可写 */ - $fp = @fopen($test_file, 'wb'); - - //如果目录中的文件创建失败,返回不可写。 - if ($fp === false) { - return $mark; - } - - //目录可写可读 011,目录可写不可读 010 - if (@fwrite($fp, 'directory access testing.') !== false) { - $mark ^= 2; - } - - @fclose($fp); - @unlink($test_file); - - /* 检查目录是否可修改 */ - $fp = @fopen($test_file, 'ab+'); - if ($fp === false) { - return $mark; - } - - if (@fwrite($fp, "modify test.\r\n") !== false) { - $mark ^= 4; - } - - @fclose($fp); - - /* 检查目录下是否有执行rename()函数的权限 */ - if (@rename($test_file, $test_file) !== false) { - $mark ^= 8; - } - - @unlink($test_file); - - /* 如果是文件 */ - } elseif (is_file($file_path)) { - /* 以读方式打开 */ - $fp = @fopen($file_path, 'rb'); - if ($fp) { - $mark ^= 1; //可读 001 - } - - @fclose($fp); - - /* 试着修改文件 */ - $fp = @fopen($file_path, 'ab+'); - if ($fp && @fwrite($fp, '') !== false) { - $mark ^= 6; //可修改可写可读 111,不可修改可写可读011... - } - - @fclose($fp); - - /* 检查目录下是否有执行rename()函数的权限 */ - if (@rename($test_file, $test_file) !== false) { - $mark ^= 8; - } - } - } else { - if (@is_readable($file_path)) { - $mark ^= 1; - } - - if (@is_writable($file_path)) { - $mark ^= 14; - } - } - - return $mark; - } -} diff --git a/src/file-utils/src/ModifyWatcher.php b/src/file-utils/src/ModifyWatcher.php deleted file mode 100644 index d3c724c..0000000 --- a/src/file-utils/src/ModifyWatcher.php +++ /dev/null @@ -1,320 +0,0 @@ -idFile = $idFile; - } - } - - /** - * @param string $idFile - * - * @return $this - */ - public function setIdFile(string $idFile): self - { - $this->idFile = $idFile; - - return $this; - } - - /** - * @param string|array $notNames - * - * @return ModifyWatcher - */ - public function name($notNames): self - { - $this->notNames = array_merge($this->notNames, (array)$notNames); - - return $this; - } - - /** - * @param string|array $notNames - * - * @return ModifyWatcher - */ - public function notName($notNames): self - { - $this->notNames = array_merge($this->notNames, (array)$notNames); - - return $this; - } - - /** - * @param string|array $excludeDirs - * - * @return ModifyWatcher - */ - public function exclude($excludeDirs): self - { - $this->excludes = array_merge($this->excludes, (array)$excludeDirs); - - return $this; - } - - /** - * @param bool $ignoreDotDirs - * - * @return ModifyWatcher - */ - public function ignoreDotDirs($ignoreDotDirs): ModifyWatcher - { - $this->ignoreDotDirs = (bool)$ignoreDotDirs; - - return $this; - } - - /** - * @param bool $ignoreDotFiles - * - * @return ModifyWatcher - */ - public function ignoreDotFiles($ignoreDotFiles): ModifyWatcher - { - $this->ignoreDotFiles = (bool)$ignoreDotFiles; - return $this; - } - - /** - * @param string|array $dirs - * - * @return $this - */ - public function watch($dirs): self - { - $this->watchDirs = array_merge($this->watchDirs, (array)$dirs); - - return $this; - } - - /** - * alias of the watch() - * - * @param string|array $dirs - * - * @return $this - */ - public function watchDir($dirs): self - { - $this->watchDirs = array_merge($this->watchDirs, (array)$dirs); - - return $this; - } - - /** - * @return bool - */ - public function isModified(): bool - { - return $this->isChanged(); - } - - /** - * @return bool - */ - public function isChanged(): bool - { - if (!$this->idFile) { - $this->idFile = Sys::getTempDir() . '/' . md5(json_encode($this->watchDirs)) . '.id'; - } - - // get old hash id - if (!($old = $this->dirMd5) && (!$old = $this->getMd5ByIdFile())) { - $this->calcMd5Hash(); - - return false; - } - - $this->calcMd5Hash(); - - return $this->dirMd5 !== $old; - } - - /** - * @return bool|string - */ - public function getMd5ByIdFile() - { - if (!$file = $this->idFile) { - return false; - } - - if (!is_file($file)) { - return false; - } - - return trim(file_get_contents($file)); - } - - /** - * @return string - */ - public function calcMd5Hash(): string - { - if (!$this->watchDirs) { - throw new RuntimeException('Please setting want to watched directories before run.'); - } - - foreach ($this->watchDirs as $dir) { - $this->collectDirMd5($dir); - } - - $this->dirMd5 = md5($this->md5String); - $this->md5String = null; - - if ($this->idFile) { - file_put_contents($this->idFile, $this->dirMd5); - } - - return $this->dirMd5; - } - - /** - * @param string $watchDir - */ - private function collectDirMd5(string $watchDir): void - { - $files = scandir($watchDir, 0); - - foreach ($files as $f) { - if ($f === '.' || $f === '..') { - continue; - } - - $path = $watchDir . '/' . $f; - - // 递归目录 - if (is_dir($path)) { - if ($this->ignoreDotDirs && $f[0] === '.') { - continue; - } - - if (in_array($f, $this->excludes, true)) { - continue; - } - - $this->collectDirMd5($path); - - continue; - } - - // 检测文件 - foreach ($this->notNames as $name) { - if (preg_match('#' . $name . '#', $name)) { - continue; - } - } - - if ($this->names) { - foreach ($this->names as $name) { - if (preg_match('#' . $name . '#', $name)) { - $this->md5String .= md5_file($path); - $this->fileCounter++; - } - } - } else { - $this->md5String .= md5_file($path); - $this->fileCounter++; - } - } - } - - /** - * @return string - */ - public function getIdFile(): string - { - return $this->idFile; - } - - /** - * @return string[] - */ - public function getWatchDir(): array - { - return $this->watchDirs; - } - - /** - * @return string - */ - public function getDirMd5(): string - { - return $this->dirMd5; - } - - /** - * @return int - */ - public function getFileCounter(): int - { - return $this->fileCounter; - } -} diff --git a/src/file-utils/src/ReadTrait.php b/src/file-utils/src/ReadTrait.php deleted file mode 100644 index 60ad167..0000000 --- a/src/file-utils/src/ReadTrait.php +++ /dev/null @@ -1,290 +0,0 @@ -=5.1.0) - if (class_exists('SplFileObject', false)) { - $count = $endLine - $startLine; - - try { - $objFile = new SplFileObject($fileName, $mode); - $objFile->seek($startLine - 1); // 转到第N行, seek方法参数从0开始计数 - - for ($i = 0; $i <= $count; ++$i) { - $content[] = $objFile->current(); // current()获取当前行内容 - $objFile->next(); // 下一行 - } - } catch (Throwable $e) { - throw new FileSystemException("Error on read the file '{$fileName}'. ERR: " . $e->getMessage()); - } - - } else { // PHP<5.1 - if (!$fp = fopen($fileName, $mode)) { - throw new FileSystemException('can not open the file:' . $fileName); - } - - // 移动指针 跳过前$startLine行 - for ($i = 1; $i < $startLine; ++$i) { - fgets($fp); - } - - // 读取文件行内容 - for (; $i <= $endLine; ++$i) { - $content[] = fgets($fp); - } - - fclose($fp); - } - - return $content; - } - - /** - * symmetry 得到当前行对称上下几($lineNum)行的内容 - * - * @param string $fileName 含完整路径的文件 - * @param integer $current [当前行数] - * @param integer $lineNum [获取行数] = $lineNum*2+1 - * - * @return array - * @throws FileSystemException - */ - public static function readSymmetry($fileName, $current = 1, $lineNum = 3): array - { - $startLine = $current - $lineNum; - $endLine = $current + $lineNum; - - if ((int)$current < ($lineNum + 1)) { - $startLine = 1; - $endLine = 9; - } - - return self::readLines($fileName, $startLine, $endLine); - } - - /** - * @param string $file - * @param int $baseLine - * @param int $prevLines - * @param int $nextLines - * - * @return array - * @throws FileSystemException - */ - public static function readRangeLines(string $file, int $baseLine, int $prevLines = 3, int $nextLines = 3): array - { - $startLine = $baseLine - $prevLines; - $endLine = $baseLine + $nextLines; - - return self::readLines($file, $startLine, $endLine); - } - - /** - * 得到基准行数上5行下3行的内容, lines up and down - * - * @param string $file - * @param int $baseLine 基准行数 - * - * @return array - * @throws FileSystemException - */ - public static function getLines5u3d(string $file, int $baseLine = 1): array - { - return self::readRangeLines($file, $baseLine, 5); - } - - /** - * 读取文件的最后几行(支持大文件读取) - * - * @link http://www.jb51.net/article/81909.htm - * - * @param resource $fp e.g fopen("access.log", "r+") - * @param int $n - * @param int $base - * - * @return array - */ - public static function tail($fp, int $n, int $base = 5): array - { - assert($n > 0); - - $pos = $n + 1; - $lines = []; - - while (count($lines) <= $n) { - try { - fseek($fp, -$pos, SEEK_END); - } catch (Exception $e) { - fclose($fp); - break; - } - - $pos *= $base; - - while (!feof($fp)) { - array_unshift($lines, fgets($fp)); - } - } - - return array_slice($lines, 0, $n); - } -} diff --git a/src/file-utils/test/boot.php b/src/file-utils/test/boot.php deleted file mode 100644 index dccabf2..0000000 --- a/src/file-utils/test/boot.php +++ /dev/null @@ -1,28 +0,0 @@ -7.1.0" - }, - "autoload": { - "psr-4": { - "Toolkit\\PhpUtil\\": "src/" - } - }, - "suggest": { - "inhere/php-validate": "Very lightweight data validate tool", - "inhere/console": "a lightweight php console application library." - } -} diff --git a/src/php-utils/example/property_exists.php b/src/php-utils/example/property_exists.php deleted file mode 100644 index 8d86234..0000000 --- a/src/php-utils/example/property_exists.php +++ /dev/null @@ -1,36 +0,0 @@ -prop1; - } -} - - -echo "use class:\n"; - -echo 'public: ' . (property_exists(Some::class, 'prop0') ? 'Y' : 'N') . PHP_EOL; -echo 'private: ' . (property_exists(Some::class, 'prop1') ? 'Y' : 'N') . PHP_EOL; -echo 'protected: ' . (property_exists(Some::class, 'prop2') ? 'Y' : 'N') . PHP_EOL; -echo "use object:\n"; - -$object = new Some(); - -echo 'public: ' . (property_exists($object, 'prop0') ? 'Y' : 'N') . PHP_EOL; -echo 'private: ' . (property_exists($object, 'prop1') ? 'Y' : 'N') . PHP_EOL; -echo 'protected: ' . (property_exists($object, 'prop2') ? 'Y' : 'N') . PHP_EOL; diff --git a/src/php-utils/phpunit.xml.dist b/src/php-utils/phpunit.xml.dist deleted file mode 100644 index 6052813..0000000 --- a/src/php-utils/phpunit.xml.dist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - test/ - - - - - - src - - - diff --git a/src/php-utils/src/AutoLoader.php b/src/php-utils/src/AutoLoader.php deleted file mode 100644 index a19099d..0000000 --- a/src/php-utils/src/AutoLoader.php +++ /dev/null @@ -1,365 +0,0 @@ -addPsr4Map([ - * 'namespace' => 'path' - * ]); - * $loader->addClassMap([ - * 'name' => 'file' - * ]); - * ``` - */ -class AutoLoader -{ - /** - * @var self - */ - private static $loader; - - /** - * @var array - */ - private static $files = []; - - /** - * @var array - * array ( - * 'prefix' => 'dir path' - * ) - */ - private $psr0Map = []; - - /** - * @var array - * array ( - * 'prefix' => 'dir path' - * ) - */ - private $psr4Map = []; - - /** - * @var array - */ - private $classMap = []; - - /** - * @var array - */ - private $missingClasses = []; - - /** - * @param array $files - * - * @return self - */ - public static function getLoader(array $files = []): self - { - if (null !== self::$loader) { - return self::$loader; - } - - if ($files) { - self::addFiles($files); - } - - self::$loader = $loader = new self(); - - $loader->register(true); - - foreach (self::$files as $fileIdentifier => $file) { - _globalIncludeFile($fileIdentifier, $file); - } - - return $loader; - } - - /************************************************************************** - * independent files - *************************************************************************/ - - /** - * @return array - */ - public static function getFiles(): array - { - return self::$files; - } - - /** - * @param array $files - */ - public static function setFiles(array $files): void - { - self::$files = $files; - } - - /** - * @param array $files - */ - public static function addFiles(array $files): void - { - if (self::$files) { - self::$files = array_merge(self::$files, $files); - } else { - self::$files = $files; - } - } - - /************************************************************************** - * class loader - *************************************************************************/ - - /** - * @param string $prefix - * @param string $path - */ - public function addPsr0(string $prefix, string $path): void - { - $this->psr0Map[$prefix] = $path; - } - - /** - * @param array $psr0Map Class to filename map - */ - public function addPsr0Map(array $psr0Map): void - { - if ($this->psr0Map) { - $this->psr0Map = array_merge($this->psr0Map, $psr0Map); - } else { - $this->psr0Map = $psr0Map; - } - } - - /** - * @param string $prefix - * @param string $path - * - * @throws InvalidArgumentException - */ - public function addPsr4(string $prefix, string $path): void - { - // Register directories for a new namespace. - $length = strlen($prefix); - - if ('\\' !== $prefix[$length - 1]) { - throw new InvalidArgumentException('A non-empty PSR-4 prefix must end with a namespace separator.'); - } - - $this->psr4Map[$prefix] = $path; - } - - /** - * @param array $psr4Map Class to filename map - */ - public function addPsr4Map(array $psr4Map): void - { - if ($this->psr4Map) { - $this->psr4Map = array_merge($this->psr4Map, $psr4Map); - } else { - $this->psr4Map = $psr4Map; - } - } - - /** - * @return array - */ - public function getPsr4Map(): array - { - return $this->psr4Map; - } - - /** - * @param array $psr4Map - */ - public function setPsr4Map($psr4Map): void - { - $this->psr4Map = $psr4Map; - } - - /** - * @return array - */ - public function getClassMap(): array - { - return $this->classMap; - } - - /** - * @param array $classMap - */ - public function setClassMap(array $classMap): void - { - $this->classMap = $classMap; - } - - /** - * @param array $classMap Class to filename map - */ - public function addClassMap(array $classMap): void - { - if ($this->classMap) { - $this->classMap = array_merge($this->classMap, $classMap); - } else { - $this->classMap = $classMap; - } - } - - /** - * Registers this instance as an autoloader. - * - * @param bool $prepend Whether to prepend the autoloader or not - */ - public function register(bool $prepend = false): void - { - spl_autoload_register([$this, 'loadClass'], true, $prepend); - } - - /** - * Un-registers this instance as an autoloader. - */ - public function unRegister(): void - { - spl_autoload_unregister([$this, 'loadClass']); - } - - /** - * Loads the given class or interface. - * - * @param string $class The name of the class - * - * @return bool|null True if loaded, null otherwise - */ - public function loadClass(string $class): ?bool - { - if ($file = $this->findFile($class)) { - _includeClassFile($file); - return true; - } - - return null; - } - - /** - * Finds the path to the file where the class is defined. - * - * @param string $class The name of the class - * - * @return string|false The path if found, false otherwise - */ - public function findFile(string $class) - { - // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 - if ('\\' === $class[0]) { - $class = (string)substr($class, 1); - } - - // class map lookup - if (isset($this->classMap[$class])) { - return $this->classMap[$class]; - } - - $file = $this->findFileWithExtension($class, '.php'); - - if (false === $file) { - // Remember that this class does not exist. - $this->missingClasses[$class] = true; - } - - return $file; - } - - /** - * @param string $class - * @param string $ext - * - * @return bool|string - */ - private function findFileWithExtension(string $class, string $ext) - { - // PSR-4 lookup - $logicalPathPsr4 = str_replace('\\', DIRECTORY_SEPARATOR, $class) . $ext; - - // PSR-4 - foreach ($this->psr4Map as $prefix => $dir) { - if (0 === strpos($class, $prefix)) { - $length = strlen($prefix); - - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { - return $file; - } - } - } - - // PEAR-like class name - $logicalPathPsr0 = str_replace('_', DIRECTORY_SEPARATOR, $class) . $ext; - - foreach ($this->psr0Map as $prefix => $dir) { - if (0 === strpos($class, $prefix)) { - $file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0; - - if (file_exists($file)) { - return $file; - } - } - } - - return false; - } - - /** - * @return array - */ - public function getMissingClasses(): array - { - return $this->missingClasses; - } -} - -function _globalIncludeFile($fileIdentifier, $file) -{ - if (empty($GLOBALS['__global_autoload_files'][$fileIdentifier])) { - require $file; - - $GLOBALS['__global_autoload_files'][$fileIdentifier] = true; - } -} - -/** - * Scope isolated include. - * Prevents access to $this/self from included files. - * - * @param $file - */ -function _includeClassFile($file) -{ - include $file; -} - diff --git a/src/php-utils/src/Php.php b/src/php-utils/src/Php.php deleted file mode 100644 index 8edec73..0000000 --- a/src/php-utils/src/Php.php +++ /dev/null @@ -1,18 +0,0 @@ - 'description', // default tag name, first line text will attach to it. - * @param array $defaults - * - * @return array The parsed tags - */ - public static function getTags(string $comment, array $options = [], array $defaults = []): array - { - if (!$comment = trim($comment, "/ \n")) { - return []; - } - - $options = array_merge([ - 'allow' => [], // only allowed tags - 'ignore' => ['param', 'return'], // ignore tags - 'default' => 'description', // default tag name, first line text will attach to it. - ], $options); - - $allow = (array)$options['allow']; - $ignored = (array)$options['ignore']; - $default = (string)$options['default']; - - $comment = str_replace("\r\n", "\n", $comment); - $comment = "@{$default} \n" . str_replace("\r", '', trim(preg_replace('/^\s*\**( |\t)?/m', '', $comment))); - - $tags = []; - $parts = preg_split('/^\s*@/m', $comment, -1, PREG_SPLIT_NO_EMPTY); - - foreach ($parts as $part) { - if (preg_match('/^(\w+)(.*)/ms', trim($part), $matches)) { - $name = $matches[1]; - if (!$name || in_array($name, $ignored, true)) { - continue; - } - - if (!$value = trim($matches[2])) { - continue; - } - - // always allow default tag - if ($default !== $name && $allow && !in_array($name, $allow, true)) { - continue; - } - - if (!isset($tags[$name])) { - $tags[$name] = $value; - } elseif (is_array($tags[$name])) { - $tags[$name][] = $value; - } else { - $tags[$name] = [$tags[$name], $value]; - } - } - } - - return $defaults ? array_merge($defaults, $tags) : $tags; - } - - /** - * Returns the first line of docBlock. - * - * @param string $comment - * - * @return string - */ - public static function firstLine(string $comment): string - { - $docLines = preg_split('~\R~u', $comment); - - if (isset($docLines[1])) { - return trim($docLines[1], "/\t *"); - } - - return ''; - } - - /** - * Returns full description from the doc-block. - * If have multi line text, will return multi line. - * - * @param string $comment - * - * @return string - */ - public static function description(string $comment): string - { - $comment = str_replace("\r", '', trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($comment, '/')))); - - if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) { - $comment = trim(substr($comment, 0, $matches[0][1])); - } - - return $comment; - } -} diff --git a/src/php-utils/src/PhpDotEnv.php b/src/php-utils/src/PhpDotEnv.php deleted file mode 100644 index bf3a170..0000000 --- a/src/php-utils/src/PhpDotEnv.php +++ /dev/null @@ -1,132 +0,0 @@ -add($file); - } - - /** - * @param string $file - */ - public function add(string $file): void - { - if (is_file($file) && is_readable($file)) { - $this->settingEnv(parse_ini_file($file)); - } - } - - /** - * setting env data - * - * @param array $data - */ - private function settingEnv(array $data): void - { - $loadedVars = array_flip(explode(',', getenv(self::FULL_KEY))); - unset($loadedVars['']); - - foreach ($data as $name => $value) { - if (is_int($name) || !is_string($value)) { - continue; - } - - $name = strtoupper($name); - $notHttpName = 0 !== strpos($name, 'HTTP_'); - - // don't check existence with getenv() because of thread safety issues - if ((isset($_ENV[$name]) || (isset($_SERVER[$name]) && $notHttpName)) && !isset($loadedVars[$name])) { - continue; - } - - // is a constant var - if ($value && defined($value)) { - $value = constant($value); - } - - // eg: "FOO=BAR" - putenv("$name=$value"); - $_ENV[$name] = $value; - - if ($notHttpName) { - $_SERVER[$name] = $value; - } - - $loadedVars[$name] = true; - } - - if ($loadedVars) { - $loadedVars = implode(',', array_keys($loadedVars)); - putenv(self::FULL_KEY . "=$loadedVars"); - $_ENV[self::FULL_KEY] = $loadedVars; - $_SERVER[self::FULL_KEY] = $loadedVars; - } - } -} diff --git a/src/php-utils/src/PhpEnv.php b/src/php-utils/src/PhpEnv.php deleted file mode 100644 index a072333..0000000 --- a/src/php-utils/src/PhpEnv.php +++ /dev/null @@ -1,232 +0,0 @@ - $lastError['type'], - 'message' => $lastError['message'], - 'file' => $lastError['file'], - 'line' => $lastError['line'], - 'catcher' => __METHOD__, - ]; - - if ($catcher) { - $data['catcher'] = $catcher; - } - - return [$digest, $data]; - } - - /** - * @param int $code - * - * @return string - */ - public static function codeToString(int $code): string - { - switch ($code) { - case E_ERROR: - return 'E_ERROR'; - case E_WARNING: - return 'E_WARNING'; - case E_PARSE: - return 'E_PARSE'; - case E_NOTICE: - return 'E_NOTICE'; - case E_CORE_ERROR: - return 'E_CORE_ERROR'; - case E_CORE_WARNING: - return 'E_CORE_WARNING'; - case E_COMPILE_ERROR: - return 'E_COMPILE_ERROR'; - case E_COMPILE_WARNING: - return 'E_COMPILE_WARNING'; - case E_USER_ERROR: - return 'E_USER_ERROR'; - case E_USER_WARNING: - return 'E_USER_WARNING'; - case E_USER_NOTICE: - return 'E_USER_NOTICE'; - case E_STRICT: - return 'E_STRICT'; - case E_RECOVERABLE_ERROR: - return 'E_RECOVERABLE_ERROR'; - case E_DEPRECATED: - return 'E_DEPRECATED'; - case E_USER_DEPRECATED: - return 'E_USER_DEPRECATED'; - } - - return 'Unknown PHP error'; - } -} diff --git a/src/php-utils/src/PhpException.php b/src/php-utils/src/PhpException.php deleted file mode 100644 index a63c072..0000000 --- a/src/php-utils/src/PhpException.php +++ /dev/null @@ -1,117 +0,0 @@ -getMessage()}"; - } else { - $message = sprintf("

    %s(%d): %s

    \n
    File: %s(Line %d)%s \n\n%s
    ", - get_class($e), $e->getCode(), $e->getMessage(), $e->getFile(), $e->getLine(), - $catcher ? "\nCatch By: $catcher" : '', $e->getTraceAsString()); - } - - return $clearHtml ? strip_tags($message) : "
    {$message}
    "; - } - - /** - * Converts an exception into a simple array. - * - * @param Exception|Throwable $e the exception being converted - * @param bool $getTrace - * @param null|string $catcher - * - * @return array - */ - public static function toArray($e, bool $getTrace = true, string $catcher = null): array - { - $data = [ - 'class' => get_class($e), - 'message' => $e->getMessage(), - 'code' => $e->getCode(), - 'file' => $e->getFile() . ':' . $e->getLine(), - ]; - - if ($catcher) { - $data['catcher'] = $catcher; - } - - if ($getTrace) { - $data['trace'] = $e->getTrace(); - } - - return $data; - } - - /** - * Converts an exception into a json string. - * - * @param Exception|Throwable $e the exception being converted - * @param bool $getTrace - * @param null|string $catcher - * - * @return string the string representation of the exception. - */ - public static function toJson($e, bool $getTrace = true, string $catcher = null): string - { - if (!$getTrace) { - return json_encode(['msg' => "Error: {$e->getMessage()}"]); - } - - $map = [ - 'code' => $e->getCode() ?: 500, - 'msg' => sprintf('%s(%d): %s, File: %s(Line %d)', get_class($e), $e->getCode(), $e->getMessage(), - $e->getFile(), $e->getLine()), - 'data' => $e->getTrace() - ]; - - if ($catcher) { - $map['catcher'] = $catcher; - } - - if ($getTrace) { - $map['trace'] = $e->getTrace(); - } - - return json_encode($map); - } -} diff --git a/src/php-utils/src/PhpHelper.php b/src/php-utils/src/PhpHelper.php deleted file mode 100644 index 0314a69..0000000 --- a/src/php-utils/src/PhpHelper.php +++ /dev/null @@ -1,208 +0,0 @@ - 0) { - $cb = explode('::', $cb, 2); - // function - } elseif (function_exists($cb)) { - return $cb(...$args); - } - } elseif (is_object($cb) && method_exists($cb, '__invoke')) { - return $cb(...$args); - } - - if (is_array($cb)) { - [$obj, $mhd] = $cb; - - return is_object($obj) ? $obj->$mhd(...$args) : $obj::$mhd(...$args); - } - - return $cb(...$args); - } - - /** - * @param callable $cb - * @param array $args - * - * @return mixed - */ - public static function callByArray(callable $cb, array $args) - { - return self::call($cb, ...$args); - } - - /** - * 给对象设置属性值 - * - 会先尝试用 setter 方法设置属性 - * - 再尝试直接设置属性 - * - * @param mixed $object An object instance - * @param array $options - * - * @return mixed - */ - public static function initObject($object, array $options) - { - foreach ($options as $property => $value) { - if (is_numeric($property)) { - continue; - } - - $setter = 'set' . ucfirst($property); - - // has setter - if (method_exists($object, $setter)) { - $object->$setter($value); - } elseif (property_exists($object, $property)) { - $object->$property = $value; - } - } - - return $object; - } - - /** - * 获取资源消耗 - * - * @param int $startTime - * @param int|float $startMem - * @param array $info - * @param bool $realUsage - * - * @return array - */ - public static function runtime($startTime, $startMem, array $info = [], $realUsage = false): array - { - $info['startTime'] = $startTime; - $info['endTime'] = microtime(true); - $info['endMemory'] = memory_get_usage($realUsage); - - // 计算运行时间 - $info['runtime'] = number_format(($info['endTime'] - $startTime) * 1000, 3) . 'ms'; - - if ($startMem) { - $startMem = array_sum(explode(' ', $startMem)); - $endMem = array_sum(explode(' ', $info['endMemory'])); - - $info['memory'] = number_format(($endMem - $startMem) / 1024, 3) . 'kb'; - } - - $peakMem = memory_get_peak_usage(true) / 1024 / 1024; - // record - $info['peakMemory'] = number_format($peakMem, 3) . 'Mb'; - - return $info; - } - - /** - * @return array - */ - public static function getUserConstants(): array - { - $const = get_defined_constants(true); - - return $const['user'] ?? []; - } - - /** - * dump vars - * - * @param array ...$args - * - * @return string - */ - public static function dumpVars(...$args): string - { - ob_start(); - var_dump(...$args); - $string = ob_get_clean(); - - return preg_replace("/=>\n\s+/", '=> ', $string); - } - - /** - * print vars - * - * @param array ...$args - * - * @return string - */ - public static function printVars(...$args): string - { - $string = ''; - - foreach ($args as $arg) { - $string .= print_r($arg, 1) . PHP_EOL; - } - - return preg_replace("/Array\n\s+\(/", 'Array (', $string); - } - - /** - * @param mixed $var - * - * @return string - */ - public static function exportVar($var): string - { - $string = var_export($var, true); - - return preg_replace('/=>\s+\n\s+array \(/', '=> array (', $string); - } - -} diff --git a/src/php-utils/src/Type.php b/src/php-utils/src/Type.php deleted file mode 100644 index 452ee20..0000000 --- a/src/php-utils/src/Type.php +++ /dev/null @@ -1,66 +0,0 @@ -assertCount(3, $ret); - $this->assertArrayHasKey('since', $ret); - $this->assertArrayHasKey('example', $ret); - $this->assertArrayHasKey('description', $ret); - - $ret = PhpDoc::getTags($comment, ['allow' => ['example']]); - $this->assertCount(2, $ret); - $this->assertArrayNotHasKey('since', $ret); - $this->assertArrayHasKey('example', $ret); - $this->assertArrayHasKey('description', $ret); - - $ret = PhpDoc::getTags($comment, [ - 'allow' => ['example'], - 'default' => 'desc' - ]); - $this->assertCount(2, $ret); - $this->assertArrayNotHasKey('since', $ret); - $this->assertArrayHasKey('example', $ret); - $this->assertArrayHasKey('desc', $ret); - $this->assertArrayNotHasKey('description', $ret); - } -} diff --git a/src/php-utils/test/boot.php b/src/php-utils/test/boot.php deleted file mode 100644 index 6acd4bf..0000000 --- a/src/php-utils/test/boot.php +++ /dev/null @@ -1,27 +0,0 @@ -7.1.0" - }, - "autoload": { - "psr-4": { - "Toolkit\\Sys\\": "src/" - } - }, - "suggest": { - "inhere/php-validate": "Very lightweight data validate tool", - "inhere/console": "a lightweight php console application library." - } -} diff --git a/src/sys-utils/phpunit.xml.dist b/src/sys-utils/phpunit.xml.dist deleted file mode 100644 index 6052813..0000000 --- a/src/sys-utils/phpunit.xml.dist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - test/ - - - - - - src - - - diff --git a/src/sys-utils/src/ProcessUtil.php b/src/sys-utils/src/ProcessUtil.php deleted file mode 100644 index 7e110db..0000000 --- a/src/sys-utils/src/ProcessUtil.php +++ /dev/null @@ -1,715 +0,0 @@ - 'SIGINT(Ctrl+C)', - SIGTERM => 'SIGTERM', - SIGKILL => 'SIGKILL', - SIGSTOP => 'SIGSTOP', - ]; - - /** - * fork/create a child process. - * - * @param callable|null $onStart Will running on the child process start. - * @param callable|null $onError - * @param int $id The process index number. will use `forks()` - * - * @return array|false - * @throws RuntimeException - */ - public static function fork(callable $onStart = null, callable $onError = null, $id = 0) - { - if (!self::hasPcntl()) { - return false; - } - - $info = []; - $pid = pcntl_fork(); - - // at parent, get forked child info - if ($pid > 0) { - $info = [ - 'id' => $id, - 'pid' => $pid, - 'startTime' => time(), - ]; - } elseif ($pid === 0) { // at child - $pid = self::getPid(); - - if ($onStart) { - $onStart($pid, $id); - } - } else { - if ($onError) { - $onError($pid); - } - - throw new RuntimeException('Fork child process failed! exiting.'); - } - - return $info; - } - - /** - * @param callable|null $onStart - * @param callable|null $onError - * @param int $id - * - * @return array|false - * @throws RuntimeException - * @see ProcessUtil::fork() - */ - public static function create(callable $onStart = null, callable $onError = null, $id = 0) - { - return self::fork($onStart, $onError, $id); - } - - /** - * Daemon, detach and run in the background - * - * @param Closure|null $beforeQuit - * - * @return int Return new process PID - * @throws RuntimeException - */ - public static function daemonRun(Closure $beforeQuit = null): int - { - if (!self::hasPcntl()) { - return 0; - } - - // umask(0); - $pid = pcntl_fork(); - - switch ($pid) { - case 0: // at new process - $pid = self::getPid(); - - if (posix_setsid() < 0) { - throw new RuntimeException('posix_setsid() execute failed! exiting'); - } - - // chdir('/'); - // umask(0); - break; - - case -1: // fork failed. - throw new RuntimeException('Fork new process is failed! exiting'); - break; - - default: // at parent - if ($beforeQuit) { - $beforeQuit($pid); - } - - exit; - } - - return $pid; - } - - /** - * @param int $number - * @param callable|null $onStart - * @param callable|null $onError - * - * @return array|false - * @throws RuntimeException - * @see ProcessUtil::forks() - */ - public static function multi(int $number, callable $onStart = null, callable $onError = null) - { - return self::forks($number, $onStart, $onError); - } - - /** - * fork/create multi child processes. - * - * @param int $number - * @param callable|null $onStart Will running on the child processes. - * @param callable|null $onError - * - * @return array|false - * @throws RuntimeException - */ - public static function forks(int $number, callable $onStart = null, callable $onError = null) - { - if ($number <= 0) { - return false; - } - - if (!self::hasPcntl()) { - return false; - } - - $pidAry = []; - - for ($id = 0; $id < $number; $id++) { - $info = self::fork($onStart, $onError, $id); - $pidAry[$info['pid']] = $info; - } - - return $pidAry; - } - - /** - * wait child exit. - * - * @param callable $onExit Exit callback. will received args: (pid, exitCode, status) - * - * @return bool - */ - public static function wait(callable $onExit): bool - { - if (!self::hasPcntl()) { - return false; - } - - $status = null; - - // pid < 0:子进程都没了 - // pid > 0:捕获到一个子进程退出的情况 - // pid = 0:没有捕获到退出的子进程 - while (($pid = pcntl_waitpid(-1, $status, WNOHANG)) >= 0) { - if ($pid) { - // handler(pid, exitCode, status) - $onExit($pid, pcntl_wexitstatus($status), $status); - } else { - usleep(50000); - } - } - - return true; - } - - /** - * Stops all running children - * - * @param array $children - * [ - * 'pid' => [ - * 'id' => worker id - * ], - * ... ... - * ] - * @param int $signal - * @param array $events - * [ - * 'beforeStops' => function ($sigText) { - * echo "Stopping processes({$sigText}) ...\n"; - * }, - * 'beforeStop' => function ($pid, $info) { - * echo "Stopping process(PID:$pid)\n"; - * } - * ] - * - * @return bool - */ - public static function stopWorkers(array $children, int $signal = SIGTERM, array $events = []): bool - { - if (!$children) { - return false; - } - - if (!self::hasPcntl()) { - return false; - } - - $events = array_merge([ - 'beforeStops' => null, - 'beforeStop' => null, - ], $events); - - if ($cb = $events['beforeStops']) { - $cb($signal, self::$signalMap[$signal]); - } - - foreach ($children as $pid => $child) { - if ($cb = $events['beforeStop']) { - $cb($pid, $child); - } - - // send exit signal. - self::sendSignal($pid, $signal); - } - - return true; - } - - /************************************************************************************** - * basic signal methods - *************************************************************************************/ - - /** - * send kill signal to the process - * - * @param int $pid - * @param bool $force - * @param int $timeout - * - * @return bool - */ - public static function kill(int $pid, bool $force = false, int $timeout = 3): bool - { - return self::sendSignal($pid, $force ? SIGKILL : SIGTERM, $timeout); - } - - /** - * Do shutdown process and wait it exit. - * - * @param int $pid Master Pid - * @param bool $force - * @param int $waitTime - * @param null $error - * @param string $name - * - * @return bool - */ - public static function killAndWait( - int $pid, - &$error = null, - $name = 'process', - bool $force = false, - int $waitTime = 10 - ): bool { - // do stop - if (!self::kill($pid, $force)) { - $error = "Send stop signal to the $name(PID:$pid) failed!"; - - return false; - } - - // not wait, only send signal - if ($waitTime <= 0) { - $error = "The $name process stopped"; - - return true; - } - - $startTime = time(); - echo 'Stopping .'; - - // wait exit - while (true) { - if (!self::isRunning($pid)) { - break; - } - - if (time() - $startTime > $waitTime) { - $error = "Stop the $name(PID:$pid) failed(timeout)!"; - break; - } - - echo '.'; - sleep(1); - } - - if ($error) { - return false; - } - - return true; - } - - /** - * @param int $pid - * - * @return bool - */ - public static function isRunning(int $pid): bool - { - return ($pid > 0) && @posix_kill($pid, 0); - } - - /** - * exit - * - * @param int $code - */ - public static function quit($code = 0): void - { - exit((int)$code); - } - - /** - * 杀死所有进程 - * - * @param $name - * @param int $sigNo - * - * @return string - */ - public static function killByName(string $name, int $sigNo = 9): string - { - $cmd = 'ps -eaf |grep "' . $name . '" | grep -v "grep"| awk "{print $2}"|xargs kill -' . $sigNo; - - return exec($cmd); - } - - /************************************************************************************** - * process signal handle - *************************************************************************************/ - - /** - * send signal to the process - * - * @param int $pid - * @param int $signal - * @param int $timeout - * - * @return bool - */ - public static function sendSignal(int $pid, int $signal, int $timeout = 0): bool - { - if ($pid <= 0 || !self::hasPosix()) { - return false; - } - - // do send - if ($ret = posix_kill($pid, $signal)) { - return true; - } - - // don't want retry - if ($timeout <= 0) { - return $ret; - } - - // failed, try again ... - $timeout = $timeout > 0 && $timeout < 10 ? $timeout : 3; - $startTime = time(); - - // retry stop if not stopped. - while (true) { - // success - if (!$isRunning = @posix_kill($pid, 0)) { - break; - } - - // have been timeout - if ((time() - $startTime) >= $timeout) { - return false; - } - - // try again kill - $ret = posix_kill($pid, $signal); - usleep(10000); - } - - return $ret; - } - - /** - * install signal - * - * @param int $signal e.g: SIGTERM SIGINT(Ctrl+C) SIGUSR1 SIGUSR2 SIGHUP - * @param callable $handler - * - * @return bool - */ - public static function installSignal($signal, callable $handler): bool - { - if (!self::hasPcntl()) { - return false; - } - - return pcntl_signal($signal, $handler, false); - } - - /** - * dispatch signal - * - * @return bool - */ - public static function dispatchSignal(): bool - { - if (!self::hasPcntl()) { - return false; - } - - // receive and dispatch signal - return pcntl_signal_dispatch(); - } - - /** - * get signal handler - * - * @param int $signal - * - * @return bool|string|mixed - * @since 7.1 - */ - public static function getSignalHandler(int $signal) - { - return pcntl_signal_get_handler($signal); - } - - /** - * Enable/disable asynchronous signal handling or return the old setting - * - * @param bool|null $on - * - bool Enable or disable. - * - null Return old setting. - * - * @return bool - * @since 7.1 - */ - public static function asyncSignal(bool $on = null): bool - { - return pcntl_async_signals($on); - } - - /************************************************************************************** - * some help method - *************************************************************************************/ - - /** - * get current process id - * - * @return int - */ - public static function getPid(): int - { - if (function_exists('posix_getpid')) { - return posix_getpid(); - } - - return getmypid(); - } - - /** - * get PID by pid File - * - * @param string $file - * @param bool $checkLive - * - * @return int - */ - public static function getPidByFile(string $file, bool $checkLive = false): int - { - if ($file && file_exists($file)) { - $pid = (int)file_get_contents($file); - - // check live - if ($checkLive && self::isRunning($pid)) { - return $pid; - } - - unlink($file); - } - - return 0; - } - - /** - * Get unix user of current process. - * - * @return array - */ - public static function getCurrentUser(): array - { - return posix_getpwuid(posix_getuid()); - } - - /** - * - * ProcessUtil::afterDo(300, function () { - * static $i = 0; - * echo "#{$i}\talarm\n"; - * $i++; - * if ($i > 20) { - * ProcessUtil::clearAlarm(); // close - * } - * }); - * - * @param int $seconds - * @param callable $handler - * - * @return bool|int - */ - public static function afterDo(int $seconds, callable $handler) - { - if (!self::hasPcntl()) { - return false; - } - - self::installSignal(SIGALRM, $handler); - - return pcntl_alarm($seconds); - } - - /** - * @return int - */ - public static function clearAlarm(): int - { - return pcntl_alarm(-1); - } - - /** - * run a command. it is support windows - * - * @param string $command - * @param string|null $cwd - * - * @return array - * @throws RuntimeException - * @deprecated Please use Sys::run() - */ - public static function run(string $command, string $cwd = null): array - { - return Sys::run($command, $cwd); - } - - /** - * Set process title. - * - * @param string $title - * - * @return bool - */ - public static function setName(string $title): bool - { - return self::setTitle($title); - } - - /** - * Set process title. - * - * @param string $title - * - * @return bool - */ - public static function setTitle(string $title): bool - { - if (!$title || 'Darwin' === PHP_OS) { - return false; - } - - if (function_exists('cli_set_process_title')) { - cli_set_process_title($title); - } elseif (function_exists('setproctitle')) { - setproctitle($title); - } - - if ($error = error_get_last()) { - throw new RuntimeException($error['message']); - } - - return false; - } - - /** - * Set unix user and group for current process script. - * - * @param string $user - * @param string $group - * - * @throws RuntimeException - */ - public static function changeScriptOwner(string $user, string $group = ''): void - { - $uInfo = posix_getpwnam($user); - - if (!$uInfo || !isset($uInfo['uid'])) { - throw new RuntimeException("User ({$user}) not found."); - } - - $uid = (int)$uInfo['uid']; - - // Get gid. - if ($group) { - if (!$gInfo = posix_getgrnam($group)) { - throw new RuntimeException("Group {$group} not exists", -300); - } - - $gid = (int)$gInfo['gid']; - } else { - $gid = (int)$uInfo['gid']; - } - - if (!posix_initgroups($uInfo['name'], $gid)) { - throw new RuntimeException("The user [{$user}] is not in the user group ID [GID:{$gid}]", -300); - } - - posix_setgid($gid); - - if (posix_geteuid() !== $gid) { - throw new RuntimeException("Unable to change group to {$user} (UID: {$gid}).", -300); - } - - posix_setuid($uid); - - if (posix_geteuid() !== $uid) { - throw new RuntimeException("Unable to change user to {$user} (UID: {$uid}).", -300); - } - } - - /** - * @return bool - */ - public static function hasPcntl(): bool - { - return !Sys::isWindows() && function_exists('pcntl_fork'); - } - - /** - * @return bool - */ - public static function hasPosix(): bool - { - return !Sys::isWindows() && function_exists('posix_kill'); - } -} diff --git a/src/sys-utils/src/Signal.php b/src/sys-utils/src/Signal.php deleted file mode 100644 index 64be458..0000000 --- a/src/sys-utils/src/Signal.php +++ /dev/null @@ -1,58 +0,0 @@ -> \"$logfile\" 2>&1", $dummy, $retVal); - - if ($retVal !== 0) { - throw new RuntimeException("command exited with status '$retVal'."); - } - - return $dummy; - } - - /** - * run a command. it is support windows - * - * @param string $command - * @param string|null $cwd - * - * @return array - * @throws RuntimeException - */ - public static function run(string $command, string $cwd = null): array - { - $descriptors = [ - 0 => ['pipe', 'r'], // stdin - read channel - 1 => ['pipe', 'w'], // stdout - write channel - 2 => ['pipe', 'w'], // stdout - error channel - 3 => ['pipe', 'r'], // stdin - This is the pipe we can feed the password into - ]; - - $process = proc_open($command, $descriptors, $pipes, $cwd); - - if (!is_resource($process)) { - throw new RuntimeException("Can't open resource with proc_open."); - } - - // Nothing to push to input. - fclose($pipes[0]); - - $output = stream_get_contents($pipes[1]); - fclose($pipes[1]); - - $error = stream_get_contents($pipes[2]); - fclose($pipes[2]); - - // TODO: Write passphrase in pipes[3]. - fclose($pipes[3]); - - // Close all pipes before proc_close! $code === 0 is success. - $code = proc_close($process); - - return [$code, $output, $error]; - } - - /** - * Method to execute a command in the sys - * Uses : - * 1. system - * 2. passthru - * 3. exec - * 4. shell_exec - * - * @param string $command - * @param bool $returnStatus - * @param string|null $cwd - * - * @return array|string - */ - public static function execute(string $command, bool $returnStatus = true, string $cwd = null) - { - $exitStatus = 1; - - if ($cwd) { - chdir($cwd); - } - - // system - if (function_exists('system')) { - ob_start(); - system($command, $exitStatus); - $output = ob_get_contents(); - ob_end_clean(); - - // passthru - } elseif (function_exists('passthru')) { - ob_start(); - passthru($command, $exitStatus); - $output = ob_get_contents(); - ob_end_clean(); - //exec - } elseif (function_exists('exec')) { - exec($command, $output, $exitStatus); - $output = implode("\n", $output); - - //shell_exec - } elseif (function_exists('shell_exec')) { - $output = shell_exec($command); - } else { - $output = 'Command execution not possible on this system'; - $exitStatus = 0; - } - - if ($returnStatus) { - return [ - 'output' => trim($output), - 'status' => $exitStatus - ]; - } - - return trim($output); - } - - /** - * run a command in background - * - * @param string $cmd - */ - public static function bgExec(string $cmd): void - { - self::execInBackground($cmd); - } - - /** - * run a command in background - * - * @param string $cmd - */ - public static function execInBackground(string $cmd): void - { - if (self::isWindows()) { - pclose(popen('start /B ' . $cmd, 'r')); - } else { - exec($cmd . ' > /dev/null &'); - } - } - - /** - * Get unix user of current process. - * - * @return array - */ - public static function getCurrentUser(): array - { - return posix_getpwuid(posix_getuid()); - } - - /** - * @return string - */ - public static function tempDir(): string - { - return self::getTempDir(); - } - - /** - * @return string - */ - public static function getTempDir(): string - { - // @codeCoverageIgnoreStart - if (function_exists('sys_get_temp_dir')) { - $tmp = sys_get_temp_dir(); - } elseif (!empty($_SERVER['TMP'])) { - $tmp = $_SERVER['TMP']; - } elseif (!empty($_SERVER['TEMP'])) { - $tmp = $_SERVER['TEMP']; - } elseif (!empty($_SERVER['TMPDIR'])) { - $tmp = $_SERVER['TMPDIR']; - } else { - $tmp = getcwd(); - } - // @codeCoverageIgnoreEnd - - return $tmp; - } - - /** - * get bash is available - * - * @return bool - */ - public static function shIsAvailable(): bool - { - // $checkCmd = "/usr/bin/env bash -c 'echo OK'"; - // $shell = 'echo $0'; - $checkCmd = "sh -c 'echo OK'"; - - return self::execute($checkCmd, false) === 'OK'; - } - - /** - * get bash is available - * - * @return bool - */ - public static function bashIsAvailable(): bool - { - // $checkCmd = "/usr/bin/env bash -c 'echo OK'"; - // $shell = 'echo $0'; - $checkCmd = "bash -c 'echo OK'"; - - return self::execute($checkCmd, false) === 'OK'; - } - - /** - * @return string - */ - public static function getOutsideIP(): string - { - [$code, $output] = self::run('ip addr | grep eth0'); - - if ($code === 0 && $output && preg_match('#inet (.*)\/#', $output, $ms)) { - return $ms[1]; - } - - return 'unknown'; - } - - /** - * get screen size - * - * ```php - * list($width, $height) = Sys::getScreenSize(); - * ``` - * - * @from Yii2 - * - * @param boolean $refresh whether to force checking and not re-use cached size value. - * This is useful to detect changing window size while the application is running but may - * not get up to date values on every terminal. - * - * @return array|boolean An array of ($width, $height) or false when it was not able to determine size. - */ - public static function getScreenSize(bool $refresh = false) - { - static $size; - if ($size !== null && !$refresh) { - return $size; - } - - if (self::shIsAvailable()) { - // try stty if available - $stty = []; - - if (exec('stty -a 2>&1', $stty) && preg_match('/rows\s+(\d+);\s*columns\s+(\d+);/mi', implode(' ', $stty), - $matches) - ) { - return ($size = [$matches[2], $matches[1]]); - } - - // fallback to tput, which may not be updated on terminal resize - if (($width = (int)exec('tput cols 2>&1')) > 0 && ($height = (int)exec('tput lines 2>&1')) > 0) { - return ($size = [$width, $height]); - } - - // fallback to ENV variables, which may not be updated on terminal resize - if (($width = (int)getenv('COLUMNS')) > 0 && ($height = (int)getenv('LINES')) > 0) { - return ($size = [$width, $height]); - } - } - - if (self::isWindows()) { - $output = []; - exec('mode con', $output); - - if (isset($output[1]) && strpos($output[1], 'CON') !== false) { - return ($size = [ - (int)preg_replace('~\D~', '', $output[3]), - (int)preg_replace('~\D~', '', $output[4]) - ]); - } - } - - return ($size = false); - } - - /** - * @param string $program - * - * @return int|string - */ - public static function getCpuUsage(string $program) - { - if (!$program) { - return -1; - } - - $info = exec('ps aux | grep ' . $program . ' | grep -v grep | grep -v su | awk {"print $3"}'); - - return $info; - } - - /** - * @param string $program - * - * @return int|string - */ - public static function getMemUsage(string $program) - { - if (!$program) { - return -1; - } - - $info = exec('ps aux | grep ' . $program . ' | grep -v grep | grep -v su | awk {"print $4"}'); - - return $info; - } -} diff --git a/src/sys-utils/src/SysEnv.php b/src/sys-utils/src/SysEnv.php deleted file mode 100644 index 8db3479..0000000 --- a/src/sys-utils/src/SysEnv.php +++ /dev/null @@ -1,145 +0,0 @@ -