diff --git a/.make/project/app/phpinsights.Makefile b/.make/project/app/phpinsights.Makefile index 9a531ccd..1dea2946 100644 --- a/.make/project/app/phpinsights.Makefile +++ b/.make/project/app/phpinsights.Makefile @@ -10,10 +10,15 @@ a_phpinsights_run: ## Run PHP Insights a_phpinsights_analyse: ## Run PHP Insights Analysis @${_ECHO} "\n${_C_SELECT} ${PROJECT_NAME} ${_C_STOP} ${_C_INFO}PHP Insights analyze...${_C_STOP}\n"; - ${_DC_EXEC} ${APP_CONTAINER} ${PHPINSIGHTS_BINARY} analyse src + ${_DC_EXEC} ${APP_CONTAINER} ${PHPINSIGHTS_BINARY} analyse + @${_ECHO}; + +a_phpinsights_analyse_verbose: ## Run PHP Insights Analysis Verbose Mode + @${_ECHO} "\n${_C_SELECT} ${PROJECT_NAME} ${_C_STOP} ${_C_INFO}PHP Insights analyze...${_C_STOP}\n"; + ${_DC_EXEC} ${APP_CONTAINER} ${PHPINSIGHTS_BINARY} analyse -vvv @${_ECHO}; a_phpinsights_summary: ## Run PHP Insights summary @${_ECHO} "\n${_C_SELECT} ${PROJECT_NAME} ${_C_STOP} ${_C_INFO}PHP Insights summary...${_C_STOP}\n"; - @${_DC_EXEC} ${APP_CONTAINER} ${PHPINSIGHTS_BINARY} --summary analyse src + @${_DC_EXEC} ${APP_CONTAINER} ${PHPINSIGHTS_BINARY} --summary analyse @${_ECHO}; diff --git a/.make/project/app/tests.Makefile b/.make/project/app/tests.Makefile index 38a29eff..95859db8 100644 --- a/.make/project/app/tests.Makefile +++ b/.make/project/app/tests.Makefile @@ -17,7 +17,7 @@ test_coverage: ## Run tests with coverage test_path_coverage: ## Run tests including path coverage @${_ECHO} "\n${_C_SELECT} ${PROJECT_NAME} ${_C_STOP} ${_C_INFO}Coverage tests...${_C_STOP}\n"; @${_ECHO} "${_C_COMMENT} ...testing ${_C_STOP}\n"; - ${_DC_EXEC} -e XDEBUG_MODE=coverage ${APP_CONTAINER} vendor/bin/phpunit --configuration phpunit.coverage.xml --coverage-text --path-coverage + ${_DC_EXEC} -e XDEBUG_MODE=coverage ${APP_CONTAINER} vendor/bin/phpunit --configuration phpunit.coverage.path.xml --coverage-text --path-coverage @${_ECHO_BG_GREEN}; test_dox: ## Run tests with testdox diff --git a/.make/var.Makefile b/.make/var.Makefile index 34ccbf09..03ce8e72 100644 --- a/.make/var.Makefile +++ b/.make/var.Makefile @@ -20,7 +20,6 @@ PROJECT_SHORT_TITLE=🏵️ # ------------------------------------------------------------------------------ # Your variables here -TEST_REPETITION=10 APP_CONTAINER=app COMPOSER_CONTAINER=${APP_CONTAINER} # COMPOSER_CONTAINER=composer diff --git a/.tools/.deptrac/module/root.yaml b/.tools/.deptrac/module/root.yaml index 00beac6d..114ca288 100644 --- a/.tools/.deptrac/module/root.yaml +++ b/.tools/.deptrac/module/root.yaml @@ -2,10 +2,12 @@ deptrac: layers: - name: Root collectors: + - type: className + value: '%namespace%\\Root\\.*' - type: bool must: - type: className value: '%namespace%\\.*' must_not: - type: className - value: '%namespace%\\.*\\.*' \ No newline at end of file + value: '%namespace%\\.*\\.*' diff --git a/.tools/.report/.build/.gitignore b/.tools/.report/.build/.gitignore index 7c26da6c..218bbb9b 100644 --- a/.tools/.report/.build/.gitignore +++ b/.tools/.report/.build/.gitignore @@ -2,3 +2,4 @@ *.* !.gitignore !coverage.txt +!coverage.path.txt diff --git a/.tools/.report/.build/coverage.txt b/.tools/.report/.build/coverage.txt index bcf0f5a1..9c3bfdea 100644 --- a/.tools/.report/.build/coverage.txt +++ b/.tools/.report/.build/coverage.txt @@ -1,7 +1,7 @@ -Code Coverage Report Summary: - Classes: 99.44% (179/180) - Methods: 99.39% (656/660) - Lines: 99.89% (3545/3549) +Code Coverage Report Summary: + Classes: 100.00% (213/213) + Methods: 100.00% (769/769) + Lines: 100.00% (3806/3806) diff --git a/.tools/.report/.deptrac/.deptrac.report b/.tools/.report/.deptrac/.deptrac.report index 8d4bf3b2..84dc3d7a 100644 --- a/.tools/.report/.deptrac/.deptrac.report +++ b/.tools/.report/.deptrac/.deptrac.report @@ -5,7 +5,7 @@ Violations 0 Skipped violations 0 Uncovered 177 - Allowed 2322 + Allowed 2775 Warnings 0 Errors 0 -------------------- ------ diff --git a/.tools/.report/.phploc/.src.phploc.report b/.tools/.report/.phploc/.src.phploc.report index b8e567f3..30952f4a 100644 --- a/.tools/.report/.phploc/.src.phploc.report +++ b/.tools/.report/.phploc/.src.phploc.report @@ -1,30 +1,30 @@ phploc 7.0.2 by Sebastian Bergmann. -Directories 70 -Files 360 +Directories 77 +Files 427 Size - Lines of Code (LOC) 12285 - Comment Lines of Code (CLOC) 687 (5.59%) - Non-Comment Lines of Code (NCLOC) 11598 (94.41%) - Logical Lines of Code (LLOC) 1332 (10.84%) - Classes 1232 (92.49%) + Lines of Code (LOC) 14094 + Comment Lines of Code (CLOC) 804 (5.70%) + Non-Comment Lines of Code (NCLOC) 13290 (94.30%) + Logical Lines of Code (LLOC) 1502 (10.66%) + Classes 1401 (93.28%) Average Class Length 3 Minimum Class Length 0 - Maximum Class Length 30 + Maximum Class Length 27 Average Method Length 1 Minimum Method Length 0 Maximum Method Length 10 Average Methods Per Class 2 Minimum Methods Per Class 0 Maximum Methods Per Class 12 - Functions 40 (3.00%) + Functions 38 (2.53%) Average Function Length 1 - Not in classes or functions 60 (4.50%) + Not in classes or functions 63 (4.19%) Cyclomatic Complexity - Average Complexity per LLOC 0.33 - Average Complexity per Class 2.13 + Average Complexity per LLOC 0.34 + Average Complexity per Class 2.11 Minimum Class Complexity 1.00 Maximum Class Complexity 21.00 Average Complexity per Method 1.52 @@ -36,35 +36,35 @@ Dependencies Global Constants 0 (0.00%) Global Variables 0 (0.00%) Super-Global Variables 0 (0.00%) - Attribute Accesses 576 - Non-Static 557 (96.70%) - Static 19 (3.30%) - Method Calls 692 - Non-Static 604 (87.28%) - Static 88 (12.72%) + Attribute Accesses 658 + Non-Static 642 (97.57%) + Static 16 (2.43%) + Method Calls 821 + Non-Static 739 (90.01%) + Static 82 (9.99%) Structure - Namespaces 70 - Interfaces 165 + Namespaces 77 + Interfaces 196 Traits 0 - Classes 176 - Abstract Classes 13 (7.39%) - Concrete Classes 163 (92.61%) - Final Classes 112 (68.71%) - Non-Final Classes 51 (31.29%) - Methods 863 + Classes 210 + Abstract Classes 14 (6.67%) + Concrete Classes 196 (93.33%) + Final Classes 73 (37.24%) + Non-Final Classes 123 (62.76%) + Methods 1011 Scope - Non-Static Methods 789 (91.43%) - Static Methods 74 (8.57%) + Non-Static Methods 946 (93.57%) + Static Methods 65 (6.43%) Visibility - Public Methods 698 (80.88%) - Protected Methods 61 (7.07%) - Private Methods 104 (12.05%) - Functions 40 - Named Functions 10 (25.00%) - Anonymous Functions 30 (75.00%) - Constants 13 + Public Methods 802 (79.33%) + Protected Methods 67 (6.63%) + Private Methods 142 (14.05%) + Functions 37 + Named Functions 7 (18.92%) + Anonymous Functions 30 (81.08%) + Constants 11 Global Constants 0 (0.00%) - Class Constants 13 (100.00%) - Public Constants 4 (30.77%) - Non-Public Constants 9 (69.23%) + Class Constants 11 (100.00%) + Public Constants 5 (45.45%) + Non-Public Constants 6 (54.55%) diff --git a/.tools/.report/.phploc/.test.phploc.report b/.tools/.report/.phploc/.test.phploc.report index 7fca646b..867b5cc5 100644 --- a/.tools/.report/.phploc/.test.phploc.report +++ b/.tools/.report/.phploc/.test.phploc.report @@ -1,15 +1,15 @@ phploc 7.0.2 by Sebastian Bergmann. -Directories 67 -Files 242 +Directories 74 +Files 280 Size - Lines of Code (LOC) 27421 - Comment Lines of Code (CLOC) 380 (1.39%) - Non-Comment Lines of Code (NCLOC) 27041 (98.61%) - Logical Lines of Code (LLOC) 6337 (23.11%) - Classes 6330 (99.89%) - Average Class Length 26 + Lines of Code (LOC) 32629 + Comment Lines of Code (CLOC) 457 (1.40%) + Non-Comment Lines of Code (NCLOC) 32172 (98.60%) + Logical Lines of Code (LLOC) 7256 (22.24%) + Classes 7248 (99.89%) + Average Class Length 25 Minimum Class Length 0 Maximum Class Length 871 Average Method Length 3 @@ -17,54 +17,54 @@ Size Maximum Method Length 374 Average Methods Per Class 6 Minimum Methods Per Class 0 - Maximum Methods Per Class 48 + Maximum Methods Per Class 21 Functions 4 (0.06%) Average Function Length 0 - Not in classes or functions 3 (0.05%) + Not in classes or functions 4 (0.06%) Cyclomatic Complexity Average Complexity per LLOC 0.06 - Average Complexity per Class 2.58 + Average Complexity per Class 2.63 Minimum Class Complexity 1.00 - Maximum Class Complexity 14.00 + Maximum Class Complexity 16.00 Average Complexity per Method 1.25 Minimum Method Complexity 1.00 - Maximum Method Complexity 7.00 + Maximum Method Complexity 9.00 Dependencies Global Accesses 0 Global Constants 0 (0.00%) Global Variables 0 (0.00%) Super-Global Variables 0 (0.00%) - Attribute Accesses 22 - Non-Static 15 (68.18%) - Static 7 (31.82%) - Method Calls 8409 - Non-Static 5501 (65.42%) - Static 2908 (34.58%) + Attribute Accesses 28 + Non-Static 23 (82.14%) + Static 5 (17.86%) + Method Calls 9974 + Non-Static 6688 (67.05%) + Static 3286 (32.95%) Structure - Namespaces 67 - Interfaces 0 + Namespaces 74 + Interfaces 1 Traits 1 - Classes 240 - Abstract Classes 5 (2.08%) - Concrete Classes 235 (97.92%) - Final Classes 218 (92.77%) - Non-Final Classes 17 (7.23%) - Methods 1545 + Classes 277 + Abstract Classes 4 (1.44%) + Concrete Classes 273 (98.56%) + Final Classes 255 (93.41%) + Non-Final Classes 18 (6.59%) + Methods 1837 Scope - Non-Static Methods 1445 (93.53%) - Static Methods 100 (6.47%) + Non-Static Methods 1734 (94.39%) + Static Methods 103 (5.61%) Visibility - Public Methods 994 (64.34%) - Protected Methods 311 (20.13%) - Private Methods 240 (15.53%) - Functions 96 - Named Functions 3 (3.12%) - Anonymous Functions 93 (96.88%) - Constants 72 + Public Methods 1113 (60.59%) + Protected Methods 352 (19.16%) + Private Methods 372 (20.25%) + Functions 97 + Named Functions 2 (2.06%) + Anonymous Functions 95 (97.94%) + Constants 75 Global Constants 0 (0.00%) - Class Constants 72 (100.00%) - Public Constants 0 (0.00%) - Non-Public Constants 72 (100.00%) + Class Constants 75 (100.00%) + Public Constants 1 (1.33%) + Non-Public Constants 74 (98.67%) diff --git a/.tools/.report/.psalm/.psalm.report.txt b/.tools/.report/.psalm/.psalm.report.txt index fe7f7888..afcf69a9 100644 --- a/.tools/.report/.psalm/.psalm.report.txt +++ b/.tools/.report/.psalm/.psalm.report.txt @@ -1,64 +1,5 @@ -/app/src/Spinner/Container/Container.php:99:38:error - MixedInferredReturnType: Could not verify return type 'T' for AlecRabbit\Spinner\Container\Container::get -/app/src/Spinner/Container/Container.php:102:20:error - MixedReturnStatement: Could not infer a return type -/app/src/Spinner/Container/Container.php:122:16:error - MixedReturnStatement: Could not infer a return type -/app/src/Spinner/Container/Container.php:149:49:error - ArgumentTypeCoercion: Argument 1 of AlecRabbit\Spinner\Container\Contract\IServiceSpawner::spawn expects callable|class-string|object, but parent type callable|object|string provided -/app/src/Spinner/Container/Contract/IContainer.php:25:32:error - MoreSpecificImplementedParamType: Argument 1 of AlecRabbit\Spinner\Container\Contract\IContainer::get has the more specific type 'class-string', expecting 'string' as defined by Psr\Container\ContainerInterface::get -/app/src/Spinner/Container/ServiceSpawner.php:96:41:error - ArgumentTypeCoercion: Argument 1 of AlecRabbit\Spinner\Container\ServiceSpawner::needsService expects ReflectionIntersectionType|ReflectionNamedType|ReflectionUnionType, but parent type ReflectionType provided -/app/src/Spinner/Container/ServiceSpawner.php:97:73:error - MixedArgument: Argument 1 of AlecRabbit\Spinner\Container\ServiceSpawner::getServiceFromContainer cannot be mixed, expecting string -/app/src/Spinner/Container/ServiceSpawner.php:97:80:error - UndefinedMethod: Method ReflectionType::getName does not exist -/app/src/Spinner/Container/ServiceSpawner.php:100:20:error - MixedMethodCall: Cannot call constructor on an unknown class -/app/src/Spinner/Container/ServiceSpawner.php:104:20:error - MixedMethodCall: Cannot call constructor on an unknown class -/app/src/Spinner/Core/Builder/DriverBuilder.php:58:25:error - PossiblyNullArgument: Argument 1 of AlecRabbit\Spinner\Core\Driver::__construct cannot be null, possibly null value provided -/app/src/Spinner/Core/Builder/DriverBuilder.php:59:24:error - PossiblyNullArgument: Argument 2 of AlecRabbit\Spinner\Core\Driver::__construct cannot be null, possibly null value provided -/app/src/Spinner/Core/Builder/DriverBuilder.php:60:34:error - PossiblyNullArgument: Argument 3 of AlecRabbit\Spinner\Core\Driver::__construct cannot be null, possibly null value provided -/app/src/Spinner/Core/Builder/DriverOutputBuilder.php:24:21:error - PossiblyNullArgument: Argument 1 of AlecRabbit\Spinner\Core\Output\DriverOutput::__construct cannot be null, possibly null value provided -/app/src/Spinner/Core/Builder/DriverOutputBuilder.php:25:21:error - PossiblyNullArgument: Argument 2 of AlecRabbit\Spinner\Core\Output\DriverOutput::__construct cannot be null, possibly null value provided -/app/src/Spinner/Core/Builder/IntegerNormalizerBuilder.php:22:13:error - PossiblyNullArgument: Argument 1 of AlecRabbit\Spinner\Core\IntegerNormalizer::__construct cannot be null, possibly null value provided -/app/src/Spinner/Core/Builder/IntegerNormalizerBuilder.php:23:13:error - PossiblyNullArgument: Argument 2 of AlecRabbit\Spinner\Core\IntegerNormalizer::__construct cannot be null, possibly null value provided -/app/src/Spinner/Core/Builder/TimerBuilder.php:23:27:error - PossiblyNullArgument: Argument 1 of AlecRabbit\Spinner\Core\Timer::__construct cannot be null, possibly null value provided -/app/src/Spinner/Core/Builder/TimerBuilder.php:24:19:error - PossiblyNullArgument: Argument 2 of AlecRabbit\Spinner\Core\Timer::__construct cannot be null, possibly null value provided -/app/src/Spinner/Core/Config/Config.php:22:33:error - MixedPropertyTypeCoercion: $this->configElements expects 'ArrayObject, AlecRabbit\Spinner\Core\Config\Contract\IConfigElement>', parent type `ArrayObject` provided -/app/src/Spinner/Core/Config/Config.php:26:43:error - ParamNameMismatch: Argument 1 of AlecRabbit\Spinner\Core\Config\Config::set has wrong name $configElements, expecting $settingsElements as defined by AlecRabbit\Spinner\Core\Config\Contract\IConfig::set -/app/src/Spinner/Core/Config/Config.php:54:38:error - InvalidReturnType: The declared return type 'T:fn-alecrabbit\spinner\core\config\contract\iconfig::get as AlecRabbit\Spinner\Core\Config\Contract\IConfigElement' for AlecRabbit\Spinner\Core\Config\Config::get is incorrect, got 'AlecRabbit\Spinner\Core\Config\Contract\IConfigElement' -/app/src/Spinner/Core/Config/Config.php:62:16:error - InvalidReturnStatement: The inferred type 'AlecRabbit\Spinner\Core\Config\Contract\IConfigElement' does not match the declared return type 'T:fn-alecrabbit\spinner\core\config\contract\iconfig::get as AlecRabbit\Spinner\Core\Config\Contract\IConfigElement' for AlecRabbit\Spinner\Core\Config\Config::get -/app/src/Spinner/Core/Config/Solver/A/ASolver.php:26:22:error - InvalidReturnType: The declared return type '(T:fn-alecrabbit\spinner\core\config\solver\a\asolver::extractsettingselement as AlecRabbit\Spinner\Core\Settings\Contract\ISettingsElement)|null' for AlecRabbit\Spinner\Core\Config\Solver\A\ASolver::extractSettingsElement is incorrect, got 'AlecRabbit\Spinner\Core\Settings\Contract\ISettingsElement|null' -/app/src/Spinner/Core/Config/Solver/A/ASolver.php:30:16:error - InvalidReturnStatement: The inferred type 'AlecRabbit\Spinner\Core\Settings\Contract\ISettingsElement|null' does not match the declared return type '(T:fn-alecrabbit\spinner\core\config\solver\a\asolver::extractsettingselement as AlecRabbit\Spinner\Core\Settings\Contract\ISettingsElement)|null' for AlecRabbit\Spinner\Core\Config\Solver\A\ASolver::extractSettingsElement -/app/src/Spinner/Core/Config/Solver/StylingMethodModeSolver.php:45:56:error - TypeDoesNotContainNull: AlecRabbit\Spinner\Contract\Mode\StylingMethodMode does not contain null -/app/src/Spinner/Core/Config/Solver/StylingMethodModeSolver.php:45:56:error - RedundantCondition: Type AlecRabbit\Spinner\Contract\Mode\StylingMethodMode for $detectedMode is never null -/app/src/Spinner/Core/Contract/IWeakMap.php:11:28:error - MissingTemplateParam: AlecRabbit\Spinner\Core\Contract\IWeakMap has missing template params when extending ArrayAccess, expecting 2 -/app/src/Spinner/Core/Contract/IWeakMap.php:11:52:error - MissingTemplateParam: AlecRabbit\Spinner\Core\Contract\IWeakMap has missing template params when extending IteratorAggregate, expecting 2 -/app/src/Spinner/Core/Contract/IWidgetContextToIntervalMap.php:12:47:error - MissingTemplateParam: AlecRabbit\Spinner\Core\Contract\IWidgetContextToIntervalMap has missing template params when extending ArrayAccess, expecting 2 -/app/src/Spinner/Core/Contract/IWidgetContextToIntervalMap.php:12:71:error - MissingTemplateParam: AlecRabbit\Spinner\Core\Contract\IWidgetContextToIntervalMap has missing template params when extending IteratorAggregate, expecting 2 -/app/src/Spinner/Core/Driver.php:16:29:error - PropertyNotSetInConstructor: Property AlecRabbit\Spinner\Core\Driver::$state is not defined in constructor of AlecRabbit\Spinner\Core\Driver or in any methods called in the constructor -/app/src/Spinner/Core/FrameCollection.php:69:34:error - InvalidNullableReturnType: The declared return type 'int' for AlecRabbit\Spinner\Core\FrameCollection::lastIndex is not nullable, but 'int|null' contains null -/app/src/Spinner/Core/FrameCollection.php:71:16:error - NullableReturnStatement: The declared return type 'int' for AlecRabbit\Spinner\Core\FrameCollection::lastIndex is not nullable, but the function returns 'int|null' -/app/src/Spinner/Core/Palette/Rainbow.php:83:31:error - MixedAssignment: Unable to determine the type that $item is being assigned to -/app/src/Spinner/Core/Palette/Rainbow.php:84:66:error - MixedArgument: Argument 2 of sprintf cannot be mixed, expecting float|int|string -/app/src/Spinner/Core/Palette/Rainbow.php:129:35:error - MixedAssignment: Unable to determine the type that $item is being assigned to -/app/src/Spinner/Core/Palette/Rainbow.php:130:17:error - MixedAssignment: Unable to determine the type of this assignment -/app/src/Spinner/Core/Palette/Rainbow.php:135:31:error - MixedAssignment: Unable to determine the type that $item is being assigned to -/app/src/Spinner/Core/Palette/Rainbow.php:136:66:error - MixedArgument: Argument 2 of sprintf cannot be mixed, expecting float|int|string -/app/src/Spinner/Core/Revolver/FrameRevolverBuilder.php:26:17:error - PossiblyNullArgument: Argument 1 of AlecRabbit\Spinner\Core\Revolver\FrameCollectionRevolver::__construct cannot be null, possibly null value provided -/app/src/Spinner/Core/Revolver/FrameRevolverBuilder.php:27:17:error - PossiblyNullArgument: Argument 2 of AlecRabbit\Spinner\Core\Revolver\FrameCollectionRevolver::__construct cannot be null, possibly null value provided -/app/src/Spinner/Core/Revolver/FrameRevolverBuilder.php:28:17:error - PossiblyNullArgument: Argument 3 of AlecRabbit\Spinner\Core\Revolver\FrameCollectionRevolver::__construct cannot be null, possibly null value provided -/app/src/Spinner/Core/Settings/Detector/ColorSupportDetector.php:21:40:error - MoreSpecificReturnType: The declared return type 'AlecRabbit\Spinner\Contract\Option\StylingMethodOption' for AlecRabbit\Spinner\Core\Settings\Detector\ColorSupportDetector::getSupportValue is more specific than the inferred return type 'enum(AlecRabbit\Spinner\Contract\Option\StylingMethodOption::NONE)|object' -/app/src/Spinner/Core/Settings/Detector/ColorSupportDetector.php:28:24:error - LessSpecificReturnStatement: The type 'object' is more general than the declared return type 'AlecRabbit\Spinner\Contract\Option\StylingMethodOption' for AlecRabbit\Spinner\Core\Settings\Detector\ColorSupportDetector::getSupportValue -/app/src/Spinner/Core/Settings/Detector/LoopSupportDetector.php:19:31:error - PossiblyNullArgument: Argument 1 of is_subclass_of cannot be null, possibly null value provided -/app/src/Spinner/Core/Settings/Detector/SignalHandlingSupportDetector.php:21:40:error - MoreSpecificReturnType: The declared return type 'AlecRabbit\Spinner\Contract\Option\SignalHandlingOption' for AlecRabbit\Spinner\Core\Settings\Detector\SignalHandlingSupportDetector::getSupportValue is more specific than the inferred return type 'enum(AlecRabbit\Spinner\Contract\Option\SignalHandlingOption::DISABLED)|object' -/app/src/Spinner/Core/Settings/Detector/SignalHandlingSupportDetector.php:28:24:error - LessSpecificReturnStatement: The type 'object' is more general than the declared return type 'AlecRabbit\Spinner\Contract\Option\SignalHandlingOption' for AlecRabbit\Spinner\Core\Settings\Detector\SignalHandlingSupportDetector::getSupportValue -/app/src/Spinner/Core/Settings/Settings.php:18:15:error - InvalidParamDefault: Default value type ArrayObject for argument 1 of method AlecRabbit\Spinner\Core\Settings\Settings::__construct does not match the given type ArrayObject, AlecRabbit\Spinner\Core\Settings\Contract\ISettingsElement> -/app/src/Spinner/Core/Timer.php:40:17:error - TypeDoesNotContainNull: ReflectionType does not contain null -/app/src/Spinner/Core/Timer.php:40:17:error - RedundantCondition: Type ReflectionType for $returnType is never null -/app/src/Spinner/Core/Timer.php:40:31:error - UndefinedMethod: Method ReflectionType::getName does not exist -/app/src/Spinner/Core/Timer.php:46:21:error - MixedArgument: Argument 3 of sprintf cannot be mixed, expecting float|int|string -/app/src/Spinner/Core/Widget/Builder/WidgetCompositeBuilder.php:33:27:error - PropertyTypeCoercion: $this->revolver expects 'AlecRabbit\Spinner\Core\Widget\Contract\IWidgetRevolver|null', parent type 'AlecRabbit\Spinner\Core\Revolver\Contract\IRevolver' provided -/app/src/Spinner/Core/Widget/Contract/IWidgetCompositeChildrenContainer.php:19:53:error - MissingTemplateParam: AlecRabbit\Spinner\Core\Widget\Contract\IWidgetCompositeChildrenContainer has missing template params when extending IteratorAggregate, expecting 2 -/app/src/Spinner/Core/Widget/Factory/WidgetFactory.php:38:38:error - ArgumentTypeCoercion: Argument 1 of AlecRabbit\Spinner\Core\Widget\Contract\IWidgetBuilder::withWidgetRevolver expects AlecRabbit\Spinner\Core\Widget\Contract\IWidgetRevolver, but parent type AlecRabbit\Spinner\Core\Revolver\Contract\IRevolver provided -/app/src/Spinner/Core/Widget/WidgetComposite.php:31:13:error - ArgumentTypeCoercion: Argument 1 of AlecRabbit\Spinner\Core\Widget\A\AWidget::__construct expects AlecRabbit\Spinner\Core\Widget\Contract\IWidgetRevolver, but parent type AlecRabbit\Spinner\Core\Revolver\Contract\IRevolver provided -/app/src/Spinner/Core/Widget/WidgetCompositeChildrenContainer.php:103:13:error - MixedAssignment: Unable to determine the type that $this->interval is being assigned to -/app/src/Spinner/Core/Widget/WidgetCompositeChildrenContainer.php:103:48:error - MixedMethodCall: Cannot determine the type of $this->interval when calling method smallest -/app/src/Spinner/Core/WidgetContextToIntervalMap.php:27:32:error - MixedAssignment: Unable to determine the type that $key is being assigned to -/app/src/Spinner/Core/WidgetContextToIntervalMap.php:27:40:error - MixedAssignment: Unable to determine the type that $value is being assigned to -/app/src/Spinner/Core/WidgetContextToIntervalMap.php:43:47:error - MixedInferredReturnType: Could not verify return type 'AlecRabbit\Spinner\Contract\IInterval|null' for AlecRabbit\Spinner\Core\WidgetContextToIntervalMap::offsetGet -/app/src/Spinner/Core/WidgetContextToIntervalMap.php:51:16:error - MixedReturnStatement: Could not infer a return type -/app/src/Spinner/definitions.php:221:25:error - ArgumentTypeCoercion: Argument 1 of AlecRabbit\Spinner\Core\Loop\Contract\ILoopCreatorClassExtractor::extract expects Traversable, but parent type Traversable provided +/app/src/Spinner/Core/Config/Solver/A/ASolver.php:27:22:error - InvalidReturnType: The declared return type '(T:fn-alecrabbit\spinner\core\config\solver\a\asolver::extractsettingselement as AlecRabbit\Spinner\Core\Settings\Contract\ISettingsElement)|null' for AlecRabbit\Spinner\Core\Config\Solver\A\ASolver::extractSettingsElement is incorrect, got 'AlecRabbit\Spinner\Core\Settings\Contract\ISettingsElement|null' +/app/src/Spinner/Core/Config/Solver/A/ASolver.php:31:16:error - InvalidReturnStatement: The inferred type 'AlecRabbit\Spinner\Core\Settings\Contract\ISettingsElement|null' does not match the declared return type '(T:fn-alecrabbit\spinner\core\config\solver\a\asolver::extractsettingselement as AlecRabbit\Spinner\Core\Settings\Contract\ISettingsElement)|null' for AlecRabbit\Spinner\Core\Config\Solver\A\ASolver::extractSettingsElement +/app/src/Spinner/Core/WidgetContextToIntervalMap.php:30:36:error - InvalidReturnType: The declared return type 'Traversable' for AlecRabbit\Spinner\Core\WidgetContextToIntervalMap::getIterator is incorrect, got 'Generator' +/app/src/Spinner/Core/WidgetContextToIntervalMap.php:55:22:error - InvalidReturnType: The declared return type 'TValue:AlecRabbit\Spinner\Core\WidgetContextToIntervalMap as AlecRabbit\Spinner\Contract\IInterval|null' for AlecRabbit\Spinner\Core\WidgetContextToIntervalMap::offsetGet is incorrect, got 'AlecRabbit\Spinner\Contract\IInterval|null' +/app/src/Spinner/Core/WidgetContextToIntervalMap.php:68:16:error - InvalidReturnStatement: The inferred type 'AlecRabbit\Spinner\Contract\IInterval|null' does not match the declared return type 'TValue:AlecRabbit\Spinner\Core\WidgetContextToIntervalMap as AlecRabbit\Spinner\Contract\IInterval|null' for AlecRabbit\Spinner\Core\WidgetContextToIntervalMap::offsetGet diff --git a/composer.json b/composer.json index 4375e4ed..84434d6c 100644 --- a/composer.json +++ b/composer.json @@ -46,6 +46,7 @@ "autoload": { "psr-4": { "AlecRabbit\\Benchmark\\": "lib\\Benchmark\\", + "AlecRabbit\\Lib\\": "lib\\Lib\\", "AlecRabbit\\Spinner\\": "src\\Spinner\\" }, "files": [ diff --git a/composer.lock b/composer.lock index f60bb45e..ad059f5b 100644 --- a/composer.lock +++ b/composer.lock @@ -234,16 +234,16 @@ }, { "name": "filp/whoops", - "version": "2.15.3", + "version": "2.15.4", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "c83e88a30524f9360b11f585f71e6b17313b7187" + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/c83e88a30524f9360b11f585f71e6b17313b7187", - "reference": "c83e88a30524f9360b11f585f71e6b17313b7187", + "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546", + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546", "shasum": "" }, "require": { @@ -293,7 +293,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.15.3" + "source": "https://github.com/filp/whoops/tree/2.15.4" }, "funding": [ { @@ -301,7 +301,7 @@ "type": "github" } ], - "time": "2023-07-13T12:00:00+00:00" + "time": "2023-11-03T12:00:00+00:00" }, { "name": "myclabs/deep-copy", @@ -713,16 +713,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.7", + "version": "10.1.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "355324ca4980b8916c18b9db29f3ef484078f26e" + "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/355324ca4980b8916c18b9db29f3ef484078f26e", - "reference": "355324ca4980b8916c18b9db29f3ef484078f26e", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a56a9ab2f680246adcf3db43f38ddf1765774735", + "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735", "shasum": "" }, "require": { @@ -779,7 +779,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.7" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.9" }, "funding": [ { @@ -787,7 +787,7 @@ "type": "github" } ], - "time": "2023-10-04T15:34:17+00:00" + "time": "2023-11-23T12:23:20+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1386,16 +1386,16 @@ }, { "name": "react/event-loop", - "version": "v1.4.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/reactphp/event-loop.git", - "reference": "6e7e587714fff7a83dcc7025aee42ab3b265ae05" + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/6e7e587714fff7a83dcc7025aee42ab3b265ae05", - "reference": "6e7e587714fff7a83dcc7025aee42ab3b265ae05", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", "shasum": "" }, "require": { @@ -1446,7 +1446,7 @@ ], "support": { "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.4.0" + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" }, "funding": [ { @@ -1454,7 +1454,7 @@ "type": "open_collective" } ], - "time": "2023-05-05T10:11:24+00:00" + "time": "2023-11-13T13:48:05+00:00" }, { "name": "react/http", @@ -1550,24 +1550,24 @@ }, { "name": "react/promise", - "version": "v3.0.0", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "c86753c76fd3be465d93b308f18d189f01a22be4" + "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/c86753c76fd3be465d93b308f18d189f01a22be4", - "reference": "c86753c76fd3be465d93b308f18d189f01a22be4", + "url": "https://api.github.com/repos/reactphp/promise/zipball/e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", + "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", "shasum": "" }, "require": { "php": ">=7.1.0" }, "require-dev": { - "phpstan/phpstan": "1.10.20 || 1.4.10", - "phpunit/phpunit": "^9.5 || ^7.5" + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", "autoload": { @@ -1611,7 +1611,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.0.0" + "source": "https://github.com/reactphp/promise/tree/v3.1.0" }, "funding": [ { @@ -1619,7 +1619,7 @@ "type": "open_collective" } ], - "time": "2023-07-11T16:12:49+00:00" + "time": "2023-11-16T16:21:57+00:00" }, { "name": "react/socket", @@ -1781,16 +1781,16 @@ }, { "name": "revolt/event-loop", - "version": "v1.0.4", + "version": "v1.0.5", "source": { "type": "git", "url": "https://github.com/revoltphp/event-loop.git", - "reference": "40292c18e53d9a1b7e6c807f4fda5908552e1ba5" + "reference": "fce6063869513de56f639aa522b2ef2fedbf8d73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/40292c18e53d9a1b7e6c807f4fda5908552e1ba5", - "reference": "40292c18e53d9a1b7e6c807f4fda5908552e1ba5", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/fce6063869513de56f639aa522b2ef2fedbf8d73", + "reference": "fce6063869513de56f639aa522b2ef2fedbf8d73", "shasum": "" }, "require": { @@ -1800,7 +1800,7 @@ "ext-json": "*", "jetbrains/phpstorm-stubs": "^2019.3", "phpunit/phpunit": "^9", - "psalm/phar": "^4.7" + "psalm/phar": "^5.15" }, "type": "library", "extra": { @@ -1847,9 +1847,9 @@ ], "support": { "issues": "https://github.com/revoltphp/event-loop/issues", - "source": "https://github.com/revoltphp/event-loop/tree/v1.0.4" + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.5" }, - "time": "2023-10-22T03:19:00+00:00" + "time": "2023-11-19T14:45:35+00:00" }, { "name": "ringcentral/psr7", @@ -2829,16 +2829,16 @@ }, { "name": "symfony/console", - "version": "v6.3.4", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6" + "reference": "0d14a9f6d04d4ac38a8cea1171f4554e325dae92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/eca495f2ee845130855ddf1cf18460c38966c8b6", - "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6", + "url": "https://api.github.com/repos/symfony/console/zipball/0d14a9f6d04d4ac38a8cea1171f4554e325dae92", + "reference": "0d14a9f6d04d4ac38a8cea1171f4554e325dae92", "shasum": "" }, "require": { @@ -2899,7 +2899,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.3.4" + "source": "https://github.com/symfony/console/tree/v6.3.8" }, "funding": [ { @@ -2915,11 +2915,11 @@ "type": "tidelift" } ], - "time": "2023-08-16T10:10:12+00:00" + "time": "2023-10-31T08:09:35+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", @@ -2966,7 +2966,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -3316,16 +3316,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" + "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b3313c2dbffaf71c8de2934e2ea56ed2291a3838", + "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838", "shasum": "" }, "require": { @@ -3378,7 +3378,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.4.0" }, "funding": [ { @@ -3394,20 +3394,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2023-07-30T20:28:31+00:00" }, { "name": "symfony/string", - "version": "v6.3.5", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339" + "reference": "13880a87790c76ef994c91e87efb96134522577a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/13d76d0fb049051ed12a04bef4f9de8715bea339", - "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339", + "url": "https://api.github.com/repos/symfony/string/zipball/13880a87790c76ef994c91e87efb96134522577a", + "reference": "13880a87790c76ef994c91e87efb96134522577a", "shasum": "" }, "require": { @@ -3464,7 +3464,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.3.5" + "source": "https://github.com/symfony/string/tree/v6.3.8" }, "funding": [ { @@ -3480,20 +3480,20 @@ "type": "tidelift" } ], - "time": "2023-09-18T10:38:32+00:00" + "time": "2023-11-09T08:28:21+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.3.6", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "999ede244507c32b8e43aebaa10e9fce20de7c97" + "reference": "81acabba9046550e89634876ca64bfcd3c06aa0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/999ede244507c32b8e43aebaa10e9fce20de7c97", - "reference": "999ede244507c32b8e43aebaa10e9fce20de7c97", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/81acabba9046550e89634876ca64bfcd3c06aa0a", + "reference": "81acabba9046550e89634876ca64bfcd3c06aa0a", "shasum": "" }, "require": { @@ -3548,7 +3548,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.3.6" + "source": "https://github.com/symfony/var-dumper/tree/v6.3.8" }, "funding": [ { @@ -3564,20 +3564,20 @@ "type": "tidelift" } ], - "time": "2023-10-12T18:45:56+00:00" + "time": "2023-11-08T10:42:36+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -3606,7 +3606,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -3614,7 +3614,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" } ], "aliases": [], diff --git a/doc/info/chart/.sample.md b/doc/.info/chart/.sample.md similarity index 100% rename from doc/info/chart/.sample.md rename to doc/.info/chart/.sample.md diff --git a/doc/info/chart/bootstrap.md b/doc/.info/chart/bootstrap.md similarity index 100% rename from doc/info/chart/bootstrap.md rename to doc/.info/chart/bootstrap.md diff --git a/doc/info/chart/services.md b/doc/.info/chart/services.md similarity index 90% rename from doc/info/chart/services.md rename to doc/.info/chart/services.md index aa07e482..be6b1f5d 100644 --- a/doc/info/chart/services.md +++ b/doc/.info/chart/services.md @@ -73,10 +73,10 @@ classDiagram classDiagram direction TB IDriverFactory ..> IDriverBuilder - IDriverFactory ..> IDriverOutputFactory + IDriverFactory ..> ISequenceStateWriterFactory IDriverFactory ..> ITimerFactory IDriverFactory ..> IDriverSetup - IDriverFactory ..> IDriverSettings + IDriverFactory ..> ILinkerSettings IDriverFactory ..> ISignalHandlingSetupFactory IDriverBuilder ..> IIntervalFactory @@ -85,9 +85,9 @@ classDiagram ISignalHandlingSetupFactory ..> ILoopFactory ISignalHandlingSetupFactory ..> ISignalHandlingSetupBuilder - IDriverOutputFactory ..> IDriverOutputBuilder - IDriverOutputFactory ..> IBufferedOutputSingletonFactory - IDriverOutputFactory ..> IConsoleCursorFactory + ISequenceStateWriterFactory ..> ISequenceStateWriterBuilder + ISequenceStateWriterFactory ..> IBufferedOutputSingletonFactory + ISequenceStateWriterFactory ..> IConsoleCursorFactory ITimerFactory ..> ITimerBuilder diff --git a/doc/info/css/named_colors.md b/doc/.info/css/named_colors.md similarity index 100% rename from doc/info/css/named_colors.md rename to doc/.info/css/named_colors.md diff --git a/doc/info/css/rgb_hsl.md b/doc/.info/css/rgb_hsl.md similarity index 100% rename from doc/info/css/rgb_hsl.md rename to doc/.info/css/rgb_hsl.md diff --git a/doc/api/config.md b/doc/api/config.md index d6c27a20..bcf1fbcc 100644 --- a/doc/api/config.md +++ b/doc/api/config.md @@ -1,51 +1,39 @@ ### Config -Config object is created using `Settings` object values combined with values retrieved from autodetection and defaults. - ```php -// Aux config -$auxConfig = $config->get(IAuxConfig::class); // returns AuxConfig object +// General config +$generalConfig->getRunMethodMode(); // RunMethodMode::ASYNC -$auxConfig->getNormalizerMode(); // NormalizerMode::BALANCED -$auxConfig->getRunMethodMode(); // RunMethodMode::ASYNC +// Normalizer config +$normalizerConfig->getNormalizerMode(); // NormalizerMode::BALANCED // Loop config -$loopConfig = $config->get(ILoopConfig::class); - $loopConfig->getAutoStartMode(); // AutoStartMode::ENABLED $loopConfig->getSignalHandlingMode(); // SignalHandlingMode::ENABLED -# NEW FEATURE // $outputConfig->getSignalHandling(); // iterable <- signal handler(s) +$loopConfig->getSignalHandlersContainer(); // ISignalHandlersContainer (SIGINT handler by default) // Output config -$outputConfig = $config->get(IOutputConfig::class); - $outputConfig->getStylingMethodMode(); // StylingMethodMode::ANSI8 $outputConfig->getCursorVisibilityMode(); // CursorVisibilityMode::HIDDEN $outputConfig->getInitializationMode(); // InitializationMode::ENABLED $outputConfig->getStream(); // STDERR -# NEW FEATURE // $outputConfig->getClearScreenMode(); // ClearScreenMode::DISABLED +// Linker config +$linkerConfig->getLinkerMode(); // LinkerMode::ENABLED // Driver config -$driverConfig = $config->get(IDriverConfig::class); - -$driverConfig->getLinkerMode(); // LinkerMode::ENABLED -$driverConfig->getInitializationMode(); // InitializationMode::ENABLED +$driverConfig->getDriverMessages(); // IDriverMessages(empty strings by default) // Widget config -$widgetConfig = $config->get(IWidgetConfig::class); - -$widgetConfig->getCharPattern(); // IBakedPattern // default: NoStylePattern -$widgetConfig->getStylePattern(); // IBakedPattern // default: NoCharPattern -$widgetConfig->getLeadingSpacer(); // IFrame // default: new CharFrame('', 0) -$widgetConfig->getTrailingSpacer(); // IFrame // default: new CharFrame(' ', 1) +$widgetConfig->getCharPalette(); // default: NoStylePalette +$widgetConfig->getStylePalette(); // default: NoCharPalette +$widgetConfig->getLeadingSpacer(); // default: new CharFrame('', 0) +$widgetConfig->getTrailingSpacer(); // default: new CharFrame(' ', 1) // Root Widget config -$rootWidgetConfig = $config->get(IRootWidgetConfig::class); - -$rootWidgetConfig->getCharPattern(); // IBakedPattern // default: baked Snake -$rootWidgetConfig->getStylePattern(); // IBakedPattern // default: baked Rainbow -$rootWidgetConfig->getLeadingSpacer(); // IFrame // default: new CharFrame('', 0) -$rootWidgetConfig->getTrailingSpacer(); // IFrame // default: new CharFrame(' ', 1) +$rootWidgetConfig->getCharPalette(); // default: Snake +$rootWidgetConfig->getStylePalette(); // default: Rainbow +$rootWidgetConfig->getLeadingSpacer(); // default: new CharFrame('', 0) +$rootWidgetConfig->getTrailingSpacer(); // default: new CharFrame(' ', 1) ``` diff --git a/doc/api/loop.md b/doc/api/loop.md index 3591c0d5..987f4c46 100644 --- a/doc/api/loop.md +++ b/doc/api/loop.md @@ -6,7 +6,7 @@ The event loop availability is detected using loop probes. Loops are probed in t ## Synchronous mode -If no loop is detected, the synchronous mode is used. In this mode, the spinner is displayed only when the `render()` method is called. +If no loop is detected, the synchronous mode is used. In this mode, the spinner is displayed only when driver's `render()` method is called. ```php $spinner = Facade::createSpinner(); @@ -25,6 +25,14 @@ To disable a specific event loop probe, you can use the following code: Probes::unregister(ReactLoopProbe::class); ``` +## How to disable all event loop probes + +To disable all event loop probes, you can use the following code: + +```php +Probes::unregister(ILoopProbe::class); +``` + ## How to add a custom event loop probe To add a custom event loop probe, you need to create a loop adapter by extending the `ALoopAdapter` class, which implements the `ILoop` interface. diff --git a/doc/api/settings.md b/doc/api/settings.md index ae4c5dc1..38fe84cd 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -6,34 +6,67 @@ Settings are used to configure the package. Here is the list of available settin use AlecRabbit\Spinner\Core\Contract\IDriver; use AlecRabbit\Spinner\Core\Loop\Contract\ILoop; -use AlecRabbit\Spinner\Core\Settings\AuxSettings; +use AlecRabbit\Spinner\Core\Settings\GeneralSettings; +use AlecRabbit\Spinner\Core\Settings\NormalizerSettings; use AlecRabbit\Spinner\Core\Settings\DriverSettings; +use AlecRabbit\Spinner\Core\Settings\LinkerSettings; use AlecRabbit\Spinner\Core\Settings\LoopSettings; +use AlecRabbit\Spinner\Core\Settings\Messages; use AlecRabbit\Spinner\Core\Settings\OutputSettings; use AlecRabbit\Spinner\Core\Settings\RootWidgetSettings; use AlecRabbit\Spinner\Core\Settings\WidgetSettings; //... -// Aux settings -$auxSettings = - new AuxSettings( - runMethodOption: RunMethodOption::AUTO, +// Normalizer settings +$normalizerSettings = + new NormalizerSettings( normalizerOption: NormalizerOption::AUTO, ); +// Revolver settings +$revolverSettings = + new RevolverSettings( + tolerance: new Tolerance(5), // defaults to: Tolerance(5) + ); + +// Widget settings +$widgetSettings = + new WidgetSettings( + leadingSpacer: null, // defaults to: CharFrame('', 0) + trailingSpacer: null, // defaults to: CharFrame('', 0) + stylePalette: null, // defaults to: NoStylePalette() + charPalette: null, // defaults to: NoCharPalette() + ); + +// Root Widget settings +$rootWidgetSettings = + new RootWidgetSettings( + leadingSpacer: null, // defaults to: WidgetConfig.leadingSpacer + trailingSpacer: null, // defaults to: WidgetConfig.trailingSpacer + stylePalette: null, // defaults to: new Rainbow() + charPalette: null, // defaults to: new Snake() + ); + +// Changes have no effect after configuration is created: + +// General settings +$generalSettings = + new GeneralSettings( + runMethodOption: RunMethodOption::AUTO, + ); -// Loop settings +// Loop settings $loopSettings = new LoopSettings( autoStartOption: AutoStartOption::AUTO, signalHandlingOption: SignalHandlingOption::AUTO, ); -// Signal handling settings +// Signal handling settings $onInterrupt = new SignalHandlerCreator( signal: SIGINT, // requires pcntl-ext - handlerCreator: new class implements IHandlerCreator { + handlerCreator: new class() implements IHandlerCreator { public function createHandler(IDriver $driver, ILoop $loop): \Closure { return @@ -50,7 +83,7 @@ $signalHandlerSettings = $onInterrupt, ); -// Output settings +// Output settings $outputSettings = new OutputSettings( stylingMethodOption: StylingMethodOption::AUTO, @@ -59,34 +92,22 @@ $outputSettings = stream: null, // defaults to: STDERR ); -// # NEW FEATURE: $outputSettings? ClearScreenOption(ClearScreenOption::AUTO); +// Linker settings +$linkerSettings = + new LinkerSettings( + linkerOption: LinkerOption::AUTO, + ); -// Driver settings +// Driver settings $driverSettings = new DriverSettings( - linkerOption: LinkerOption::AUTO, // todo: check semantics - ); - -// # NEW FEATURE: $driverSettings? FinalMessage(''); // todo: where to put it? -// # NEW FEATURE: $driverSettings? InterruptMessage(''); // todo: where to put it? - -// Widget settings -$widgetSettings = - new WidgetSettings( - leadingSpacer: null, // defaults to: CharFrame('', 0) - trailingSpacer: null, // defaults to: CharFrame('', 0) - stylePalette: null, // defaults to: none - charPalette: null, // defaults to: none + messages: new Messages( + finalMessage: null, // defaults to: '' + interruptionMessage: null, // defaults to: '' + ) ); + -// Root Widget settings -$rootWidgetSettings = - new RootWidgetSettings( - leadingSpacer: null, // defaults to: WidgetConfig.leadingSpacer - trailingSpacer: null, // defaults to: WidgetConfig.trailingSpacer - stylePalette: null, // defaults to: new Rainbow() - charPalette: null, // defaults to: new Snake() - ); ``` ```php @@ -94,11 +115,14 @@ $rootWidgetSettings = $settings = Facade::getSettings(); $settings->set( - $auxSettings, + $generalSettings, + $normalizerSettings, + $driverSettings, $loopSettings, - $outputSettings, $driverSettings, + $linkerSettings, $widgetSettings, + $revolverSettings, $rootWidgetSettings, $signalHandlerSettings, ); @@ -106,5 +130,5 @@ $settings->set( ```php // to get settings -$settings->get(IAuxSettings::class); // returns AuxSettings object or null +$settings->get(IGeneralSettings::class); // returns GeneralSettings object or null ``` diff --git a/doc/development.md b/doc/development.md index 75e1c2ff..2a09377d 100644 --- a/doc/development.md +++ b/doc/development.md @@ -34,7 +34,7 @@ Execute `make install_dam_tool` to install `DAM` tool. ### 3. Initialize the project ```bash -$ make init +make init ``` This command will: diff --git a/doc/features.md b/doc/features.md index 098ef51a..66789d08 100644 --- a/doc/features.md +++ b/doc/features.md @@ -3,20 +3,18 @@ > **Note** See [limitations](limitations.md) -- [x] Extremely flexible +- [x] Extremely flexible (a bit complicated because of this) - [x] Extensible -- [x] "Zero" dependencies +- [x] "Zero" dependencies (requires `psr/container`) - [x] Asynchronous mode - [x] Support of `revolt/event-loop` - [x] Support of `react/event-loop` - [x] Synchronous mode -- [ ] Color mode switch: - - [ ] no color - - [ ] 16 colors - - [ ] 256 colors (default) - - [ ] true color - - [ ] _[Postponed]_ auto-detection -- [ ] _[Postponed]_ Terminal width auto-detection +- [x] Styling mode switch: + - [x] no color + - [x] 16 colors + - [x] 256 colors (default) + - [x] true color - [x] Pipe support ```text $ app.php | grep "something" @@ -26,3 +24,4 @@ $ app.php | grep "something" $ app.php > output.txt ``` - [x] Auto cursor hide/show (can be disabled) +- [x] Event loop auto start (can be disabled for `revolt/event-loop`) diff --git a/doc/how_does_it_work.md b/doc/how_does_it_work.md index 0fde8acc..dcd1f391 100644 --- a/doc/how_does_it_work.md +++ b/doc/how_does_it_work.md @@ -13,13 +13,16 @@ output through its `render()` method. If no spinner is registered with the drive the output. ```php -$spinner = Facade::createSpinner(attach: false); +$spinnerSettings = new SpinnerSettings(autoAttach: false); + +$spinner = Facade::createSpinner($spinnerSettings); + $driver = Facade::getDriver(); $driver->add($spinner); ``` -If an event-loop is available, the driver will be linked to the loop using `IDriverLinker`. Thus, `render()` method will +If an event-loop is available, the driver will be linked to the loop using `IDriverLinker` implementation. Thus, `render()` method will be called automatically. ```php @@ -34,40 +37,3 @@ If no loop is available, the `render()` method must be called manually. ```php $driver->render(); ``` ---- - -(AI-generated from notes and code) - -`Driver` is the central element of the package. Its method `render()` must be called to render the current frame and -send it to the output. In the presence of an event-loop, the driver is linked to the loop using `IDriverLinker`. In the -absence of a loop, the `render()` method must be called manually. `Driver` has an initial interval of 900 seconds. If -no spinner is registered in the driver, nothing is sent to the output. - -In the presence of an event-loop, the driver is linked to the loop using `IDriverLinker`. This ensures that the driver's -method `render()` is called with the event-loop timer and that the frames are rendered at the correct time. - -If no spinner is registered in the driver, the driver will not send anything to the output. This is because the spinner -is the object that provides the driver with the data to render. If there is no spinner, the driver will not have any -data to render. - ---- - -The initial interval for the driver is set to 900 seconds. - -If an event-loop is available, the driver is linked to the loop using `IDriverLinker`. If no loop is available, the -`render()` method must be called manually. - -The Driver class is implemented in PHP and contains a range of interfaces and classes to support the spinner system. -The class accepts an observer object and implements the `IDriver` interface. It also makes use of other interfaces -such as `ISpinner`, `ITimer`, and `IInterval`. - -The `add()` method adds a spinner to the driver, while the `remove()` method removes it. The `wrap()` method is used to -add a callback to the driver, which is executed before rendering the spinner. The `update()` method is called when a -spinner is updated. - -The `interrupt()` method is used to interrupt the spinner, while the `finalize()` method is used to finalize the spinner -and send the final message to the output. The `initialize()` method initializes the driver, while the `erase()` method -is used to erase the spinner. - -The `render()` method renders the current frame and sends it to the output. The `getInterval()` method returns the -interval for the spinner or driver's initial interval. diff --git a/doc/known_issues.md b/doc/known_issues.md index aa0c421d..8b64e4fc 100644 --- a/doc/known_issues.md +++ b/doc/known_issues.md @@ -5,10 +5,12 @@ ### Event loop auto start feature - Autostart feature interferes with error handling: - - If you have custom error handler (e.g. `NunoMaduro\Collision\Provider` is registered) in case of error(exception) the event loop will be started anyway (see `[889ad594-ca28-4770-bb38-fd5bd8cb1777]` code comment). -- Autostart feature for ReactPHP event loop can not be disabled. + - If you have custom error handler (e.g. `NunoMaduro\Collision\Provider` is registered) in case of error(exception) +the event loop will be started anyway (see `[889ad594-ca28-4770-bb38-fd5bd8cb1777]` code comment). +- Autostart feature for ReactPHP event loop CAN NOT be disabled. ### Docker - Usage inside docker containers: - - To run your application with `docker-compose exec` use `-T` option (or `tty: true` for your app service in `docker-compose.yml`) to disable pseudo-tty allocation. + - To run your application with `docker-compose exec` use `-T` option (or `tty: true` for your app service +in `docker-compose.yml`) to disable pseudo-tty allocation. - To run daemon-like applications you should disable spinner entirely to not mess-up docker logs. diff --git a/doc/limitations.md b/doc/limitations.md index 5021374c..31cf5b5c 100644 --- a/doc/limitations.md +++ b/doc/limitations.md @@ -10,7 +10,6 @@ In "Zero" dependencies "mode", the library has the following limitations: - ❗ **Only synchronous mode is available.** - Flexibility is limited. - Terminal color support can not be detected thus it is set to 256 colors (ansi8) by default. Can be overridden manually. -- Terminal width can not be detected thus it is set to 100 by default. Can be overridden manually. - Frame width can not be determined thus it is user responsibility to set it properly. - Signal handling is not supported. @@ -19,13 +18,10 @@ In "Zero" dependencies "mode", the library has the following limitations: - [Revolt](https://github.com/revoltphp/event-loop) - [ReactPHP](https://github.com/reactphp/event-loop) - For flexibility install: - - [alecrabbit/php-console-spinner-extras](https://github.com/alecrabbit/php-console-spinner-extras) -- For terminal features install one of supported libraries: - - [symfony/console](https://github.com/symfony/console) -- For frame width auto-detection install: - - [alecrabbit/php-wcwidth](https://github.com/alecrabbit/php-wcwidth) + - [alecrabbit/php-console-spinner-extras](https://github.com/alecrabbit/php-console-spinner-extras) (in development) - Install `ext-pcntl` extension for signal handling feature. ### Windows support - To run spinner spiced applications on Windows you should have some *nix-like terminal emulator installed. - VT100 terminal, e.g. [minTTY](https://github.com/mintty/mintty). +- Signal handling is NOT supported on Windows. diff --git a/doc/palettes.md b/doc/palettes.md new file mode 100644 index 00000000..04780926 --- /dev/null +++ b/doc/palettes.md @@ -0,0 +1,10 @@ +# Char Palette + +This package is supplied with one character palette - `Snake::class` + + +# Style Palette + +This package is supplied with one style palette - `Rainbow::class` + +> **Note** For more palettes you should install [php-console-spinner-extras](https://github.com/alecrabbit/php-console-spinner-extras) package. diff --git a/doc/patterns.md b/doc/patterns.md deleted file mode 100644 index d2bab77e..00000000 --- a/doc/patterns.md +++ /dev/null @@ -1,10 +0,0 @@ -# Char Patterns - -This package is supplied with one character pattern - `Snake::class` - - -# Style Patterns - -This package is supplied with one style pattern - `Rainbow::class` - -> **Note** For more patterns you should install [php-console-spinner-extras](https://github.com/alecrabbit/php-console-spinner-extras) package. diff --git a/doc/usage.md b/doc/usage.md index 09b2f9d0..e5a765cd 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -1,11 +1,11 @@ [⬅️ to README.md](../README.md) # Usage -+ [Usage with event loop](#ev) -+ [Usage without event loop](#no-ev) -+ [Patterns](#patterns) ++ [Usage with event loop (Asynchronous mode)](#evl) ++ [Usage without event loop (Synchronous mode)](#no-evl) ++ [Custom palettes](#palettes) -## Usage with event loop - Asynchronous mode(default) +## Usage with event loop - Asynchronous mode(default) ```php use AlecRabbit\Spinner\Factory\Factory; @@ -14,81 +14,76 @@ require_once __DIR__ . '/../bootstrap.php'; $spinner = Factory::createSpinner(); ``` -> see [example/async](../example/async) -## Usage without event loop - Synchronous mode +## Usage without event loop - Synchronous mode -In synchronous mode usage is a bit more complicated. For the sake of examples `App::class` is used. It is a simple class with `run()` method. It is used to demonstrate how to use `Spinner` in synchronous mode. +In synchronous mode usage is a bit more complicated. Simply speaking, you need to periodically call `render()` method of `IDriver` implementation. ```php -use Example\Kernel\App; +$driver = \AlecRabbit\Spinner\Facade::getDriver(); -require_once __DIR__ . '/../bootstrap.php'; - -App::prepareDefaults(); - -$app = new App(); - -$app->run(); +while (true) { + $driver->render(); + usleep(100000); +} ``` -> see [example/synchronous](../example/synchronous) - - -## Patterns - -There are two pattern types: -- character patterns, e.g. `'⠏'` `'⠛'` `'⠹'` -- style patterns, describing which style to apply to a char frame -### Character patterns +## Custom palettes -List of supplied character patterns you will find [here](patterns.md). +There are four palettes supplied with the package: +- Rainbow (style) +- NoStylePalette +- Snake (characters) +- NoCharPalette -#### How to create your own character pattern - -For that purpose you can use `\AlecRabbit\Spinner\Core\Pattern\Char\CustomPattern::class`. +#### How to create your own character palette ```php -// ... -$config = - Facade::getConfigBuilder() - ->withCharPattern( - new CustomPattern( - pattern: ['1', '2', '3'], // takes iterable of Stringable|string|IFrame - interval: 1000, - reversed: false - ) - ) - ->build(); -// ... +class Dots extends ACharPalette { + protected function createFrame(string $element): ICharFrame + { + return new CharFrame($element, 3); // note the width is 3 + } + + /** @inheritDoc */ + protected function sequence(): Traversable + { + // note the width of each element + $a = [' ', '. ', '.. ', '...', ' ..', ' .', ' ']; + + if ($this->options->getReversed()) { + $a = array_reverse($a); + } + + yield from $a; + } +} ``` -> **Note** IFrame is a raw representation of a frame, e.g. `FrameFactory::create('⠹', 1)` - -### Style patterns - -List of supplied style patterns you will find [here](patterns.md). -#### How to create your own style pattern - -For that purpose you can use `\AlecRabbit\Spinner\Core\Pattern\Style\CustomStylePattern::class`. +#### How to create your own style palette ```php -// ... -$config = - Facade::getConfigBuilder() - ->withStylePattern( - new CustomStylePattern( - pattern: [$style1, $style2], // required, takes iterable of IStyle|IFrame - colorMode: ColorMode::ANSI8 // optional - interval: 1000, // optional - reversed: false // optional - ) - ) - ->build(); -// ... +class Greeny extends AStylePalette { + protected function ansi4StyleFrames(): Traversable + { + yield from [ + $this->createFrame("\e[92m%s\e[39m"), + ]; + } + + protected function ansi8StyleFrames(): Traversable + { + return $this->ansi4StyleFrames(); + } + + protected function ansi24StyleFrames(): Traversable + { + return $this->ansi4StyleFrames(); + } + + protected function getInterval(StylingMethodMode $stylingMode): ?int + { + return null; // due to single style frame + } +} ``` -> **Note** IFrame is a raw representation of a frame, e.g. `FrameFactory::create("\e[38;2;255;255;255;48;2;255;0;0m%s\e[0m", 0)`(`%s` is important) - -> **Note** AnsiColorConverter supplied with this package is capable of converting IStyle to ANSI escape codes using only foreground color, in `int` format or using `#rrggbb` format(only colors in a table). - -> **Note** IStyle|IFrame limitation is not implemented yet. diff --git a/example/async/bootstrap.async.php b/example/async/bootstrap.async.php index 84ce6d89..e7a49ed7 100644 --- a/example/async/bootstrap.async.php +++ b/example/async/bootstrap.async.php @@ -2,51 +2,90 @@ declare(strict_types=1); +use AlecRabbit\Lib\Helper\MemoryUsage; +use AlecRabbit\Lib\Spinner\Contract\Factory\IDriverLinkerWithOutputFactory; +use AlecRabbit\Lib\Spinner\Factory\DriverLinkerWithOutputFactory; +use AlecRabbit\Spinner\Container\Contract\IContainer; +use AlecRabbit\Spinner\Container\Contract\IServiceDefinition; +use AlecRabbit\Spinner\Container\DefinitionRegistry; +use AlecRabbit\Spinner\Container\ServiceDefinition; +use AlecRabbit\Spinner\Core\Contract\IDriverLinker; use AlecRabbit\Spinner\Facade; -use AlecRabbit\Spinner\Helper\MemoryUsage; + +const MEMORY_REPORT_INTERVAL = 60; // seconds // Code in this file is NOT REQUIRED // and is used only for demonstration convenience. - require_once __DIR__ . '/../bootstrap.php'; // <-- except this line - it is required 🙂 -$memoryReportInterval = 60; // seconds +$registry = DefinitionRegistry::getInstance(); -$driver = Facade::getDriver(); +// Replace default driver linker with driver linker with output +$registry->bind( + new ServiceDefinition( + IDriverLinker::class, + static function (IContainer $container): IDriverLinker { + return $container->get(IDriverLinkerWithOutputFactory::class)->create(); + }, + IServiceDefinition::SINGLETON, + ), +); -// Create echo function -$echo = - $driver->wrap( - static function (?string $message = null): void { - echo $message . PHP_EOL; - } - ); +// Register driver linker with output factory +$registry->bind( + new ServiceDefinition(IDriverLinkerWithOutputFactory::class, DriverLinkerWithOutputFactory::class), +); -// Create memory report function -$memoryReport = - static function () use ($echo): void { - static $memoryUsage = new MemoryUsage(); +//$container = (new ContainerFactory($registry))->create(); +// +//Facade::useContainer($container); - $echo( - sprintf( - '%s %s', - (new DateTimeImmutable())->format(DATE_RFC3339_EXTENDED), - $memoryUsage->report(), - ) - ); - }; +register_shutdown_function( + static function (): void { + $driver = Facade::getDriver(); + + // Create echo function + $echo = + $driver->wrap( + static function (?string $message = null): void { + echo $message . PHP_EOL; + } + ); -$loop = Facade::getLoop(); + // Create memory report function + $memoryReport = + static function () use ($echo): void { + static $memoryUsage = new MemoryUsage(); -// Execute memory report function every $reportInterval seconds -$loop - ->repeat( - $memoryReportInterval, - $memoryReport - ) -; + $echo( + sprintf( + '%s %s', + (new DateTimeImmutable())->format(DATE_RFC3339_EXTENDED), + $memoryUsage->report(), + ) + ); + }; -$echo(PHP_EOL . sprintf('Using loop: "%s"', get_debug_type($loop))); -$echo(); + $loop = Facade::getLoop(); -$memoryReport(); // initial report + $echo(); + $echo(sprintf('Using loop: "%s"', get_debug_type($loop))); + $echo(); + + // Schedule memory report function + $loop + ->repeat( + MEMORY_REPORT_INTERVAL, + $memoryReport + ) + ; + + // Schedule initial memory report immediately after loop start + $loop + ->delay( + 0, + $memoryReport + ) + ; + } +); diff --git a/example/async/config/sigint-handler-override.php b/example/async/config/sigint-handler-override.php deleted file mode 100644 index 31aa3dc9..00000000 --- a/example/async/config/sigint-handler-override.php +++ /dev/null @@ -1,59 +0,0 @@ -interrupt($message); - - $loop->delay( - $delay, - static function () use ($loop): void { - $loop->stop(); - } - ); - - $loop->onSignal( - SIGINT, - static function () use ($loop): void { - $loop->stop(); - } - ); - }; - } - }; - -$creator = - new SignalHandlerCreator( - signal: SIGINT, // requires pcntl-ext - handlerCreator: $handlerCreator, - ); - -Facade::getSettings() - ->set( - new SignalHandlerSettings( - $creator - ), - ) -; - -$spinner = Facade::createSpinner(); diff --git a/example/async/config/cursor-visible.php b/example/async/settings/cursor-visible.php similarity index 79% rename from example/async/config/cursor-visible.php rename to example/async/settings/cursor-visible.php index 3e12522b..e5bd7db2 100644 --- a/example/async/config/cursor-visible.php +++ b/example/async/settings/cursor-visible.php @@ -6,7 +6,7 @@ use AlecRabbit\Spinner\Core\Settings\OutputSettings; use AlecRabbit\Spinner\Facade; -require_once __DIR__ . '/../../bootstrap.php'; +require_once __DIR__ . '/../bootstrap.async.php'; Facade::getSettings() ->set( @@ -17,8 +17,3 @@ ; $spinner = Facade::createSpinner(); - -// perform example unrelated actions: -require_once __DIR__ . '/../bootstrap.async.php'; - -//dump($spinner); diff --git a/example/async/settings/custom-char-palette.php b/example/async/settings/custom-char-palette.php new file mode 100644 index 00000000..e2ffbd90 --- /dev/null +++ b/example/async/settings/custom-char-palette.php @@ -0,0 +1,53 @@ +options->getReversed()) { + $a = array_reverse($a); + } + + yield from $a; + } + }; + +$widgetSettings = + new WidgetSettings( + charPalette: $charPalette, + ); + +$spinnerSettings = + new SpinnerSettings( + widgetSettings: $widgetSettings, + ); + +$spinner = Facade::createSpinner($spinnerSettings); diff --git a/example/async/settings/custom-final-message.php b/example/async/settings/custom-final-message.php new file mode 100644 index 00000000..e4ce89cf --- /dev/null +++ b/example/async/settings/custom-final-message.php @@ -0,0 +1,34 @@ +set( + new DriverSettings( + messages: new Messages( + finalMessage: '>>> Custom final message.' . PHP_EOL, + ) + ), + ) +; + +$driver = Facade::getDriver(); +$loop = Facade::getLoop(); + +$loop + ->delay( + 5, // seconds + static function () use ($driver, $loop): void { + $driver->finalize(); + $loop->stop(); + } + ) +; + +$spinner = Facade::createSpinner(); diff --git a/example/async/settings/custom-interruption-message.php b/example/async/settings/custom-interruption-message.php new file mode 100644 index 00000000..ee6b584f --- /dev/null +++ b/example/async/settings/custom-interruption-message.php @@ -0,0 +1,23 @@ +set( + new DriverSettings( + messages: new Messages( + interruptionMessage: PHP_EOL . 'Custom interruption message.' . PHP_EOL, + ) + ), + ) +; + +echo PHP_EOL . 'Press CTRL+C to interrupt.' . PHP_EOL . PHP_EOL; + +$spinner = Facade::createSpinner(); diff --git a/example/async/settings/custom-style-palette.php b/example/async/settings/custom-style-palette.php new file mode 100644 index 00000000..e448c96e --- /dev/null +++ b/example/async/settings/custom-style-palette.php @@ -0,0 +1,48 @@ +createFrame("\e[92m%s\e[39m"), + ]; + } + + protected function ansi8StyleFrames(): Traversable + { + return $this->ansi4StyleFrames(); + } + + protected function ansi24StyleFrames(): Traversable + { + return $this->ansi4StyleFrames(); + } + + protected function getInterval(StylingMethodMode $stylingMode): ?int + { + return null; // due to single style frame + } + }; + +$widgetSettings = + new WidgetSettings( + stylePalette: $stylePalette, + ); + +$spinnerSettings = + new SpinnerSettings( + widgetSettings: $widgetSettings, + ); + +$spinner = Facade::createSpinner($spinnerSettings); diff --git a/example/async/settings/driver-disabled.php b/example/async/settings/driver-disabled.php new file mode 100644 index 00000000..c5c59669 --- /dev/null +++ b/example/async/settings/driver-disabled.php @@ -0,0 +1,22 @@ +set( + $driverSettings, + ) +; + +$spinner = Facade::createSpinner(); diff --git a/example/async/config/force-synchronous.php b/example/async/settings/force-synchronous.php similarity index 51% rename from example/async/config/force-synchronous.php rename to example/async/settings/force-synchronous.php index 39fcdd4d..81dd4195 100644 --- a/example/async/config/force-synchronous.php +++ b/example/async/settings/force-synchronous.php @@ -3,14 +3,18 @@ declare(strict_types=1); use AlecRabbit\Spinner\Contract\Option\RunMethodOption; -use AlecRabbit\Spinner\Core\Settings\AuxSettings; +use AlecRabbit\Spinner\Core\Settings\GeneralSettings; use AlecRabbit\Spinner\Facade; require_once __DIR__ . '/../../bootstrap.php'; +$cycles = 100; +$min = 1000; +$max = 500000; + Facade::getSettings() ->set( - new AuxSettings( + new GeneralSettings( runMethodOption: RunMethodOption::SYNCHRONOUS, ), ) @@ -20,15 +24,14 @@ $driver = Facade::getDriver(); -echo 'Synchronous mode forced' . PHP_EOL; +echo 'Synchronous mode forced.' . PHP_EOL; +echo 'Runtime: ' . ($cycles * $min / 1e6) . '..' . ($cycles * $max / 1e6) . 's' . PHP_EOL; // Will throw: //Facade::getLoop()->run(); -for ($i = 0; $i < 1000; $i++) { +for ($i = 0; $i < $cycles; $i++) { $driver->render(); - \usleep(\random_int(1000, 500000)); // simulates unequal intervals + usleep(random_int($min, $max)); // simulates unequal intervals } -$driver->finalize(); - -echo 'Finished' . PHP_EOL; +$driver->finalize('Finished.' . PHP_EOL); diff --git a/example/async/config/loop-autostart-disabled.php b/example/async/settings/loop-autostart-disabled.php similarity index 73% rename from example/async/config/loop-autostart-disabled.php rename to example/async/settings/loop-autostart-disabled.php index 1a7ec781..fa9a79c6 100644 --- a/example/async/config/loop-autostart-disabled.php +++ b/example/async/settings/loop-autostart-disabled.php @@ -8,7 +8,7 @@ use AlecRabbit\Spinner\Facade; use AlecRabbit\Spinner\Probes; -require_once __DIR__ . '/../../bootstrap.php'; +require_once __DIR__ . '/../bootstrap.async.php'; // AutoStartOption can NOT be DISABLED for ReactPHP event loop Probes::unregister(ReactLoopProbe::class); @@ -22,10 +22,11 @@ $spinner = Facade::createSpinner(); -// perform example unrelated actions: -require_once __DIR__ . '/../bootstrap.async.php'; - -echo 'Starting loop...' . PHP_EOL; -// Loop autostart is disabled, so we need to run do it manually -Facade::getLoop()->run(); +register_shutdown_function( + static function (): void { + echo 'Starting loop...' . PHP_EOL; + // Loop autostart is disabled, so we need to run do it manually + Facade::getLoop()->run(); + } +); diff --git a/example/async/settings/no-style-palette-combo-v1.php b/example/async/settings/no-style-palette-combo-v1.php new file mode 100644 index 00000000..0c7260fe --- /dev/null +++ b/example/async/settings/no-style-palette-combo-v1.php @@ -0,0 +1,34 @@ +set( + new RootWidgetSettings( + leadingSpacer: new CharFrame('<>', 2), // <-- ignored + trailingSpacer: new CharFrame(' 💨', 3), + ) + ) +; + +$widgetSettings = + new WidgetSettings( + leadingSpacer: new CharFrame('', 0), // <-- used this instead + stylePalette: new NoStylePalette(), + ); + +$spinnerSettings = + new SpinnerSettings( + widgetSettings: $widgetSettings, + ); + +$spinner = Facade::createSpinner($spinnerSettings); diff --git a/example/async/settings/no-style-palette-combo-v2.php b/example/async/settings/no-style-palette-combo-v2.php new file mode 100644 index 00000000..1b3ddee6 --- /dev/null +++ b/example/async/settings/no-style-palette-combo-v2.php @@ -0,0 +1,37 @@ +set( + new WidgetSettings( + leadingSpacer: new CharFrame('<>', 2), // <-- ignored spacer + ), + new RootWidgetSettings( + leadingSpacer: new CharFrame('🧲 ', 3), // used this spacer instead + stylePalette: new Rainbow(), // <-- ignored palette + ), + ) +; + +$widgetSettings = + new WidgetSettings( + stylePalette: new NoStylePalette(), // <-- used this palette instead + ); + +$spinnerSettings = + new SpinnerSettings( + widgetSettings: $widgetSettings, + ); + +$spinner = Facade::createSpinner($spinnerSettings); diff --git a/example/async/settings/no-style-palette-v1.php b/example/async/settings/no-style-palette-v1.php new file mode 100644 index 00000000..2ca52cd5 --- /dev/null +++ b/example/async/settings/no-style-palette-v1.php @@ -0,0 +1,21 @@ +set( + new RootWidgetSettings( + stylePalette: new NoStylePalette(), + ) + ) +; + +$spinner = Facade::createSpinner(); diff --git a/example/async/config/normalizer-option.php b/example/async/settings/normalizer-option.php similarity index 54% rename from example/async/config/normalizer-option.php rename to example/async/settings/normalizer-option.php index 33160029..e4667caa 100644 --- a/example/async/config/normalizer-option.php +++ b/example/async/settings/normalizer-option.php @@ -3,25 +3,17 @@ declare(strict_types=1); use AlecRabbit\Spinner\Contract\Option\NormalizerOption; -use AlecRabbit\Spinner\Core\Settings\AuxSettings; +use AlecRabbit\Spinner\Core\Settings\NormalizerSettings; use AlecRabbit\Spinner\Facade; -require_once __DIR__ . '/../../bootstrap.php'; +require_once __DIR__ . '/../bootstrap.async.php'; Facade::getSettings() ->set( - new AuxSettings( + new NormalizerSettings( normalizerOption: NormalizerOption::SLOW, ), ) ; $spinner = Facade::createSpinner(); - -// perform example unrelated actions: -require_once __DIR__ . '/../bootstrap.async.php'; - -echo sprintf( - 'Actual interval: %sms' . PHP_EOL, - $spinner->getInterval()->toMilliseconds() -); diff --git a/example/async/config/readme.md b/example/async/settings/readme.md similarity index 67% rename from example/async/config/readme.md rename to example/async/settings/readme.md index 2e37ea6e..321a6aaa 100644 --- a/example/async/config/readme.md +++ b/example/async/settings/readme.md @@ -1,9 +1,10 @@ !!! ATTENTION !!! -You can change settings BEFORE creating a configuration. +after configuration is created NOT all settings changes take effect Configuration is created once with first call of: + - `Facade::getDriver()` - `Facade::getLoop()` - `Facade::createSpinner()` diff --git a/example/async/settings/replace-spinner.php b/example/async/settings/replace-spinner.php new file mode 100644 index 00000000..aa7e99b2 --- /dev/null +++ b/example/async/settings/replace-spinner.php @@ -0,0 +1,70 @@ +options->getReversed()) { + $a = array_reverse($a); + } + + yield from $a; + } + }; + +// Let's change default settings +Facade::getSettings() + ->set( + new RootWidgetSettings( + leadingSpacer: new CharFrame('⏳ ', 3), + stylePalette: new NoStylePalette(), + charPalette: $charPalette, + ), + new NormalizerSettings( + normalizerOption: NormalizerOption::SLOW, + ) + ) +; + +$spinnerTwo = Facade::createSpinner(new SpinnerSettings(autoAttach: false)); + +$driver = Facade::getDriver(); +$loop = Facade::getLoop(); + +$loop->delay( + 5, // add spinner at + static function () use ($driver, $spinnerTwo): void { + $driver->add($spinnerTwo); + $driver->render(); // optional + } +); + +$loop->delay( + 15, // add spinner at + static function () use ($driver, $spinnerOne): void { + $driver->add($spinnerOne); + } +); diff --git a/example/async/settings/reversed-slow-snake-palette.php b/example/async/settings/reversed-slow-snake-palette.php new file mode 100644 index 00000000..14033427 --- /dev/null +++ b/example/async/settings/reversed-slow-snake-palette.php @@ -0,0 +1,48 @@ +set($outputSettings) + ; +} + +$spinner = Facade::createSpinner($spinnerSettings); diff --git a/example/async/settings/sigint-handler-override.php b/example/async/settings/sigint-handler-override.php new file mode 100644 index 00000000..23cc820c --- /dev/null +++ b/example/async/settings/sigint-handler-override.php @@ -0,0 +1,66 @@ +interrupt($message); + + $loop->delay( + $delay, + static function () use ($loop): void { + $loop->stop(); + } + ); + + $loop->onSignal( + SIGINT, // requires pcntl-ext + static function () use ($loop): void { + $loop->stop(); + } + ); + }; + } + }; + +$creator = + new SignalHandlerCreator( + signal: SIGINT, // requires pcntl-ext + handlerCreator: $handlerCreator, + ); + +Facade::getSettings() + ->set( + new SignalHandlerSettings( + $creator + ), + new DriverSettings( + messages: new Messages( + interruptionMessage: PHP_EOL . 'Custom interruption message.' . PHP_EOL, // note: will not be used + ) + ), + ) +; + +$spinner = Facade::createSpinner(); diff --git a/example/async/config/styling-disabled.php b/example/async/settings/styling-disabled.php similarity index 82% rename from example/async/config/styling-disabled.php rename to example/async/settings/styling-disabled.php index af9bf0cc..16af8547 100644 --- a/example/async/config/styling-disabled.php +++ b/example/async/settings/styling-disabled.php @@ -6,18 +6,20 @@ use AlecRabbit\Spinner\Core\Settings\OutputSettings; use AlecRabbit\Spinner\Facade; -require_once __DIR__ . '/../../bootstrap.php'; +require_once __DIR__ . '/../bootstrap.async.php'; $outputSettings = new OutputSettings( stylingMethodOption: StylingMethodOption::NONE, ); -Facade::getSettings()->set($outputSettings); +Facade::getSettings() + ->set($outputSettings) +; $spinner = Facade::createSpinner(); // perform example unrelated actions: -require_once __DIR__ . '/../bootstrap.async.php'; + //dump($spinner); diff --git a/example/async/simple/app.php b/example/async/simple/app.php index 14c9dd58..533dcc4d 100644 --- a/example/async/simple/app.php +++ b/example/async/simple/app.php @@ -6,4 +6,4 @@ require_once __DIR__ . '/../bootstrap.async.php'; -$spinner = Facade::createSpinner(); +$spinner = Facade::createSpinner(); // yep, that's it diff --git a/example/async/benchmark/app.php b/example/benchmark/async/app.php similarity index 78% rename from example/async/benchmark/app.php rename to example/benchmark/async/app.php index dd5c781f..61f8c4b5 100644 --- a/example/async/benchmark/app.php +++ b/example/benchmark/async/app.php @@ -2,28 +2,28 @@ declare(strict_types=1); -use AlecRabbit\Benchmark\Contract\Factory\IBenchmarkResultsFactory; -use AlecRabbit\Benchmark\Contract\IReportPrinter; use AlecRabbit\Benchmark\Factory\ReportFactory; -use AlecRabbit\Benchmark\Spinner\Contract\IBenchmarkingDriver; +use AlecRabbit\Lib\Helper\MemoryUsage; +use AlecRabbit\Lib\Spinner\BenchmarkFacade; +use AlecRabbit\Lib\Spinner\Contract\IBenchmarkingDriver; +use AlecRabbit\Spinner\Asynchronous\React\ReactLoopProbe; use AlecRabbit\Spinner\Facade; -use AlecRabbit\Spinner\Helper\MemoryUsage; use AlecRabbit\Spinner\Probes; // in seconds const RUNTIME = 600; const MEMORY_REPORT_INTERVAL = 60; -$container = require __DIR__ . '/../../benchmark/bootstrap.php'; +require __DIR__ . '/../container.php'; // Pick ONE of the following event loops: -Probes::unregister(\AlecRabbit\Spinner\Asynchronous\React\ReactLoopProbe::class); +Probes::unregister(ReactLoopProbe::class); //Probes::unregister(\AlecRabbit\Spinner\Asynchronous\Revolt\RevoltLoopProbe::class); $driver = Facade::getDriver(); if (!$driver instanceof IBenchmarkingDriver) { - throw new \LogicException( + throw new LogicException( sprintf( 'Driver must implement "%s".', IBenchmarkingDriver::class @@ -39,9 +39,7 @@ static function (?string $message = null): void { } ); - -/** @var IBenchmarkResultsFactory $benchmarkResultsFactory */ -$benchmarkResultsFactory = $container->get(IBenchmarkResultsFactory::class); +$benchmarkResultsFactory = BenchmarkFacade::getBenchmarkResultsFactory(); $benchmarkResults = $benchmarkResultsFactory @@ -52,9 +50,7 @@ static function (?string $message = null): void { ) ; - -// Create report function: -$reportPrinter = $container->get(IReportPrinter::class); +$reportPrinter = BenchmarkFacade::getReportPrinter(); $reportObject = (new ReportFactory(benchmarkResults: $benchmarkResults, title: 'Benchmarking')) @@ -115,6 +111,7 @@ static function () use ($driver, $spinner): void { // Begin benchmarking $echo(sprintf('Runtime: %ss', RUNTIME)); +$echo(sprintf('Render interval, ms: %s', $driver->getInterval()->toMilliseconds())); $echo(); $echo(sprintf('Using loop: "%s"', get_debug_type($loop))); $echo(); diff --git a/example/benchmark/bootstrap.php b/example/benchmark/bootstrap.php deleted file mode 100644 index edeccd8e..00000000 --- a/example/benchmark/bootstrap.php +++ /dev/null @@ -1,97 +0,0 @@ -bind(ITimer::class, new MicrosecondTimer()); -$registry->bind(IDriverProviderFactory::class, BenchmarkingDriverProviderFactory::class); -$registry->bind(IResultMaker::class, ResultMaker::class); -$registry->bind(IBenchmarkResultsFactory::class, BenchmarkResultsFactory::class); -$registry->bind(IBenchmarkingDriverFactory::class, BenchmarkingDriverFactory::class); -$registry->bind(IBenchmarkingDriverBuilder::class, BenchmarkingDriverBuilder::class); -$registry->bind(IBenchmarkFactory::class, BenchmarkFactory::class); -$registry->bind(IMeasurementFactory::class, MeasurementFactory::class); -$registry->bind(IStopwatchBuilder::class, StopwatchBuilder::class); -$registry->bind(IStopwatchFactory::class, StopwatchFactory::class); -$registry->bind(IReportPrinterFactory::class, static function (ContainerInterface $container): IReportPrinterFactory { - $stream = - new class implements IResourceStream { - public function write(Traversable $data): void - { - foreach ($data as $line) { - echo $line; - } - } - }; - - $output = - new Output( - $stream - ); - - return - new ReportPrinterFactory( - $container->get(IReportPrinterBuilder::class), - $output, - $container->get(IReportFormatter::class), - ); -}); -$registry->bind(IReportPrinterBuilder::class, ReportPrinterBuilder::class); -$registry->bind(IReportFormatter::class, ReportFormatter::class); -$registry->bind(IDatetimeFormatter::class, DatetimeFormatter::class); -$registry->bind(IResultFormatter::class, ResultFormatter::class); -$registry->bind(IKeyFormatter::class, KeyFormatter::class); -$registry->bind(IReportPrinter::class, static function (ContainerInterface $container): IReportPrinter { - return $container->get(IReportPrinterFactory::class)->create(); -}); - -$container = (new ContainerFactory($registry))->create(); - -Facade::useContainer($container); - -return $container; diff --git a/example/benchmark/container.php b/example/benchmark/container.php new file mode 100644 index 00000000..14883390 --- /dev/null +++ b/example/benchmark/container.php @@ -0,0 +1,99 @@ +bind(new ServiceDefinition(ITimer::class, new MicrosecondTimer())); +$registry->bind(new ServiceDefinition(IDriverProviderFactory::class, BenchmarkingDriverProviderFactory::class)); +$registry->bind(new ServiceDefinition(IResultMaker::class, ResultMaker::class)); +$registry->bind(new ServiceDefinition(IBenchmarkResultsFactory::class, BenchmarkResultsFactory::class)); +$registry->bind(new ServiceDefinition(IBenchmarkingDriverFactory::class, BenchmarkingDriverFactory::class)); +$registry->bind(new ServiceDefinition(IBenchmarkingDriverBuilder::class, BenchmarkingDriverBuilder::class)); +$registry->bind(new ServiceDefinition(IBenchmarkFactory::class, BenchmarkFactory::class)); +$registry->bind(new ServiceDefinition(IMeasurementFactory::class, MeasurementFactory::class)); +$registry->bind(new ServiceDefinition(IStopwatchBuilder::class, StopwatchBuilder::class)); +$registry->bind(new ServiceDefinition(IStopwatchFactory::class, StopwatchFactory::class)); +$registry->bind(new ServiceDefinition(IReportPrinterBuilder::class, ReportPrinterBuilder::class)); +$registry->bind(new ServiceDefinition(IReportFormatter::class, ReportFormatter::class)); +$registry->bind(new ServiceDefinition(IDatetimeFormatter::class, DatetimeFormatter::class)); +$registry->bind(new ServiceDefinition(IResultFormatter::class, ResultFormatter::class)); +$registry->bind(new ServiceDefinition(IKeyFormatter::class, KeyFormatter::class)); + +$registry->bind( + new ServiceDefinition( + IReportPrinter::class, + static function (ContainerInterface $container): IReportPrinter { + return $container->get(IReportPrinterFactory::class)->create(); + } + ), +); + +$registry->bind( + new ServiceDefinition( + IReportPrinterFactory::class, + static function (ContainerInterface $container): IReportPrinterFactory { + $stream = + new class() implements IWritableStream { + public function write(Traversable $data): void + { + foreach ($data as $el) { + echo $el; + } + } + }; + + $output = + new Output( + $stream + ); + + return new ReportPrinterFactory( + $container->get(IReportPrinterBuilder::class), + $output, + $container->get(IReportFormatter::class), + ); + } + ) +); diff --git a/example/benchmark/sync/app.php b/example/benchmark/sync/app.php new file mode 100644 index 00000000..15ad1498 --- /dev/null +++ b/example/benchmark/sync/app.php @@ -0,0 +1,87 @@ +format(DATE_RFC3339_EXTENDED), + $memoryUsage->report(), + ); + echo ' '; + echo sprintf( + '%s%% [%d/%d]', + (int)ceil(100 * $i / CYCLES), + $i, + CYCLES + ); + echo PHP_EOL; + } + + $driver->render(); +} +echo PHP_EOL; + +// call other methods: +$driver->remove($spinner); +$driver->getInterval(); +$driver->wrap(static fn() => null); + +// Finalize: +$driver->finalize(); + +// Print report: +$benchmarkResultsFactory = BenchmarkFacade::getBenchmarkResultsFactory(); + +$benchmarkResults = + $benchmarkResultsFactory + ->create( + $driver + ->getBenchmark() + ->getMeasurements() + ) +; + +$reportPrinter = BenchmarkFacade::getReportPrinter(); + +$title = sprintf('%s cycles', CYCLES); + +$reportObject = + (new ReportFactory(benchmarkResults: $benchmarkResults, title: $title)) + ->create() +; + +$reportPrinter->print($reportObject); diff --git a/example/benchmark/sync/container.sync.php b/example/benchmark/sync/container.sync.php new file mode 100644 index 00000000..00713a07 --- /dev/null +++ b/example/benchmark/sync/container.sync.php @@ -0,0 +1,38 @@ +bind( + new ServiceDefinition( + IWritableStream::class, + new class() implements IWritableStream { + public function write(Traversable $data): void + { + // unwrap $data + iterator_to_array($data); + } + } + ), +); + +$registry->bind( + new ServiceDefinition( + IDeltaTimer::class, + new class() implements IDeltaTimer { + public function getDelta(): float + { + // simulate unequal time intervals + return (float)random_int(1000, 500000); + } + } + ), +); diff --git a/example/bootstrap.php b/example/bootstrap.php index ddf8542d..0e76e67c 100644 --- a/example/bootstrap.php +++ b/example/bootstrap.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\AbstractDumper; use Symfony\Component\VarDumper\Dumper\CliDumper; use Symfony\Component\VarDumper\Dumper\ContextProvider\CliContextProvider; use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; @@ -20,30 +21,35 @@ if (class_exists(Symfony\Component\VarDumper\VarDumper::class)) { $cloner = new VarCloner(); - $dumper = new ServerDumper(getHost(), getFallbackDumper(), [ - 'cli' => new CliContextProvider(), - 'source' => new SourceContextProvider(), - ]); - - VarDumper::setHandler(static function ($var) use ($cloner, $dumper): void { - $dumper->dump($cloner->cloneVar($var)); // intentional dump - }); - + $dumper = + new ServerDumper( + getHost(), + getDumper(), + [ + 'cli' => new CliContextProvider(), + 'source' => new SourceContextProvider(), + ] + ); + + VarDumper::setHandler( + static function ($var) use ($cloner, $dumper): void { + $dumper->dump($cloner->cloneVar($var)); // dump + } + ); } -function getFallbackDumper(): HtmlDumper|CliDumper +function getHost(): string { - return in_array(PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliDumper() : new HtmlDumper(); -} + $srv = getenv('VAR_DUMPER_SERVER'); -function getAddress(false|string $srv): string -{ return $srv === false ? 'tcp://127.0.0.1:9912' : sprintf('tcp://%s', $srv); } -function getHost(): string +function getDumper(): AbstractDumper { - return getAddress(getenv('VAR_DUMPER_SERVER')); + return in_array(PHP_SAPI, ['cli', 'phpdbg'], true) + ? new CliDumper() + : new HtmlDumper(); } diff --git a/example/sync/benchmark/app.php b/example/sync/benchmark/app.php deleted file mode 100644 index b7412867..00000000 --- a/example/sync/benchmark/app.php +++ /dev/null @@ -1,114 +0,0 @@ -bind( - IResourceStream::class, - new class implements IResourceStream { - public function write(Traversable $data): void - { - // unwrap data - iterator_to_array($data); - } - } - ); - $registry->bind( - ITimer::class, - new class implements ITimer { - public function getDelta(): float - { - // simulate unequal time intervals - return (float)random_int(1000, 500000); - } - } - ); - - $container = (new ContainerFactory($registry))->create(); - - Facade::useContainer($container); -} - -$driver = Facade::getDriver(); - -if (!$driver instanceof IBenchmarkingDriver) { - throw new \LogicException( - sprintf( - 'Driver must implement "%s".', - IBenchmarkingDriver::class - ) - ); -} - -$spinner = Facade::createSpinner(); - - -// Do benchmarking: -for ($i = 0; $i < CYCLES; $i++) { - if ($i % PROGRESS_EVERY_CYCLES === 0) { - echo sprintf( - '%s%%, cycle: %d/%d', - (int)ceil(100 * $i / CYCLES), - $i, - CYCLES - ) . PHP_EOL; - } - - $driver->render(); -} -echo PHP_EOL; - -// call other methods: -$driver->remove($spinner); -$driver->getInterval(); -$driver->wrap(static fn() => null); - -// Finalize: -$driver->finalize(); - -// Print report: -/** @var IBenchmarkResultsFactory $benchmarkResultsFactory */ -$benchmarkResultsFactory = $container->get(IBenchmarkResultsFactory::class); - -$benchmarkResults = - $benchmarkResultsFactory - ->create( - $driver - ->getBenchmark() - ->getMeasurements() - ) -; - -$reportPrinter = $container->get(IReportPrinter::class); - -$title = sprintf('%s cycles', CYCLES); - -$reportObject = - (new ReportFactory(benchmarkResults: $benchmarkResults, title: $title)) - ->create() -; - -$reportPrinter->print($reportObject); diff --git a/example/sync/bootstrap.sync.php b/example/sync/bootstrap.sync.php new file mode 100644 index 00000000..f07ec5f1 --- /dev/null +++ b/example/sync/bootstrap.sync.php @@ -0,0 +1,11 @@ +run(); + +for ($i = 0; $i < $cycles; $i++) { + $driver->render(); + usleep(random_int($min, $max)); // simulates unequal intervals +} +$driver->finalize('Finished.' . PHP_EOL); diff --git a/lib/Benchmark/Benchmark.php b/lib/Benchmark/Benchmark.php index 64d20ced..57554b2e 100644 --- a/lib/Benchmark/Benchmark.php +++ b/lib/Benchmark/Benchmark.php @@ -25,7 +25,6 @@ public function run(string $key, Closure $callback, mixed ...$args): mixed return $result; } - /** @inheritDoc */ public function getStopwatch(): IStopwatch { return $this->stopwatch; diff --git a/lib/Benchmark/Builder/ReportBuilder.php b/lib/Benchmark/Builder/ReportBuilder.php index 52482f82..d4f69c28 100644 --- a/lib/Benchmark/Builder/ReportBuilder.php +++ b/lib/Benchmark/Builder/ReportBuilder.php @@ -20,12 +20,11 @@ public function build(): IReport { $this->validate(); - return - new Report( - $this->results, - $this->title, - $this->prefix, - ); + return new Report( + $this->results, + $this->title, + $this->prefix, + ); } private function validate(): void diff --git a/lib/Benchmark/Builder/ReportPrinterBuilder.php b/lib/Benchmark/Builder/ReportPrinterBuilder.php index 904fd59c..224a04b1 100644 --- a/lib/Benchmark/Builder/ReportPrinterBuilder.php +++ b/lib/Benchmark/Builder/ReportPrinterBuilder.php @@ -20,11 +20,10 @@ public function build(): IReportPrinter { $this->validate(); - return - new ReportPrinter( - output: $this->output, - reportFormatter: $this->reportFormatter, - ); + return new ReportPrinter( + output: $this->output, + reportFormatter: $this->reportFormatter, + ); } private function validate(): void diff --git a/lib/Benchmark/Exception/MeasurementException.php b/lib/Benchmark/Exception/MeasurementException.php index 488766d5..a516f339 100644 --- a/lib/Benchmark/Exception/MeasurementException.php +++ b/lib/Benchmark/Exception/MeasurementException.php @@ -8,5 +8,4 @@ class MeasurementException extends LogicException { - } diff --git a/lib/Benchmark/Factory/BenchmarkFactory.php b/lib/Benchmark/Factory/BenchmarkFactory.php index 6b74f780..211e3534 100644 --- a/lib/Benchmark/Factory/BenchmarkFactory.php +++ b/lib/Benchmark/Factory/BenchmarkFactory.php @@ -18,9 +18,8 @@ public function __construct( public function create(): IBenchmark { - return - new Benchmark( - $this->stopwatchFactory->create(), - ); + return new Benchmark( + $this->stopwatchFactory->create(), + ); } } diff --git a/lib/Benchmark/Factory/BenchmarkResultsFactory.php b/lib/Benchmark/Factory/BenchmarkResultsFactory.php index 1e6315ad..656a994d 100644 --- a/lib/Benchmark/Factory/BenchmarkResultsFactory.php +++ b/lib/Benchmark/Factory/BenchmarkResultsFactory.php @@ -23,14 +23,14 @@ public function __construct( /** @inheritDoc */ public function create(iterable $measurements): IBenchmarkResults { - return - new BenchmarkResults( - $this->createResults($measurements), - ); + return new BenchmarkResults( + $this->createResults($measurements), + ); } /** * @param iterable $measurements + * * @return Traversable */ private function createResults(iterable $measurements): Traversable diff --git a/lib/Benchmark/Factory/MeasurementFactory.php b/lib/Benchmark/Factory/MeasurementFactory.php index 357449e6..3d4aee9a 100644 --- a/lib/Benchmark/Factory/MeasurementFactory.php +++ b/lib/Benchmark/Factory/MeasurementFactory.php @@ -12,7 +12,6 @@ final class MeasurementFactory implements IMeasurementFactory { public function create(): IMeasurement { - return - new Measurement(); + return new Measurement(); } } diff --git a/lib/Benchmark/Factory/ReportFactory.php b/lib/Benchmark/Factory/ReportFactory.php index 173b46d8..71409d47 100644 --- a/lib/Benchmark/Factory/ReportFactory.php +++ b/lib/Benchmark/Factory/ReportFactory.php @@ -21,11 +21,10 @@ public function __construct( public function create(): IReport { - return - $this->reportBuilder - ->withBenchmarkResults($this->benchmarkResults) - ->withTitle($this->title) - ->build() + return $this->reportBuilder + ->withBenchmarkResults($this->benchmarkResults) + ->withTitle($this->title) + ->build() ; } } diff --git a/lib/Benchmark/Factory/ReportPrinterFactory.php b/lib/Benchmark/Factory/ReportPrinterFactory.php index 3a139f9c..3187b4b7 100644 --- a/lib/Benchmark/Factory/ReportPrinterFactory.php +++ b/lib/Benchmark/Factory/ReportPrinterFactory.php @@ -21,11 +21,10 @@ public function __construct( public function create(): IReportPrinter { - return - $this->builder - ->withOutput($this->output) - ->withReportFormatter($this->reportFormatter) - ->build() + return $this->builder + ->withOutput($this->output) + ->withReportFormatter($this->reportFormatter) + ->build() ; } } diff --git a/lib/Benchmark/Factory/ResultMaker.php b/lib/Benchmark/Factory/ResultMaker.php index 25f8d6d4..48a55b1f 100644 --- a/lib/Benchmark/Factory/ResultMaker.php +++ b/lib/Benchmark/Factory/ResultMaker.php @@ -12,25 +12,22 @@ final class ResultMaker implements IResultMaker { - /** @inheritDoc */ public function make(IMeasurement $measurement): IResult { try { - return - new Result( - $measurement->getAverage(), - $measurement->getMin(), - $measurement->getMax(), - $measurement->getCount(), - ); + return new Result( + $measurement->getAverage(), + $measurement->getMin(), + $measurement->getMax(), + $measurement->getCount(), + ); } catch (MeasurementException $_) { - return - new Result( - $measurement->getAny(), - $measurement->getMin(), - $measurement->getMax(), - $measurement->getCount(), - ); + return new Result( + $measurement->getAny(), + $measurement->getMin(), + $measurement->getMax(), + $measurement->getCount(), + ); } } } diff --git a/lib/Benchmark/Factory/StopwatchFactory.php b/lib/Benchmark/Factory/StopwatchFactory.php index d541af2b..930dd569 100644 --- a/lib/Benchmark/Factory/StopwatchFactory.php +++ b/lib/Benchmark/Factory/StopwatchFactory.php @@ -21,11 +21,10 @@ public function __construct( public function create(): IStopwatch { - return - $this->builder - ->withTimer($this->timer) - ->withMeasurementFactory($this->measurementFactory) - ->build() + return $this->builder + ->withTimer($this->timer) + ->withMeasurementFactory($this->measurementFactory) + ->build() ; } } diff --git a/lib/Benchmark/KeyFormatter.php b/lib/Benchmark/KeyFormatter.php index 782a1475..f8bc2bca 100644 --- a/lib/Benchmark/KeyFormatter.php +++ b/lib/Benchmark/KeyFormatter.php @@ -15,9 +15,8 @@ final class KeyFormatter implements IKeyFormatter public function format(string $key, ?string $prefix = null): string { - return - null !== $prefix && str_starts_with($key, $prefix) - ? str_replace($prefix, self::REPLACE, $key) - : $key; + return $prefix !== null && str_starts_with($key, $prefix) + ? str_replace($prefix, self::REPLACE, $key) + : $key; } } diff --git a/lib/Benchmark/ReportFormatter.php b/lib/Benchmark/ReportFormatter.php index 355e0d20..d1fe613a 100644 --- a/lib/Benchmark/ReportFormatter.php +++ b/lib/Benchmark/ReportFormatter.php @@ -12,12 +12,12 @@ use AlecRabbit\Benchmark\Contract\IResultFormatter; use DateTimeImmutable; -final class ReportFormatter implements IReportFormatter +final readonly class ReportFormatter implements IReportFormatter { public function __construct( - protected IDatetimeFormatter $datetimeFormatter, - protected IResultFormatter $resultFormatter, - protected IKeyFormatter $keyFormatter, + private IDatetimeFormatter $datetimeFormatter, + private IResultFormatter $resultFormatter, + private IKeyFormatter $keyFormatter, ) { } @@ -44,8 +44,7 @@ public function format(IReport $report): string private function initialOutput(IReport $report): string { - return - <<
getTitle()} Date: {$this->datetimeFormatter->format(new DateTimeImmutable())} @@ -53,7 +52,7 @@ private function initialOutput(IReport $report): string HEADER; } - protected function extractPrefix(IReport $report): string + private function extractPrefix(IReport $report): string { $prefix = $report->getPrefix(); return $prefix === '' ? '' : PHP_EOL . "Prefix: {$prefix}" . PHP_EOL; diff --git a/lib/Benchmark/Stopwatch/A/ATimer.php b/lib/Benchmark/Stopwatch/A/ATimer.php index f0dda29a..e7366083 100644 --- a/lib/Benchmark/Stopwatch/A/ATimer.php +++ b/lib/Benchmark/Stopwatch/A/ATimer.php @@ -6,7 +6,7 @@ use AlecRabbit\Benchmark\Contract\ITimer; use AlecRabbit\Benchmark\Contract\TimeUnit; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; use Closure; use ReflectionFunction; use ReflectionIntersectionType; @@ -28,7 +28,7 @@ protected static function assertTimeFunction(Closure $timeFunction): void try { $timeFunction(); } catch (Throwable $e) { - throw new InvalidArgumentException( + throw new InvalidArgument( 'Invoke of time function throws: ' . $e->getMessage(), previous: $e, ); @@ -37,7 +37,7 @@ protected static function assertTimeFunction(Closure $timeFunction): void $returnType = $reflection->getReturnType(); if ($returnType === null) { - throw new InvalidArgumentException( + throw new InvalidArgument( 'Return type of time function is not specified.' ); } @@ -56,8 +56,8 @@ protected static function assertReturnType( private static function assertIntersectionType(ReflectionIntersectionType $intersectionType): void { - throw new InvalidArgumentException( - 'Unexpected intersection type.', + throw new InvalidArgument( + 'Unexpected intersection type. ' . get_debug_type($intersectionType), ); } @@ -72,12 +72,12 @@ private static function assertNamedType(ReflectionNamedType $namedType): void { $type = $namedType->getName(); if ($type === 'null' || $namedType->allowsNull()) { - throw new InvalidArgumentException( + throw new InvalidArgument( 'Time function return type allows null.', ); } if ($type !== 'float' && $type !== 'int') { - throw new InvalidArgumentException( + throw new InvalidArgument( sprintf( 'Time function must return "int|float"(e.g. "%s"), instead return type is "%s".', 'fn(): int|float => 0.0', diff --git a/lib/Benchmark/Stopwatch/Builder/StopwatchBuilder.php b/lib/Benchmark/Stopwatch/Builder/StopwatchBuilder.php index 50d4e399..72d8748b 100644 --- a/lib/Benchmark/Stopwatch/Builder/StopwatchBuilder.php +++ b/lib/Benchmark/Stopwatch/Builder/StopwatchBuilder.php @@ -20,18 +20,17 @@ public function build(): IStopwatch { $this->validate(); - return - new Stopwatch( - timer: $this->timer, - measurementFactory: $this->measurementFactory, - ); + return new Stopwatch( + timer: $this->timer, + measurementFactory: $this->measurementFactory, + ); } private function validate(): void { match (true) { - null === $this->timer => throw new LogicException('Timer is not set.'), - null === $this->measurementFactory => throw new LogicException('Measurement factory is not set.'), + $this->timer === null => throw new LogicException('Timer is not set.'), + $this->measurementFactory === null => throw new LogicException('Measurement factory is not set.'), default => null, }; } diff --git a/lib/Benchmark/Stopwatch/Measurement.php b/lib/Benchmark/Stopwatch/Measurement.php index 9b5f8808..3dea4c7d 100644 --- a/lib/Benchmark/Stopwatch/Measurement.php +++ b/lib/Benchmark/Stopwatch/Measurement.php @@ -7,14 +7,16 @@ use AlecRabbit\Benchmark\Contract\IMeasurement; use AlecRabbit\Benchmark\Exception\MeasurementException; -class Measurement implements IMeasurement +final class Measurement implements IMeasurement { - protected const DEFAULT_THRESHOLD = 2; - protected array $data = []; - protected int|float $average; - protected int|float $min; - protected int|float $max; - protected int $count = 0; + private const DEFAULT_THRESHOLD = 2; + + /** @var array */ + private array $data = []; + private int|float $average; + private int|float $min; + private int|float $max; + private int $count = 0; public function __construct( protected readonly int $threshold = self::DEFAULT_THRESHOLD, @@ -44,18 +46,16 @@ public function add(int|float $value): void } } - protected function reachedThreshold(): bool + private function reachedThreshold(): bool { return count($this->data) >= $this->threshold; } - /** @inheritDoc */ public function getAverage(): int|float { return $this->average ?? throw new MeasurementException('Not enough data.'); } - /** @inheritDoc */ public function getAny(): int|float { $count = count($this->data); @@ -72,13 +72,11 @@ public function getCount(): int return $this->count; } - /** @inheritDoc */ public function getMin(): int|float { return $this->min ?? throw new MeasurementException('Min is not set.'); } - /** @inheritDoc */ public function getMax(): int|float { return $this->max ?? throw new MeasurementException('Max is not set.'); diff --git a/lib/Benchmark/Stopwatch/MicrosecondTimer.php b/lib/Benchmark/Stopwatch/MicrosecondTimer.php index 98f5f557..15efa3b5 100644 --- a/lib/Benchmark/Stopwatch/MicrosecondTimer.php +++ b/lib/Benchmark/Stopwatch/MicrosecondTimer.php @@ -16,9 +16,9 @@ final class MicrosecondTimer extends ATimer public function __construct( TimeUnit $unit = self::UNIT, - Closure $timeFunction = null, + ?Closure $timeFunction = null, ) { - if (null === $timeFunction) { + if ($timeFunction === null) { $timeFunction = static fn(): float => microtime(true) * 1_000_000; // microseconds } diff --git a/lib/Benchmark/Stopwatch/ResultFormatter.php b/lib/Benchmark/Stopwatch/ResultFormatter.php index 26f7d9e6..02e80e2b 100644 --- a/lib/Benchmark/Stopwatch/ResultFormatter.php +++ b/lib/Benchmark/Stopwatch/ResultFormatter.php @@ -13,7 +13,7 @@ final class ResultFormatter implements IResultFormatter private string $format; public function __construct( - string $format = null, + ?string $format = null, protected string $shortFormat = self::FORMAT, string $formatPrototype = '%s [%s/%s]', protected string $units = 'μs', @@ -29,18 +29,16 @@ public function __construct( ); } - public function format(IResult $result): string { - return - sprintf( - $this->format, - $result->getAverage(), - $this->units, - $result->getMax(), - $this->units, - $result->getMin(), - $this->units, - ); + return sprintf( + $this->format, + $result->getAverage(), + $this->units, + $result->getMax(), + $this->units, + $result->getMin(), + $this->units, + ); } } diff --git a/lib/Benchmark/Stopwatch/ResultShortFormatter.php b/lib/Benchmark/Stopwatch/ResultShortFormatter.php index 89e358d3..f8b21555 100644 --- a/lib/Benchmark/Stopwatch/ResultShortFormatter.php +++ b/lib/Benchmark/Stopwatch/ResultShortFormatter.php @@ -19,11 +19,10 @@ public function __construct( public function format(IResult $result): string { - return - sprintf( - $this->format, - $result->getAverage(), - $this->units, - ); + return sprintf( + $this->format, + $result->getAverage(), + $this->units, + ); } } diff --git a/lib/Benchmark/Stopwatch/Stopwatch.php b/lib/Benchmark/Stopwatch/Stopwatch.php index 8a5c05fe..9529ec67 100644 --- a/lib/Benchmark/Stopwatch/Stopwatch.php +++ b/lib/Benchmark/Stopwatch/Stopwatch.php @@ -8,20 +8,20 @@ use AlecRabbit\Benchmark\Contract\IMeasurement; use AlecRabbit\Benchmark\Contract\IStopwatch; use AlecRabbit\Benchmark\Contract\ITimer; -use AlecRabbit\Benchmark\Contract\TimeUnit; use RuntimeException; use Traversable; -class Stopwatch implements IStopwatch +final class Stopwatch implements IStopwatch { - protected const COUNT = 100; + /** @var array */ + private array $current = []; - protected array $current = []; - protected array $measurements = []; + /** @var array */ + private array $measurements = []; public function __construct( - protected readonly ITimer $timer, - protected readonly IMeasurementFactory $measurementFactory, + private readonly ITimer $timer, + private readonly IMeasurementFactory $measurementFactory, ) { } @@ -43,7 +43,7 @@ public function stop(string $key): void } } - protected function addMeasurement(string $key, mixed $value): void + private function addMeasurement(string $key, mixed $value): void { if (!isset($this->measurements[$key])) { $this->measurements[$key] = $this->createMeasurement(); @@ -51,10 +51,9 @@ protected function addMeasurement(string $key, mixed $value): void $this->measurements[$key]->add($value); } - protected function createMeasurement(): IMeasurement + private function createMeasurement(): IMeasurement { - return - $this->measurementFactory->create(); + return $this->measurementFactory->create(); } public function getMeasurements(): Traversable diff --git a/src/Spinner/Helper/BytesFormatter.php b/lib/Lib/Helper/BytesFormatter.php similarity index 83% rename from src/Spinner/Helper/BytesFormatter.php rename to lib/Lib/Helper/BytesFormatter.php index b73a651d..d6d6a0df 100644 --- a/src/Spinner/Helper/BytesFormatter.php +++ b/lib/Lib/Helper/BytesFormatter.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace AlecRabbit\Spinner\Helper; +namespace AlecRabbit\Lib\Helper; -use AlecRabbit\Spinner\Helper\Contract\IBytesFormatter; +use AlecRabbit\Lib\Helper\Contract\IBytesFormatter; final class BytesFormatter implements IBytesFormatter { diff --git a/src/Spinner/Helper/Contract/IBytesFormatter.php b/lib/Lib/Helper/Contract/IBytesFormatter.php similarity index 70% rename from src/Spinner/Helper/Contract/IBytesFormatter.php rename to lib/Lib/Helper/Contract/IBytesFormatter.php index 825085e6..c33fc43c 100644 --- a/src/Spinner/Helper/Contract/IBytesFormatter.php +++ b/lib/Lib/Helper/Contract/IBytesFormatter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace AlecRabbit\Spinner\Helper\Contract; +namespace AlecRabbit\Lib\Helper\Contract; interface IBytesFormatter { diff --git a/lib/Lib/Helper/MemoryUsage.php b/lib/Lib/Helper/MemoryUsage.php new file mode 100644 index 00000000..e01a5a35 --- /dev/null +++ b/lib/Lib/Helper/MemoryUsage.php @@ -0,0 +1,31 @@ +formatter = $formatter ?? new BytesFormatter(); + } + + public function report(?int $peakBytes = null, ?int $bytes = null): string + { + return sprintf( + $this->format, + $this->formatter->format($peakBytes ?? memory_get_peak_usage(true)), + $this->formatter->format($bytes ?? memory_get_usage(true)), + ); + } +} diff --git a/lib/Lib/Spinner/BenchmarkFacade.php b/lib/Lib/Spinner/BenchmarkFacade.php new file mode 100644 index 00000000..ef7bf696 --- /dev/null +++ b/lib/Lib/Spinner/BenchmarkFacade.php @@ -0,0 +1,25 @@ +get(IReportPrinter::class); + } + + public static function getBenchmarkResultsFactory(): IBenchmarkResultsFactory + { + return self::getContainer()->get(IBenchmarkResultsFactory::class); + } +} diff --git a/lib/Benchmark/Spinner/Builder/BenchmarkingDriverBuilder.php b/lib/Lib/Spinner/Builder/BenchmarkingDriverBuilder.php similarity index 74% rename from lib/Benchmark/Spinner/Builder/BenchmarkingDriverBuilder.php rename to lib/Lib/Spinner/Builder/BenchmarkingDriverBuilder.php index bee94746..68f30a5e 100644 --- a/lib/Benchmark/Spinner/Builder/BenchmarkingDriverBuilder.php +++ b/lib/Lib/Spinner/Builder/BenchmarkingDriverBuilder.php @@ -2,18 +2,17 @@ declare(strict_types=1); -namespace AlecRabbit\Benchmark\Spinner\Builder; +namespace AlecRabbit\Lib\Spinner\Builder; use AlecRabbit\Benchmark\Contract\IBenchmark; -use AlecRabbit\Benchmark\Spinner\BenchmarkingDriver; -use AlecRabbit\Benchmark\Spinner\Contract\Builder\IBenchmarkingDriverBuilder; -use AlecRabbit\Benchmark\Spinner\Contract\IBenchmarkingDriver; +use AlecRabbit\Lib\Spinner\Contract\Builder\IBenchmarkingDriverBuilder; +use AlecRabbit\Lib\Spinner\Contract\IBenchmarkingDriver; +use AlecRabbit\Lib\Spinner\Core\BenchmarkingDriver; use AlecRabbit\Spinner\Core\Contract\IDriver; use AlecRabbit\Spinner\Exception\LogicException; final class BenchmarkingDriverBuilder implements IBenchmarkingDriverBuilder { - private ?IDriver $driver = null; private ?IBenchmark $benchmark = null; @@ -21,11 +20,10 @@ public function build(): IBenchmarkingDriver { $this->validate(); - return - new BenchmarkingDriver( - driver: $this->driver, - benchmark: $this->benchmark, - ); + return new BenchmarkingDriver( + driver: $this->driver, + benchmark: $this->benchmark, + ); } /** diff --git a/lib/Benchmark/Spinner/Contract/Builder/IBenchmarkingDriverBuilder.php b/lib/Lib/Spinner/Contract/Builder/IBenchmarkingDriverBuilder.php similarity index 79% rename from lib/Benchmark/Spinner/Contract/Builder/IBenchmarkingDriverBuilder.php rename to lib/Lib/Spinner/Contract/Builder/IBenchmarkingDriverBuilder.php index f2a0c424..4dab89c8 100644 --- a/lib/Benchmark/Spinner/Contract/Builder/IBenchmarkingDriverBuilder.php +++ b/lib/Lib/Spinner/Contract/Builder/IBenchmarkingDriverBuilder.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace AlecRabbit\Benchmark\Spinner\Contract\Builder; +namespace AlecRabbit\Lib\Spinner\Contract\Builder; use AlecRabbit\Benchmark\Contract\IBenchmark; -use AlecRabbit\Benchmark\Spinner\Contract\IBenchmarkingDriver; +use AlecRabbit\Lib\Spinner\Contract\IBenchmarkingDriver; use AlecRabbit\Spinner\Core\Contract\IDriver; use AlecRabbit\Spinner\Exception\LogicException; diff --git a/lib/Benchmark/Spinner/Contract/Factory/IBenchmarkingDriverFactory.php b/lib/Lib/Spinner/Contract/Factory/IBenchmarkingDriverFactory.php similarity index 63% rename from lib/Benchmark/Spinner/Contract/Factory/IBenchmarkingDriverFactory.php rename to lib/Lib/Spinner/Contract/Factory/IBenchmarkingDriverFactory.php index 206fa9c4..97fbabfa 100644 --- a/lib/Benchmark/Spinner/Contract/Factory/IBenchmarkingDriverFactory.php +++ b/lib/Lib/Spinner/Contract/Factory/IBenchmarkingDriverFactory.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace AlecRabbit\Benchmark\Spinner\Contract\Factory; +namespace AlecRabbit\Lib\Spinner\Contract\Factory; -use AlecRabbit\Benchmark\Spinner\Contract\IBenchmarkingDriver; +use AlecRabbit\Lib\Spinner\Contract\IBenchmarkingDriver; use AlecRabbit\Spinner\Core\Factory\Contract\IDriverFactory; interface IBenchmarkingDriverFactory extends IDriverFactory diff --git a/lib/Lib/Spinner/Contract/Factory/IDriverLinkerWithOutputFactory.php b/lib/Lib/Spinner/Contract/Factory/IDriverLinkerWithOutputFactory.php new file mode 100644 index 00000000..52eb6783 --- /dev/null +++ b/lib/Lib/Spinner/Contract/Factory/IDriverLinkerWithOutputFactory.php @@ -0,0 +1,12 @@ +benchmark->run( - $this->refineKey(__FUNCTION__), - $this->driver->has(...), - $spinner - ); + return $this->benchmark->run( + $this->refineKey(__FUNCTION__), + $this->driver->has(...), + $spinner + ); } public function remove(ISpinner $spinner): void @@ -86,21 +85,19 @@ public function finalize(?string $finalMessage = null): void public function wrap(Closure $callback): Closure { - return - $this->benchmark->run( - $this->refineKey(__FUNCTION__), - $this->driver->wrap(...), - $callback - ); + return $this->benchmark->run( + $this->refineKey(__FUNCTION__), + $this->driver->wrap(...), + $callback + ); } public function getInterval(): IInterval { - return - $this->benchmark->run( - $this->refineKey(__FUNCTION__), - $this->driver->getInterval(...), - ); + return $this->benchmark->run( + $this->refineKey(__FUNCTION__), + $this->driver->getInterval(...), + ); } public function update(ISubject $subject): void diff --git a/lib/Lib/Spinner/Core/DriverLinkerWithOutput.php b/lib/Lib/Spinner/Core/DriverLinkerWithOutput.php new file mode 100644 index 00000000..3bde3d8e --- /dev/null +++ b/lib/Lib/Spinner/Core/DriverLinkerWithOutput.php @@ -0,0 +1,58 @@ +attach($this); // setting $this as an observer + + try { + // Observer can not be overwritten so `attach()` will throw and should + // be the last line in the method: + // + // # $driver->attach($this); // <-- this line + $this->linker->link($driver); + } catch (ObserverCanNotBeOverwritten $_) { + // ignore + } + + $this->write($driver); + } + + private function write(IDriver $driver): void + { + $this->output->write( + $this->formatter->format($driver) + ); + } + + public function update(ISubject $subject): void + { + $this->linker->update($subject); + + if ($subject instanceof IDriver) { + $this->write($subject); + } + } +} diff --git a/lib/Benchmark/Spinner/Factory/BenchmarkingDriverFactory.php b/lib/Lib/Spinner/Factory/BenchmarkingDriverFactory.php similarity index 52% rename from lib/Benchmark/Spinner/Factory/BenchmarkingDriverFactory.php rename to lib/Lib/Spinner/Factory/BenchmarkingDriverFactory.php index ef8fe4c2..11f37e42 100644 --- a/lib/Benchmark/Spinner/Factory/BenchmarkingDriverFactory.php +++ b/lib/Lib/Spinner/Factory/BenchmarkingDriverFactory.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace AlecRabbit\Benchmark\Spinner\Factory; +namespace AlecRabbit\Lib\Spinner\Factory; use AlecRabbit\Benchmark\Contract\Factory\IBenchmarkFactory; -use AlecRabbit\Benchmark\Spinner\Contract\Builder\IBenchmarkingDriverBuilder; -use AlecRabbit\Benchmark\Spinner\Contract\Factory\IBenchmarkingDriverFactory; -use AlecRabbit\Benchmark\Spinner\Contract\IBenchmarkingDriver; +use AlecRabbit\Lib\Spinner\Contract\Builder\IBenchmarkingDriverBuilder; +use AlecRabbit\Lib\Spinner\Contract\Factory\IBenchmarkingDriverFactory; +use AlecRabbit\Lib\Spinner\Contract\IBenchmarkingDriver; use AlecRabbit\Spinner\Core\Factory\Contract\IDriverFactory; final class BenchmarkingDriverFactory implements IBenchmarkingDriverFactory @@ -21,11 +21,10 @@ public function __construct( public function create(): IBenchmarkingDriver { - return - $this->benchmarkingDriverBuilder - ->withDriver($this->driverFactory->create()) - ->withBenchmark($this->benchmarkFactory->create()) - ->build() + return $this->benchmarkingDriverBuilder + ->withDriver($this->driverFactory->create()) + ->withBenchmark($this->benchmarkFactory->create()) + ->build() ; } } diff --git a/lib/Benchmark/Spinner/Factory/BenchmarkingDriverProviderFactory.php b/lib/Lib/Spinner/Factory/BenchmarkingDriverProviderFactory.php similarity index 76% rename from lib/Benchmark/Spinner/Factory/BenchmarkingDriverProviderFactory.php rename to lib/Lib/Spinner/Factory/BenchmarkingDriverProviderFactory.php index 3e097bc6..f6051039 100644 --- a/lib/Benchmark/Spinner/Factory/BenchmarkingDriverProviderFactory.php +++ b/lib/Lib/Spinner/Factory/BenchmarkingDriverProviderFactory.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace AlecRabbit\Benchmark\Spinner\Factory; +namespace AlecRabbit\Lib\Spinner\Factory; -use AlecRabbit\Benchmark\Spinner\Contract\Factory\IBenchmarkingDriverFactory; +use AlecRabbit\Lib\Spinner\Contract\Factory\IBenchmarkingDriverFactory; use AlecRabbit\Spinner\Core\Contract\IDriverSetup; use AlecRabbit\Spinner\Core\Factory\A\ADriverProviderFactory; diff --git a/lib/Lib/Spinner/Factory/DriverLinkerWithOutputFactory.php b/lib/Lib/Spinner/Factory/DriverLinkerWithOutputFactory.php new file mode 100644 index 00000000..e1bace3f --- /dev/null +++ b/lib/Lib/Spinner/Factory/DriverLinkerWithOutputFactory.php @@ -0,0 +1,35 @@ +driverLinkerFactory->create(); + + if ($linker instanceof DummyDriverLinker) { + return $linker; + } + + return new DriverLinkerWithOutput( + $linker, + $this->output, + ); + } +} diff --git a/lib/Lib/Spinner/IntervalFormatter.php b/lib/Lib/Spinner/IntervalFormatter.php new file mode 100644 index 00000000..574c23b5 --- /dev/null +++ b/lib/Lib/Spinner/IntervalFormatter.php @@ -0,0 +1,21 @@ +getInterval()->toMilliseconds() + ); + } +} diff --git a/phpunit.coverage.path.xml b/phpunit.coverage.path.xml new file mode 100644 index 00000000..a26f0288 --- /dev/null +++ b/phpunit.coverage.path.xml @@ -0,0 +1,45 @@ + + + + + tests/Spinner/Functional + + + tests/Spinner/Unit + + + tests/Lib/Functional + + + tests/Lib/Unit + + + tests/Benchmark/Functional + + + tests/Benchmark/Unit + + + + + + + + + + + lib + src + + + diff --git a/phpunit.coverage.xml b/phpunit.coverage.xml index edf6dd14..0b1b97e9 100644 --- a/phpunit.coverage.xml +++ b/phpunit.coverage.xml @@ -11,17 +11,29 @@ beStrictAboutCoverageMetadata="false" > - - tests/Functional - - - tests/Unit - + + tests/Spinner/Functional + + + tests/Spinner/Unit + + + tests/Lib/Functional + + + tests/Lib/Unit + + + tests/Benchmark/Functional + + + tests/Benchmark/Unit + - + diff --git a/phpunit.testdox.xml b/phpunit.testdox.xml index aaaab76a..518b58b0 100644 --- a/phpunit.testdox.xml +++ b/phpunit.testdox.xml @@ -12,12 +12,24 @@ testdox="true" > - - tests/Functional - - - tests/Unit - + + tests/Spinner/Functional + + + tests/Spinner/Unit + + + tests/Lib/Functional + + + tests/Lib/Unit + + + tests/Benchmark/Functional + + + tests/Benchmark/Unit + diff --git a/phpunit.xml b/phpunit.xml index d1083f9a..b5418917 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -12,12 +12,24 @@ stopOnError="false" stopOnFailure="false" > - - - tests/Functional - - - tests/Unit - - + + + tests/Spinner/Functional + + + tests/Spinner/Unit + + + tests/Lib/Functional + + + tests/Lib/Unit + + + tests/Benchmark/Functional + + + tests/Benchmark/Unit + + diff --git a/src/Spinner/Asynchronous/React/ReactLoopAdapter.php b/src/Spinner/Asynchronous/React/ReactLoopAdapter.php index 54791e07..89fe2b6c 100644 --- a/src/Spinner/Asynchronous/React/ReactLoopAdapter.php +++ b/src/Spinner/Asynchronous/React/ReactLoopAdapter.php @@ -5,7 +5,7 @@ namespace AlecRabbit\Spinner\Asynchronous\React; use AlecRabbit\Spinner\Core\Loop\Contract\A\ALoopAdapter; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; use Closure; use React\EventLoop\LoopInterface; use React\EventLoop\TimerInterface; @@ -48,7 +48,7 @@ public function stop(): void public function cancel(mixed $timer): void { if (!$timer instanceof TimerInterface) { - throw new InvalidArgumentException( + throw new InvalidArgument( sprintf( 'Invalid timer type: %s, expected %s', gettype($timer), diff --git a/src/Spinner/Asynchronous/React/ReactLoopCreator.php b/src/Spinner/Asynchronous/React/ReactLoopCreator.php index 8503c611..5381f6ae 100644 --- a/src/Spinner/Asynchronous/React/ReactLoopCreator.php +++ b/src/Spinner/Asynchronous/React/ReactLoopCreator.php @@ -12,7 +12,6 @@ final class ReactLoopCreator implements ILoopCreator { public function create(): ILoop { - return - new ReactLoopAdapter(Loop::get()); + return new ReactLoopAdapter(Loop::get()); } } diff --git a/src/Spinner/Asynchronous/Revolt/RevoltLoopAdapter.php b/src/Spinner/Asynchronous/Revolt/RevoltLoopAdapter.php index 54f8a3c5..672cf836 100644 --- a/src/Spinner/Asynchronous/Revolt/RevoltLoopAdapter.php +++ b/src/Spinner/Asynchronous/Revolt/RevoltLoopAdapter.php @@ -5,7 +5,7 @@ namespace AlecRabbit\Spinner\Asynchronous\Revolt; use AlecRabbit\Spinner\Core\Loop\Contract\A\ALoopAdapter; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; use Closure; use Revolt\EventLoop; @@ -24,7 +24,7 @@ public function __construct( public function cancel(mixed $timer): void { if (!is_string($timer)) { - throw new InvalidArgumentException( + throw new InvalidArgument( sprintf( 'Invalid timer type: %s, expected string', gettype($timer) diff --git a/src/Spinner/Asynchronous/Revolt/RevoltLoopCreator.php b/src/Spinner/Asynchronous/Revolt/RevoltLoopCreator.php index c89f3e11..c5a2048b 100644 --- a/src/Spinner/Asynchronous/Revolt/RevoltLoopCreator.php +++ b/src/Spinner/Asynchronous/Revolt/RevoltLoopCreator.php @@ -12,7 +12,6 @@ final class RevoltLoopCreator implements ILoopCreator { public function create(): ILoop { - return - new RevoltLoopAdapter(EventLoop::getDriver()); + return new RevoltLoopAdapter(EventLoop::getDriver()); } } diff --git a/src/Spinner/Container/A/AContainerEnclosure.php b/src/Spinner/Container/A/AContainerEnclosure.php index 04f9c5a5..2cdbe5c7 100644 --- a/src/Spinner/Container/A/AContainerEnclosure.php +++ b/src/Spinner/Container/A/AContainerEnclosure.php @@ -4,12 +4,19 @@ namespace AlecRabbit\Spinner\Container\A; +use AlecRabbit\Spinner\Container\Contract\IContainer; +use AlecRabbit\Spinner\Container\Contract\IContainerFactory; +use AlecRabbit\Spinner\Container\Contract\IDefinitionRegistry; +use AlecRabbit\Spinner\Container\DefinitionRegistry; use AlecRabbit\Spinner\Container\Exception\ContainerException; -use Psr\Container\ContainerInterface; +use AlecRabbit\Spinner\Container\Factory\ContainerFactory; abstract class AContainerEnclosure { - private static ?ContainerInterface $container = null; + private static ?IContainer $container = null; + + /** @var class-string */ + private static string $factoryClass = ContainerFactory::class; /** * @codeCoverageIgnore @@ -19,14 +26,49 @@ final protected function __construct() // No instances allowed. } - public static function useContainer(?ContainerInterface $container): void + public static function useContainerFactory(string $factoryClass): void + { + if (!is_subclass_of($factoryClass, IContainerFactory::class)) { + throw new ContainerException( + sprintf( + 'Factory class must implement [%s]. "%s" given.', + IContainerFactory::class, + $factoryClass, + ) + ); + } + + self::$factoryClass = $factoryClass; + } + + final protected static function getContainer(): IContainer + { + if (self::$container === null) { + self::$container = self::createContainer(); + } + return self::$container; + } + + /** + * This method is used to override container instance in tests. + */ + final protected static function setContainer(?IContainer $container): void { self::$container = $container; } - final protected static function getContainer(): ContainerInterface + private static function createContainer(): IContainer + { + return self::getFactory()->create(); + } + + private static function getFactory(): IContainerFactory + { + return new (self::$factoryClass)(self::getDefinitionRegistry()); + } + + private static function getDefinitionRegistry(): IDefinitionRegistry { - return - self::$container ?? throw new ContainerException('Container is not set.'); + return DefinitionRegistry::getInstance(); } } diff --git a/src/Spinner/Container/Adapter/ContainerAdapter.php b/src/Spinner/Container/Adapter/ContainerAdapter.php new file mode 100644 index 00000000..84cd3664 --- /dev/null +++ b/src/Spinner/Container/Adapter/ContainerAdapter.php @@ -0,0 +1,33 @@ +container->get($id); + } + + /** @inheritDoc */ + public function has(string $id): bool + { + return $this->container->has($id); + } +} diff --git a/src/Spinner/Container/Builder/ServiceSpawnerBuilder.php b/src/Spinner/Container/Builder/ServiceSpawnerBuilder.php new file mode 100644 index 00000000..a700588b --- /dev/null +++ b/src/Spinner/Container/Builder/ServiceSpawnerBuilder.php @@ -0,0 +1,67 @@ +container = $container; + return $clone; + } + + public function build(): IServiceSpawner + { + $this->validate(); + + return new ServiceSpawner( + container: $this->container, + circularDependencyDetector: $this->circularDependencyDetector, + serviceObjectFactory: $this->serviceObjectFactory, + ); + } + + private function validate(): void + { + match (true) { + $this->container === null => throw new LogicException('Container is not set.'), + $this->circularDependencyDetector === null => throw new LogicException( + 'Circular dependency detector is not set.' + ), + $this->serviceObjectFactory === null => throw new LogicException('Service object factory is not set.'), + default => null, + }; + } + + public function withCircularDependencyDetector(ICircularDependencyDetector $detector): IServiceSpawnerBuilder + { + $clone = clone $this; + $clone->circularDependencyDetector = $detector; + return $clone; + } + + public function withServiceObjectFactory(IServiceObjectFactory $serviceObjectFactory): IServiceSpawnerBuilder + { + $clone = clone $this; + $clone->serviceObjectFactory = $serviceObjectFactory; + return $clone; + } +} diff --git a/src/Spinner/Container/CircularDependencyDetector.php b/src/Spinner/Container/CircularDependencyDetector.php new file mode 100644 index 00000000..53a10973 --- /dev/null +++ b/src/Spinner/Container/CircularDependencyDetector.php @@ -0,0 +1,37 @@ +stack->getArrayCopy(), true)) { + throw new CircularDependencyDetected($this->stack); + } + + $this->stack->append($id); + } + + public function pop(): void + { + $this->stack->offsetUnset( + array_key_last( + $this->stack->getArrayCopy() + ) + ); + } +} diff --git a/src/Spinner/Container/Container.php b/src/Spinner/Container/Container.php index 9c49137e..a0f8fd66 100644 --- a/src/Spinner/Container/Container.php +++ b/src/Spinner/Container/Container.php @@ -5,41 +5,45 @@ namespace AlecRabbit\Spinner\Container; use AlecRabbit\Spinner\Container\Contract\IContainer; +use AlecRabbit\Spinner\Container\Contract\IService; +use AlecRabbit\Spinner\Container\Contract\IServiceDefinition; use AlecRabbit\Spinner\Container\Contract\IServiceSpawner; -use AlecRabbit\Spinner\Container\Exception\CircularDependencyException; +use AlecRabbit\Spinner\Container\Contract\IServiceSpawnerFactory; use AlecRabbit\Spinner\Container\Exception\ContainerException; -use AlecRabbit\Spinner\Container\Exception\NotInContainerException; +use AlecRabbit\Spinner\Container\Exception\NotFoundInContainer; use ArrayObject; -use Closure; +use Psr\Container\ContainerExceptionInterface; use Throwable; use Traversable; -final class Container implements IContainer +final readonly class Container implements IContainer { private IServiceSpawner $serviceSpawner; - /** @var ArrayObject */ + /** @var ArrayObject */ private ArrayObject $definitions; - /** @var ArrayObject */ + /** @var ArrayObject */ private ArrayObject $services; - private ArrayObject $dependencyStack; - - public function __construct(Closure $spawnerCreatorCb, ?Traversable $definitions = null) - { - $this->serviceSpawner = $this->createSpawner($spawnerCreatorCb); + /** + * @param Traversable|null $definitions + */ + public function __construct( + IServiceSpawnerFactory $spawnerFactory, + ?Traversable $definitions = null, + ) { + $this->serviceSpawner = $spawnerFactory->create($this); /** @psalm-suppress MixedPropertyTypeCoercion */ $this->definitions = new ArrayObject(); /** @psalm-suppress MixedPropertyTypeCoercion */ $this->services = new ArrayObject(); - $this->dependencyStack = new ArrayObject(); if ($definitions) { /** - * @var string $id - * @var callable|object|string $definition + * @var int|string $id + * @var IServiceDefinition $definition */ foreach ($definitions as $id => $definition) { $this->register($id, $definition); @@ -47,35 +51,19 @@ public function __construct(Closure $spawnerCreatorCb, ?Traversable $definitions } } - /** - * @psalm-suppress MixedInferredReturnType - * @psalm-suppress MixedReturnStatement - */ - protected function createSpawner(Closure $spawnerCreatorCb): IServiceSpawner + private function register(int|string $id, IServiceDefinition $definition): void { - return $spawnerCreatorCb($this); - } - - private function register(string $id, mixed $definition): void - { - $this->assertDefinition($definition); - - $this->assertNotRegistered($id); + if (is_int($id)) { + $id = $definition->getId(); + } - /** @var callable|object|string $definition */ - $this->definitions[$id] = $definition; + $this->registerDefinition($id, $definition); } - private function assertDefinition(mixed $definition): void + private function registerDefinition(string $id, IServiceDefinition $definition): void { - if (!is_callable($definition) && !is_object($definition) && !is_string($definition)) { - throw new ContainerException( - sprintf( - 'Definition should be callable, object or string, "%s" given.', - gettype($definition), - ) - ); - } + $this->assertNotRegistered($id); + $this->definitions->offsetSet($id, $definition); } private function assertNotRegistered(string $id): void @@ -91,19 +79,24 @@ private function assertNotRegistered(string $id): void } public function has(string $id): bool + { + return $this->hasDefinition($id); + } + + private function hasDefinition(string $id): bool { return $this->definitions->offsetExists($id); } - /** @inheritDoc */ + /** + * @inheritDoc + * + * @psalm-suppress MixedInferredReturnType + */ public function get(string $id): mixed { - if ($this->hasService($id)) { - return $this->services[$id]; - } - if (!$this->has($id)) { - throw new NotInContainerException( + throw new NotFoundInContainer( sprintf( 'There is no service with id "%s" in the container.', $id, @@ -111,15 +104,13 @@ public function get(string $id): mixed ); } - $this->addDependencyToStack($id); - - $definition = $this->definitions[$id]; - - $this->services[$id] = $this->getService($id, $definition); - - $this->removeDependencyFromStack(); + if ($this->hasService($id)) { + /** @psalm-suppress MixedReturnStatement */ + return $this->retrieveService($id)->getValue(); + } - return $this->services[$id]; + /** @psalm-suppress MixedReturnStatement */ + return $this->getService($id)->getValue(); } private function hasService(string $id): bool @@ -127,44 +118,56 @@ private function hasService(string $id): bool return $this->services->offsetExists($id); } - private function addDependencyToStack(string $id): void + private function retrieveService(string $id): IService { - $this->assertDependencyIsNotInStack($id); - - $this->dependencyStack->append($id); + /** @psalm-suppress MixedReturnStatement */ + return $this->services->offsetGet($id); } - private function assertDependencyIsNotInStack(string $id): void + /** + * @throws ContainerExceptionInterface + */ + private function getService(string $id): IService { - if (in_array($id, $this->dependencyStack->getArrayCopy(), true)) { - // @codeCoverageIgnoreStart - throw new CircularDependencyException($this->dependencyStack); - // @codeCoverageIgnoreEnd + $definition = $this->getDefinition($id); + + $service = $this->spawn($definition); + + if ($service->isStorable()) { + $this->services->offsetSet($id, $service); } + + /** @psalm-suppress MixedReturnStatement */ + return $service; + } + + private function getDefinition(string $id): IServiceDefinition + { + return $this->definitions->offsetGet($id); } - private function getService(string $id, callable|object|string $definition): object + /** + * @throws ContainerExceptionInterface + */ + private function spawn(IServiceDefinition $definition): IService { try { return $this->serviceSpawner->spawn($definition); } catch (Throwable $e) { + $details = + sprintf( + '[%s]: "%s".', + get_debug_type($e), + $e->getMessage(), + ); + throw new ContainerException( sprintf( - 'Could not instantiate service with id "%s".%s', - $id, - sprintf( - ' [%s]: "%s".', - get_debug_type($e), - $e->getMessage(), - ), + 'Could not instantiate service. %s', + $details, ), previous: $e, ); } } - - private function removeDependencyFromStack(): void - { - $this->dependencyStack->offsetUnset($this->dependencyStack->count() - 1); - } } diff --git a/src/Spinner/Container/Contract/ICircularDependencyDetector.php b/src/Spinner/Container/Contract/ICircularDependencyDetector.php new file mode 100644 index 00000000..d4a9d4d0 --- /dev/null +++ b/src/Spinner/Container/Contract/ICircularDependencyDetector.php @@ -0,0 +1,12 @@ + $id + * @param class-string $id * * @psalm-return T * * @throws ContainerExceptionInterface Error while retrieving the entry. * @throws NotFoundExceptionInterface No entry was found for **this** identifier. + * + * @psalm-suppress MoreSpecificImplementedParamType */ public function get(string $id); - /** @inheritDoc */ public function has(string $id): bool; } diff --git a/src/Spinner/Container/Contract/IContainerFactory.php b/src/Spinner/Container/Contract/IContainerFactory.php index 277ac0e9..eb10c6b1 100644 --- a/src/Spinner/Container/Contract/IContainerFactory.php +++ b/src/Spinner/Container/Contract/IContainerFactory.php @@ -6,5 +6,7 @@ interface IContainerFactory { + public function __construct(IDefinitionRegistry $definitionRegistry); + public function create(): IContainer; } diff --git a/src/Spinner/Container/Contract/IDefinitionRegistry.php b/src/Spinner/Container/Contract/IDefinitionRegistry.php index 97cb4b63..a5c2c7e0 100644 --- a/src/Spinner/Container/Contract/IDefinitionRegistry.php +++ b/src/Spinner/Container/Contract/IDefinitionRegistry.php @@ -8,7 +8,10 @@ interface IDefinitionRegistry { + /** + * @return Traversable + */ public function load(): Traversable; - public function bind(string $typeId, object|callable|string $definition): void; + public function bind(IServiceDefinition $serviceDefinition): void; } diff --git a/src/Spinner/Container/Contract/IIsStorableSolver.php b/src/Spinner/Container/Contract/IIsStorableSolver.php new file mode 100644 index 00000000..8bc35fee --- /dev/null +++ b/src/Spinner/Container/Contract/IIsStorableSolver.php @@ -0,0 +1,10 @@ +getOptions() === IServiceDefinition::SINGLETON; + } +} diff --git a/src/Spinner/Container/DefinitionRegistry.php b/src/Spinner/Container/DefinitionRegistry.php index fdfc6d0a..45f4aeba 100644 --- a/src/Spinner/Container/DefinitionRegistry.php +++ b/src/Spinner/Container/DefinitionRegistry.php @@ -5,21 +5,27 @@ namespace AlecRabbit\Spinner\Container; use AlecRabbit\Spinner\Container\Contract\IDefinitionRegistry; +use AlecRabbit\Spinner\Container\Contract\IServiceDefinition; +use ArrayObject; use Traversable; final class DefinitionRegistry implements IDefinitionRegistry { private static ?IDefinitionRegistry $instance = null; - private array $definitions = []; - private function __construct() - { - // Can be instantiated only by getInstance() + /** @var ArrayObject */ + private readonly ArrayObject $definitions; + + private function __construct( + ArrayObject $definitions = new ArrayObject(), + ) { + /** @var ArrayObject $definitions */ + $this->definitions = $definitions; } public static function getInstance(): IDefinitionRegistry { - if (null === self::$instance) { + if (self::$instance === null) { self::$instance = new self(); } return self::$instance; @@ -30,8 +36,8 @@ public function load(): Traversable yield from $this->definitions; } - public function bind(string $typeId, callable|object|string $definition): void + public function bind(IServiceDefinition $serviceDefinition): void { - $this->definitions[$typeId] = $definition; + $this->definitions->offsetSet($serviceDefinition->getId(), $serviceDefinition); } } diff --git a/src/Spinner/Container/Exception/CircularDependencyException.php b/src/Spinner/Container/Exception/CircularDependencyDetected.php similarity index 90% rename from src/Spinner/Container/Exception/CircularDependencyException.php rename to src/Spinner/Container/Exception/CircularDependencyDetected.php index 71213bb2..daf93fe8 100644 --- a/src/Spinner/Container/Exception/CircularDependencyException.php +++ b/src/Spinner/Container/Exception/CircularDependencyDetected.php @@ -10,7 +10,7 @@ /** * @codeCoverageIgnore */ -final class CircularDependencyException extends ContainerException +final class CircularDependencyDetected extends ContainerException { public function __construct(ArrayObject $dependencyStack, ?Throwable $previous = null) { diff --git a/src/Spinner/Container/Exception/SpawnFailedException.php b/src/Spinner/Container/Exception/ClassDoesNotExist.php similarity index 59% rename from src/Spinner/Container/Exception/SpawnFailedException.php rename to src/Spinner/Container/Exception/ClassDoesNotExist.php index 229c9404..44547b47 100644 --- a/src/Spinner/Container/Exception/SpawnFailedException.php +++ b/src/Spinner/Container/Exception/ClassDoesNotExist.php @@ -4,6 +4,6 @@ namespace AlecRabbit\Spinner\Container\Exception; -final class SpawnFailedException extends ContainerException +final class ClassDoesNotExist extends ContainerException { } diff --git a/src/Spinner/Container/Exception/InvalidDefinitionArgument.php b/src/Spinner/Container/Exception/InvalidDefinitionArgument.php new file mode 100644 index 00000000..58c7edd5 --- /dev/null +++ b/src/Spinner/Container/Exception/InvalidDefinitionArgument.php @@ -0,0 +1,11 @@ +getSpawnerCreator(), - definitions: $this->registry->load(), - ); - } - - protected function getSpawnerCreator(): Closure - { - return - $this->spawnerCreator - ?? - static function (ContainerInterface $container): IServiceSpawner { - return - new ServiceSpawner($container); - }; + return new Container( + spawnerFactory: $this->spawnerFactory, + definitions: $this->definitionRegistry->load(), + ); } } diff --git a/src/Spinner/Container/Factory/ServiceObjectFactory.php b/src/Spinner/Container/Factory/ServiceObjectFactory.php new file mode 100644 index 00000000..624ab20c --- /dev/null +++ b/src/Spinner/Container/Factory/ServiceObjectFactory.php @@ -0,0 +1,28 @@ +isStorableSolver->isStorable($serviceDefinition), + ); + } +} diff --git a/src/Spinner/Container/Factory/ServiceSpawnerFactory.php b/src/Spinner/Container/Factory/ServiceSpawnerFactory.php new file mode 100644 index 00000000..8578a489 --- /dev/null +++ b/src/Spinner/Container/Factory/ServiceSpawnerFactory.php @@ -0,0 +1,34 @@ +spawnerBuilder + ->withContainer($container) + ->withCircularDependencyDetector($this->circularDependencyDetector) + ->withServiceObjectFactory($this->serviceObjectFactory) + ->build() + ; + } +} diff --git a/src/Spinner/Container/Service.php b/src/Spinner/Container/Service.php new file mode 100644 index 00000000..ff17307c --- /dev/null +++ b/src/Spinner/Container/Service.php @@ -0,0 +1,26 @@ +value; + } + + public function isStorable(): bool + { + return $this->storable; + } +} diff --git a/src/Spinner/Container/ServiceDefinition.php b/src/Spinner/Container/ServiceDefinition.php new file mode 100644 index 00000000..873e032c --- /dev/null +++ b/src/Spinner/Container/ServiceDefinition.php @@ -0,0 +1,81 @@ +id = $id; + /** @var object|callable|class-string $definition */ + $this->definition = $definition; + $this->options = $options; + } + + private static function assertOptions(int $options): void + { + if ($options < 0) { + throw new InvalidOptionsArgument( + sprintf('Invalid options. Negative value: [%s].', $options) + ); + } + + $maxValue = self::maxOptionsValue(); + + if ($options > $maxValue) { + throw new InvalidOptionsArgument( + sprintf('Invalid options. Max value exceeded: [%s].', $maxValue) + ); + } + } + + private static function maxOptionsValue(): int + { + return self::SINGLETON + | self::TRANSIENT; + } + + private static function assertDefinition(mixed $definition): void + { + if (!is_callable($definition) && !is_object($definition) && !is_string($definition)) { + throw new InvalidDefinitionArgument( + sprintf( + 'Definition should be callable, object or string, "%s" given.', + get_debug_type($definition), + ) + ); + } + } + + public function getId(): string + { + return $this->id; + } + + public function getDefinition(): object|callable|string + { + return $this->definition; + } + + public function getOptions(): int + { + return $this->options; + } +} diff --git a/src/Spinner/Container/ServiceSpawner.php b/src/Spinner/Container/ServiceSpawner.php index f44a65a2..31fe840c 100644 --- a/src/Spinner/Container/ServiceSpawner.php +++ b/src/Spinner/Container/ServiceSpawner.php @@ -4,51 +4,81 @@ namespace AlecRabbit\Spinner\Container; +use AlecRabbit\Spinner\Container\Contract\ICircularDependencyDetector; +use AlecRabbit\Spinner\Container\Contract\IService; +use AlecRabbit\Spinner\Container\Contract\IServiceDefinition; +use AlecRabbit\Spinner\Container\Contract\IServiceObjectFactory; use AlecRabbit\Spinner\Container\Contract\IServiceSpawner; -use AlecRabbit\Spinner\Container\Exception\ClassDoesNotExistException; -use AlecRabbit\Spinner\Container\Exception\SpawnFailedException; -use AlecRabbit\Spinner\Container\Exception\UnableToCreateInstanceException; -use AlecRabbit\Spinner\Container\Exception\UnableToExtractTypeException; +use AlecRabbit\Spinner\Container\Exception\ClassDoesNotExist; +use AlecRabbit\Spinner\Container\Exception\SpawnFailed; +use AlecRabbit\Spinner\Container\Exception\UnableToCreateInstance; +use AlecRabbit\Spinner\Container\Exception\UnableToExtractType; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; use ReflectionClass; use ReflectionException; -use ReflectionIntersectionType; use ReflectionNamedType; -use ReflectionUnionType; use Throwable; -final class ServiceSpawner implements IServiceSpawner +final readonly class ServiceSpawner implements IServiceSpawner { public function __construct( - protected ContainerInterface $container, + private ContainerInterface $container, + private ICircularDependencyDetector $circularDependencyDetector, + private IServiceObjectFactory $serviceObjectFactory, ) { } - public function spawn(string|callable|object $definition): object + public function spawn(IServiceDefinition $serviceDefinition): IService { try { - return match (true) { - is_callable($definition) => $this->spawnByCallable($definition), - is_string($definition) => $this->spawnByClassConstructor($definition), - default => $definition, // return object as is - }; + return $this->spawnService($serviceDefinition); } catch (Throwable $e) { - throw new SpawnFailedException( + $details = + sprintf( + '[%s]: "%s".', + get_debug_type($e), + $e->getMessage(), + ); + + throw new SpawnFailed( sprintf( - 'Could not spawn object with callable.%s', - sprintf( - ' [%s]: "%s".', - get_debug_type($e), - $e->getMessage(), - ), + 'Failed to spawn service with id "%s". %s', + $serviceDefinition->getId(), + $details, ), previous: $e, ); } } + /** + * @throws ReflectionException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + private function spawnService(IServiceDefinition $serviceDefinition): IService + { + $this->circularDependencyDetector->push($serviceDefinition->getId()); + + $definition = $serviceDefinition->getDefinition(); + + $value = + match (true) { + is_callable($definition) => $this->spawnByCallable($definition), + is_string($definition) => $this->spawnByClassConstructor($definition), + default => $definition, // return object as is + }; + + $this->circularDependencyDetector->pop(); + + return $this->serviceObjectFactory->create( + value: $value, + serviceDefinition: $serviceDefinition, + ); + } + /** * @psalm-suppress MixedInferredReturnType * @psalm-suppress MixedReturnStatement @@ -69,7 +99,9 @@ private function spawnByClassConstructor(string $definition): object { return match (true) { class_exists($definition) => $this->createInstanceByReflection($definition), - default => throw new ClassDoesNotExistException(sprintf('Class does not exist: %s', (string)$definition)), + default => throw new ClassDoesNotExist( + sprintf('Class does not exist: %s', (string)$definition) + ), }; } @@ -89,33 +121,36 @@ private function createInstanceByReflection(string $class): object $parameters = []; foreach ($constructorParameters as $parameter) { $name = $parameter->getName(); + /** @var ReflectionNamedType|null $type */ $type = $parameter->getType(); if ($type === null) { - throw new UnableToExtractTypeException('Unable to extract type for parameter name: $' . $name); + throw new UnableToExtractType('Unable to extract type for parameter name: $' . $name); } if ($this->needsService($type)) { $parameters[$name] = $this->getServiceFromContainer($type->getName()); } } + /** @psalm-suppress MixedMethodCall */ return new $class(...$parameters); } try { + /** @psalm-suppress MixedMethodCall */ return new $class(); } catch (Throwable $e) { - throw new UnableToCreateInstanceException('Unable to create instance of ' . $class, previous: $e); + throw new UnableToCreateInstance('Unable to create instance of ' . $class, previous: $e); } } /** * @throws ContainerExceptionInterface */ - private function needsService(ReflectionIntersectionType|ReflectionNamedType|ReflectionUnionType $type): bool + private function needsService(mixed $type): bool { return match (true) { // assumes that all non-builtin types are services $type instanceof ReflectionNamedType => !$type->isBuiltin(), - default => throw new UnableToExtractTypeException( + default => throw new UnableToExtractType( sprintf( 'Only %s is supported.', ReflectionNamedType::class, diff --git a/src/Spinner/Contract/ITimer.php b/src/Spinner/Contract/IDeltaTimer.php similarity index 84% rename from src/Spinner/Contract/ITimer.php rename to src/Spinner/Contract/IDeltaTimer.php index 2e59dbe2..4d714edc 100644 --- a/src/Spinner/Contract/ITimer.php +++ b/src/Spinner/Contract/IDeltaTimer.php @@ -4,7 +4,7 @@ namespace AlecRabbit\Spinner\Contract; -interface ITimer +interface IDeltaTimer { public function getDelta(): float; } diff --git a/src/Spinner/Contract/IFinalizable.php b/src/Spinner/Contract/IFinalizable.php new file mode 100644 index 00000000..a744a9d4 --- /dev/null +++ b/src/Spinner/Contract/IFinalizable.php @@ -0,0 +1,10 @@ +value <= $other->value) { - return $this; - } - return $other; - } } diff --git a/src/Spinner/Contract/Output/IResourceStream.php b/src/Spinner/Contract/Output/IWritableStream.php similarity index 88% rename from src/Spinner/Contract/Output/IResourceStream.php rename to src/Spinner/Contract/Output/IWritableStream.php index c128ac14..d7a89fe2 100644 --- a/src/Spinner/Contract/Output/IResourceStream.php +++ b/src/Spinner/Contract/Output/IWritableStream.php @@ -6,7 +6,7 @@ use Traversable; -interface IResourceStream +interface IWritableStream { /** * @param Traversable $data diff --git a/src/Spinner/Contract/Pattern/IProceduralPattern.php b/src/Spinner/Contract/Pattern/IProceduralPattern.php index a66bdd9f..bcd7274f 100644 --- a/src/Spinner/Contract/Pattern/IProceduralPattern.php +++ b/src/Spinner/Contract/Pattern/IProceduralPattern.php @@ -6,5 +6,4 @@ interface IProceduralPattern extends IPattern { - } diff --git a/src/Spinner/Contract/Probe/IColorSupportProbe.php b/src/Spinner/Contract/Probe/IStylingMethodProbe.php similarity index 61% rename from src/Spinner/Contract/Probe/IColorSupportProbe.php rename to src/Spinner/Contract/Probe/IStylingMethodProbe.php index 853e223a..72dedb5b 100644 --- a/src/Spinner/Contract/Probe/IColorSupportProbe.php +++ b/src/Spinner/Contract/Probe/IStylingMethodProbe.php @@ -4,6 +4,6 @@ namespace AlecRabbit\Spinner\Contract\Probe; -interface IColorSupportProbe extends IStaticProbe +interface IStylingMethodProbe extends IStaticProbe { } diff --git a/src/Spinner/Core/A/ADriver.php b/src/Spinner/Core/A/ADriver.php index 66f80f9b..e799eba2 100644 --- a/src/Spinner/Core/A/ADriver.php +++ b/src/Spinner/Core/A/ADriver.php @@ -4,39 +4,39 @@ namespace AlecRabbit\Spinner\Core\A; +use AlecRabbit\Spinner\Contract\IDeltaTimer; use AlecRabbit\Spinner\Contract\IInterval; use AlecRabbit\Spinner\Contract\IObserver; -use AlecRabbit\Spinner\Contract\ITimer; use AlecRabbit\Spinner\Core\Contract\IDriver; +use AlecRabbit\Spinner\Core\Contract\IDriverMessages; +use AlecRabbit\Spinner\Core\Contract\IRenderer; use AlecRabbit\Spinner\Core\Contract\ISpinner; -use AlecRabbit\Spinner\Core\Output\Contract\IDriverOutput; use Closure; abstract class ADriver extends ASubject implements IDriver { protected IInterval $interval; - public function __construct( - protected readonly IDriverOutput $output, - protected readonly ITimer $timer, + protected function __construct( protected readonly IInterval $initialInterval, + protected readonly IDriverMessages $driverMessages, + protected readonly IRenderer $renderer, + protected readonly IDeltaTimer $deltaTimer, ?IObserver $observer = null, ) { parent::__construct($observer); $this->interval = $this->initialInterval; } - /** @inheritDoc */ public function interrupt(?string $interruptMessage = null): void { - $this->finalize($interruptMessage); + $this->finalize($interruptMessage ?? $this->driverMessages->getInterruptionMessage()); } - /** @inheritDoc */ public function finalize(?string $finalMessage = null): void { $this->erase(); - $this->output->finalize($finalMessage); + $this->renderer->finalize($finalMessage ?? $this->driverMessages->getFinalMessage()); } abstract protected function erase(): void; @@ -46,25 +46,21 @@ public function getInterval(): IInterval return $this->interval; } - /** @inheritDoc */ public function wrap(Closure $callback): Closure { - return - function (mixed ...$args) use ($callback): void { - $this->erase(); - $callback(...$args); - $this->render(); - }; + return function (mixed ...$args) use ($callback): void { + $this->erase(); + $callback(...$args); + $this->render(); + }; } abstract public function render(?float $dt = null): void; - /** @inheritDoc */ public function initialize(): void { - $this->output->initialize(); + $this->renderer->initialize(); } - /** @inheritDoc */ abstract public function has(ISpinner $spinner): bool; } diff --git a/src/Spinner/Core/A/AFrame.php b/src/Spinner/Core/A/AFrame.php index 16cacf70..c9c34fc5 100644 --- a/src/Spinner/Core/A/AFrame.php +++ b/src/Spinner/Core/A/AFrame.php @@ -14,12 +14,12 @@ public function __construct( ) { } - public function sequence(): string + public function getSequence(): string { return $this->sequence; } - public function width(): int + public function getWidth(): int { return $this->width; } diff --git a/src/Spinner/Core/A/ASubject.php b/src/Spinner/Core/A/ASubject.php index c21d0cac..07c3eac5 100644 --- a/src/Spinner/Core/A/ASubject.php +++ b/src/Spinner/Core/A/ASubject.php @@ -6,7 +6,9 @@ use AlecRabbit\Spinner\Contract\IObserver; use AlecRabbit\Spinner\Contract\ISubject; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; +use AlecRabbit\Spinner\Exception\LogicException; +use AlecRabbit\Spinner\Exception\ObserverCanNotBeOverwritten; use function sprintf; @@ -17,33 +19,27 @@ public function __construct( ) { } - /** @inheritDoc */ public function notify(): void { $this->observer?->update($this); } - /** @inheritDoc */ public function attach(IObserver $observer): void { - $this->assertObserverIsNotAttached(); - $this->assertNotSelf($observer); - $this->observer = $observer; - } + $this->assertObserverIsNotAttached(); - protected function assertObserverIsNotAttached(): void - { - if ($this->observer !== null) { - throw new InvalidArgumentException('Observer is already attached.'); - } + $this->observer = $observer; } + /** + * @throws InvalidArgument + */ protected function assertNotSelf(object $obj): void { if ($obj === $this) { - throw new InvalidArgumentException( + throw new InvalidArgument( sprintf( 'Object can not be self. %s #%s.', get_debug_type($obj), @@ -53,7 +49,16 @@ protected function assertNotSelf(object $obj): void } } - /** @inheritDoc */ + /** + * @throws LogicException + */ + protected function assertObserverIsNotAttached(): void + { + if ($this->observer instanceof IObserver) { + throw new ObserverCanNotBeOverwritten('Observer is already attached.'); + } + } + public function detach(IObserver $observer): void { if ($this->observer === $observer) { diff --git a/src/Spinner/Core/Builder/ConsoleCursorBuilder.php b/src/Spinner/Core/Builder/ConsoleCursorBuilder.php index 02c48490..e5606f19 100644 --- a/src/Spinner/Core/Builder/ConsoleCursorBuilder.php +++ b/src/Spinner/Core/Builder/ConsoleCursorBuilder.php @@ -23,11 +23,10 @@ public function build(): IConsoleCursor { $this->validate(); - return - new ConsoleCursor( - $this->buffer, - $this->cursorVisibilityMode, - ); + return new ConsoleCursor( + $this->buffer, + $this->cursorVisibilityMode, + ); } private function validate(): void diff --git a/src/Spinner/Core/Builder/Contract/IConsoleCursorBuilder.php b/src/Spinner/Core/Builder/Contract/IConsoleCursorBuilder.php index c20871d8..f30a329c 100644 --- a/src/Spinner/Core/Builder/Contract/IConsoleCursorBuilder.php +++ b/src/Spinner/Core/Builder/Contract/IConsoleCursorBuilder.php @@ -5,7 +5,6 @@ namespace AlecRabbit\Spinner\Core\Builder\Contract; use AlecRabbit\Spinner\Contract\Mode\CursorVisibilityMode; -use AlecRabbit\Spinner\Contract\Output\IBufferedOutput; use AlecRabbit\Spinner\Core\Output\Contract\IBuffer; use AlecRabbit\Spinner\Core\Output\Contract\IConsoleCursor; diff --git a/src/Spinner/Core/Builder/Contract/IDeltaTimerBuilder.php b/src/Spinner/Core/Builder/Contract/IDeltaTimerBuilder.php new file mode 100644 index 00000000..5e4942ac --- /dev/null +++ b/src/Spinner/Core/Builder/Contract/IDeltaTimerBuilder.php @@ -0,0 +1,17 @@ +validate(); + + return new DeltaTimer( + nowTimer: $this->nowTimer, + startTime: $this->startTime, + ); + } + + private function validate(): void + { + match (true) { + $this->startTime === null => throw new LogicException('Start time is not set.'), + $this->nowTimer === null => throw new LogicException('NowTimer is not set.'), + default => null, + }; + } + + public function withStartTime(float $time): IDeltaTimerBuilder + { + $clone = clone $this; + $clone->startTime = $time; + return $clone; + } + + public function withNowTimer(INowTimer $now): IDeltaTimerBuilder + { + $clone = clone $this; + $clone->nowTimer = $now; + return $clone; + } +} diff --git a/src/Spinner/Core/Builder/DriverBuilder.php b/src/Spinner/Core/Builder/DriverBuilder.php index 972ff185..6cd538f6 100644 --- a/src/Spinner/Core/Builder/DriverBuilder.php +++ b/src/Spinner/Core/Builder/DriverBuilder.php @@ -4,33 +4,33 @@ namespace AlecRabbit\Spinner\Core\Builder; +use AlecRabbit\Spinner\Contract\IDeltaTimer; use AlecRabbit\Spinner\Contract\IInterval; use AlecRabbit\Spinner\Contract\IObserver; -use AlecRabbit\Spinner\Contract\ITimer; use AlecRabbit\Spinner\Core\Contract\IDriver; use AlecRabbit\Spinner\Core\Contract\IDriverBuilder; +use AlecRabbit\Spinner\Core\Contract\IDriverMessages; +use AlecRabbit\Spinner\Core\Contract\IIntervalComparator; +use AlecRabbit\Spinner\Core\Contract\IRenderer; use AlecRabbit\Spinner\Core\Driver; -use AlecRabbit\Spinner\Core\Output\Contract\IDriverOutput; use AlecRabbit\Spinner\Exception\LogicException; +/** + * @psalm-suppress PossiblyNullArgument + */ final class DriverBuilder implements IDriverBuilder { - private ?IDriverOutput $driverOutput = null; - private ?ITimer $timer = null; + private ?IDeltaTimer $deltaTimer = null; private ?IInterval $initialInterval = null; private ?IObserver $observer = null; + private ?IDriverMessages $driverMessages = null; + private ?IIntervalComparator $intervalComparator = null; + private ?IRenderer $renderer = null; - public function withDriverOutput(IDriverOutput $driverOutput): IDriverBuilder + public function withDeltaTimer(IDeltaTimer $timer): IDriverBuilder { $clone = clone $this; - $clone->driverOutput = $driverOutput; - return $clone; - } - - public function withTimer(ITimer $timer): IDriverBuilder - { - $clone = clone $this; - $clone->timer = $timer; + $clone->deltaTimer = $timer; return $clone; } @@ -48,18 +48,18 @@ public function withObserver(IObserver $observer): IDriverBuilder return $clone; } - /** @inheritDoc */ public function build(): IDriver { $this->validate(); - return - new Driver( - output: $this->driverOutput, - timer: $this->timer, - initialInterval: $this->initialInterval, - observer: $this->observer, - ); + return new Driver( + initialInterval: $this->initialInterval, + driverMessages: $this->driverMessages, + renderer: $this->renderer, + intervalComparator: $this->intervalComparator, + deltaTimer: $this->deltaTimer, + observer: $this->observer, + ); } /** @@ -68,10 +68,33 @@ public function build(): IDriver private function validate(): void { match (true) { - $this->driverOutput === null => throw new LogicException('DriverOutput is not set.'), - $this->timer === null => throw new LogicException('Timer is not set.'), + $this->renderer === null => throw new LogicException('Renderer is not set.'), + $this->deltaTimer === null => throw new LogicException('Timer is not set.'), $this->initialInterval === null => throw new LogicException('InitialInterval is not set.'), + $this->driverMessages === null => throw new LogicException('DriverMessages is not set.'), + $this->intervalComparator === null => throw new LogicException('IntervalComparator is not set.'), default => null, }; } + + public function withIntervalComparator(IIntervalComparator $intervalComparator): IDriverBuilder + { + $clone = clone $this; + $clone->intervalComparator = $intervalComparator; + return $clone; + } + + public function withDriverMessages(IDriverMessages $driverMessages): IDriverBuilder + { + $clone = clone $this; + $clone->driverMessages = $driverMessages; + return $clone; + } + + public function withRenderer(IRenderer $renderer): IDriverBuilder + { + $clone = clone $this; + $clone->renderer = $renderer; + return $clone; + } } diff --git a/src/Spinner/Core/Builder/DriverOutputBuilder.php b/src/Spinner/Core/Builder/DriverOutputBuilder.php deleted file mode 100644 index d345332a..00000000 --- a/src/Spinner/Core/Builder/DriverOutputBuilder.php +++ /dev/null @@ -1,51 +0,0 @@ -validate(); - - return new DriverOutput( - output: $this->bufferedOutput, - cursor: $this->cursor, - ); - } - - private function validate(): void - { - match (true) { - null === $this->bufferedOutput => throw new LogicException('Output is not set.'), - $this->cursor === null => throw new LogicException('Cursor is not set.'), - default => null, - }; - } - - public function withOutput(IBufferedOutput $bufferedOutput): IDriverOutputBuilder - { - $clone = clone $this; - $clone->bufferedOutput = $bufferedOutput; - return $clone; - } - - public function withCursor(IConsoleCursor $cursor): IDriverOutputBuilder - { - $clone = clone $this; - $clone->cursor = $cursor; - return $clone; - } -} diff --git a/src/Spinner/Core/Builder/IntegerNormalizerBuilder.php b/src/Spinner/Core/Builder/IntegerNormalizerBuilder.php index 8a1821d8..a148514c 100644 --- a/src/Spinner/Core/Builder/IntegerNormalizerBuilder.php +++ b/src/Spinner/Core/Builder/IntegerNormalizerBuilder.php @@ -9,6 +9,9 @@ use AlecRabbit\Spinner\Core\IntegerNormalizer; use AlecRabbit\Spinner\Exception\LogicException; +/** + * @psalm-suppress PossiblyNullArgument + */ final class IntegerNormalizerBuilder implements IIntegerNormalizerBuilder { private ?int $divisor = null; @@ -27,7 +30,7 @@ public function build(): IIntegerNormalizer private function validate(): void { match (true) { - null === $this->divisor => throw new LogicException('Divisor value is not set.'), + $this->divisor === null => throw new LogicException('Divisor value is not set.'), $this->min === null => throw new LogicException('Min value is not set.'), default => null, }; diff --git a/src/Spinner/Core/Builder/SequenceStateBuilder.php b/src/Spinner/Core/Builder/SequenceStateBuilder.php new file mode 100644 index 00000000..374e07cd --- /dev/null +++ b/src/Spinner/Core/Builder/SequenceStateBuilder.php @@ -0,0 +1,65 @@ +sequence = $sequence; + return $clone; + } + + public function withWidth(int $width): ISequenceStateBuilder + { + $clone = clone $this; + $clone->width = $width; + return $clone; + } + + public function withPreviousWidth(int $previousWidth): ISequenceStateBuilder + { + $clone = clone $this; + $clone->previousWidth = $previousWidth; + return $clone; + } + + public function build(): ISequenceState + { + $this->validate(); + + return new SequenceState( + sequence: $this->sequence, + width: $this->width, + previousWidth: $this->previousWidth, + ); + } + + /** + * @throws LogicException + */ + private function validate(): void + { + match (true) { + $this->sequence === null => throw new LogicException('Sequence is not set.'), + $this->width === null => throw new LogicException('Width is not set.'), + $this->previousWidth === null => throw new LogicException('Previous width is not set.'), + default => null, + }; + } +} diff --git a/src/Spinner/Core/Builder/SequenceStateWriterBuilder.php b/src/Spinner/Core/Builder/SequenceStateWriterBuilder.php new file mode 100644 index 00000000..7dfaa551 --- /dev/null +++ b/src/Spinner/Core/Builder/SequenceStateWriterBuilder.php @@ -0,0 +1,66 @@ +validate(); + + return new SequenceStateWriter( + output: $this->bufferedOutput, + cursor: $this->cursor, + initializationResolver: $this->initializationResolver, + ); + } + + private function validate(): void + { + match (true) { + $this->bufferedOutput === null => throw new LogicException('Output is not set.'), + $this->cursor === null => throw new LogicException('Cursor is not set.'), + $this->initializationResolver === null => throw new LogicException('Initialization resolver is not set.'), + default => null, + }; + } + + public function withOutput(IBufferedOutput $bufferedOutput): ISequenceStateWriterBuilder + { + $clone = clone $this; + $clone->bufferedOutput = $bufferedOutput; + return $clone; + } + + public function withCursor(IConsoleCursor $cursor): ISequenceStateWriterBuilder + { + $clone = clone $this; + $clone->cursor = $cursor; + return $clone; + } + + public function withInitializationResolver( + IInitializationResolver $initializationResolver + ): ISequenceStateWriterBuilder { + $clone = clone $this; + $clone->initializationResolver = $initializationResolver; + return $clone; + } +} diff --git a/src/Spinner/Core/Builder/TimerBuilder.php b/src/Spinner/Core/Builder/TimerBuilder.php deleted file mode 100644 index 0e35bee6..00000000 --- a/src/Spinner/Core/Builder/TimerBuilder.php +++ /dev/null @@ -1,50 +0,0 @@ -validate(); - - return new Timer( - timeFunction: $this->timeFunction, - time: $this->startingTime, - ); - } - - private function validate(): void - { - match (true) { - null === $this->startingTime => throw new LogicException('Starting time is not set.'), - $this->timeFunction === null => throw new LogicException('Time function is not set.'), - default => null, - }; - } - - public function withStartTime(float $time): ITimerBuilder - { - $clone = clone $this; - $clone->startingTime = $time; - return $clone; - } - - public function withTimeFunction(Closure $timeFunction): ITimerBuilder - { - $clone = clone $this; - $clone->timeFunction = $timeFunction; - return $clone; - } -} diff --git a/src/Spinner/Core/CharFrame.php b/src/Spinner/Core/CharFrame.php index 5c0a241b..ab02f09e 100644 --- a/src/Spinner/Core/CharFrame.php +++ b/src/Spinner/Core/CharFrame.php @@ -18,5 +18,4 @@ public static function createSpace(): ICharFrame { return new CharFrame(' ', 1); } - } diff --git a/src/Spinner/Core/Config/AuxConfig.php b/src/Spinner/Core/Config/AuxConfig.php deleted file mode 100644 index cca96c8a..00000000 --- a/src/Spinner/Core/Config/AuxConfig.php +++ /dev/null @@ -1,37 +0,0 @@ -runMethodMode; - } - - public function getNormalizerMode(): NormalizerMode - { - return $this->normalizerMode; - } - - /** - * @return class-string - */ - public function getIdentifier(): string - { - return IAuxConfig::class; - } -} diff --git a/src/Spinner/Core/Config/Builder/AuxConfigBuilder.php b/src/Spinner/Core/Config/Builder/AuxConfigBuilder.php deleted file mode 100644 index 708dc393..00000000 --- a/src/Spinner/Core/Config/Builder/AuxConfigBuilder.php +++ /dev/null @@ -1,58 +0,0 @@ -validate(); - - return - new AuxConfig( - runMethodMode: $this->runMethodMode, - normalizerMode: $this->normalizerMode, - ); - } - - private function validate(): void - { - match (true) { - $this->runMethodMode === null => throw new LogicException('RunMethodMode is not set.'), - $this->normalizerMode === null => throw new LogicException('NormalizerMode is not set.'), - default => null, - }; - } - - public function withRunMethodMode(RunMethodMode $runMethodMode): IAuxConfigBuilder - { - $clone = clone $this; - $clone->runMethodMode = $runMethodMode; - return $clone; - } - - public function withNormalizerMode(NormalizerMode $normalizerMode): IAuxConfigBuilder - { - $clone = clone $this; - $clone->normalizerMode = $normalizerMode; - return $clone; - } -} diff --git a/src/Spinner/Core/Config/Builder/DriverConfigBuilder.php b/src/Spinner/Core/Config/Builder/DriverConfigBuilder.php index 549c73a7..13a03819 100644 --- a/src/Spinner/Core/Config/Builder/DriverConfigBuilder.php +++ b/src/Spinner/Core/Config/Builder/DriverConfigBuilder.php @@ -4,11 +4,11 @@ namespace AlecRabbit\Spinner\Core\Config\Builder; -use AlecRabbit\Spinner\Contract\Mode\InitializationMode; -use AlecRabbit\Spinner\Contract\Mode\LinkerMode; +use AlecRabbit\Spinner\Contract\Mode\DriverMode; use AlecRabbit\Spinner\Core\Config\Contract\Builder\IDriverConfigBuilder; use AlecRabbit\Spinner\Core\Config\Contract\IDriverConfig; use AlecRabbit\Spinner\Core\Config\DriverConfig; +use AlecRabbit\Spinner\Core\Contract\IDriverMessages; use AlecRabbit\Spinner\Exception\LogicException; /** @@ -16,19 +16,17 @@ */ final class DriverConfigBuilder implements IDriverConfigBuilder { - private ?LinkerMode $linkerMode = null; + private ?IDriverMessages $driverMessages = null; + private ?DriverMode $driverMode = null; - /** - * @inheritDoc - */ public function build(): IDriverConfig { $this->validate(); - return - new DriverConfig( - linkerMode: $this->linkerMode, - ); + return new DriverConfig( + driverMessages: $this->driverMessages, + driverMode: $this->driverMode, + ); } /** @@ -37,15 +35,23 @@ public function build(): IDriverConfig private function validate(): void { match (true) { - $this->linkerMode === null => throw new LogicException('LinkerMode is not set.'), + $this->driverMessages === null => throw new LogicException('DriverMessages is not set.'), + $this->driverMode === null => throw new LogicException('DriverMode is not set.'), default => null, }; } - public function withLinkerMode(LinkerMode $linkerMode): IDriverConfigBuilder + public function withDriverMessages(IDriverMessages $driverMessages): IDriverConfigBuilder + { + $clone = clone $this; + $clone->driverMessages = $driverMessages; + return $clone; + } + + public function withDriverMode(DriverMode $driverMode): IDriverConfigBuilder { $clone = clone $this; - $clone->linkerMode = $linkerMode; + $clone->driverMode = $driverMode; return $clone; } } diff --git a/src/Spinner/Core/Config/Builder/GeneralConfigBuilder.php b/src/Spinner/Core/Config/Builder/GeneralConfigBuilder.php new file mode 100644 index 00000000..46f9c1ab --- /dev/null +++ b/src/Spinner/Core/Config/Builder/GeneralConfigBuilder.php @@ -0,0 +1,43 @@ +validate(); + + return new GeneralConfig( + runMethodMode: $this->runMethodMode, + ); + } + + private function validate(): void + { + match (true) { + $this->runMethodMode === null => throw new LogicException('RunMethodMode is not set.'), + default => null, + }; + } + + public function withRunMethodMode(RunMethodMode $runMethodMode): IGeneralConfigBuilder + { + $clone = clone $this; + $clone->runMethodMode = $runMethodMode; + return $clone; + } +} diff --git a/src/Spinner/Core/Config/Builder/LinkerConfigBuilder.php b/src/Spinner/Core/Config/Builder/LinkerConfigBuilder.php new file mode 100644 index 00000000..beb59d43 --- /dev/null +++ b/src/Spinner/Core/Config/Builder/LinkerConfigBuilder.php @@ -0,0 +1,46 @@ +validate(); + + return new LinkerConfig( + linkerMode: $this->linkerMode, + ); + } + + /** + * @throws LogicException + */ + private function validate(): void + { + match (true) { + $this->linkerMode === null => throw new LogicException('LinkerMode is not set.'), + default => null, + }; + } + + public function withLinkerMode(LinkerMode $linkerMode): ILinkerConfigBuilder + { + $clone = clone $this; + $clone->linkerMode = $linkerMode; + return $clone; + } +} diff --git a/src/Spinner/Core/Config/Builder/LoopConfigBuilder.php b/src/Spinner/Core/Config/Builder/LoopConfigBuilder.php index d8529e79..40eaafbd 100644 --- a/src/Spinner/Core/Config/Builder/LoopConfigBuilder.php +++ b/src/Spinner/Core/Config/Builder/LoopConfigBuilder.php @@ -9,7 +9,7 @@ use AlecRabbit\Spinner\Core\Config\Contract\Builder\ILoopConfigBuilder; use AlecRabbit\Spinner\Core\Config\Contract\ILoopConfig; use AlecRabbit\Spinner\Core\Config\LoopConfig; -use AlecRabbit\Spinner\Core\ISignalHandlersContainer; +use AlecRabbit\Spinner\Core\Contract\ISignalHandlersContainer; use AlecRabbit\Spinner\Exception\LogicException; /** @@ -21,19 +21,15 @@ final class LoopConfigBuilder implements ILoopConfigBuilder private ?SignalHandlingMode $signalHandlersMode = null; private ?ISignalHandlersContainer $signalHandlersContainer = null; - /** - * @inheritDoc - */ public function build(): ILoopConfig { $this->validate(); - return - new LoopConfig( - autoStartMode: $this->autoStartMode, - signalHandlersMode: $this->signalHandlersMode, - signalHandlersContainer: $this->signalHandlersContainer, - ); + return new LoopConfig( + autoStartMode: $this->autoStartMode, + signalHandlersMode: $this->signalHandlersMode, + signalHandlersContainer: $this->signalHandlersContainer, + ); } /** diff --git a/src/Spinner/Core/Config/Builder/NormalizerConfigBuilder.php b/src/Spinner/Core/Config/Builder/NormalizerConfigBuilder.php new file mode 100644 index 00000000..f3b9e13b --- /dev/null +++ b/src/Spinner/Core/Config/Builder/NormalizerConfigBuilder.php @@ -0,0 +1,43 @@ +validate(); + + return new NormalizerConfig( + normalizerMode: $this->normalizerMode, + ); + } + + private function validate(): void + { + match (true) { + $this->normalizerMode === null => throw new LogicException('NormalizerMode is not set.'), + default => null, + }; + } + + public function withNormalizerMode(NormalizerMode $normalizerMode): INormalizerConfigBuilder + { + $clone = clone $this; + $clone->normalizerMode = $normalizerMode; + return $clone; + } +} diff --git a/src/Spinner/Core/Config/Builder/OutputConfigBuilder.php b/src/Spinner/Core/Config/Builder/OutputConfigBuilder.php index fbb9116a..ad999736 100644 --- a/src/Spinner/Core/Config/Builder/OutputConfigBuilder.php +++ b/src/Spinner/Core/Config/Builder/OutputConfigBuilder.php @@ -22,20 +22,16 @@ final class OutputConfigBuilder implements IOutputConfigBuilder private ?InitializationMode $initializationMode = null; private mixed $stream = null; - /** - * @inheritDoc - */ public function build(): IOutputConfig { $this->validate(); - return - new OutputConfig( - stylingMethodMode: $this->stylingMethodMode, - cursorVisibilityMode: $this->cursorVisibilityMode, - initializationMode: $this->initializationMode, - stream: $this->stream, - ); + return new OutputConfig( + stylingMethodMode: $this->stylingMethodMode, + cursorVisibilityMode: $this->cursorVisibilityMode, + initializationMode: $this->initializationMode, + stream: $this->stream, + ); } /** diff --git a/src/Spinner/Core/Config/Builder/RevolverConfigBuilder.php b/src/Spinner/Core/Config/Builder/RevolverConfigBuilder.php new file mode 100644 index 00000000..36e099ce --- /dev/null +++ b/src/Spinner/Core/Config/Builder/RevolverConfigBuilder.php @@ -0,0 +1,46 @@ +validate(); + + return new RevolverConfig( + tolerance: $this->tolerance, + ); + } + + /** + * @throws LogicException + */ + private function validate(): void + { + match (true) { + $this->tolerance === null => throw new LogicException('Tolerance is not set.'), + default => null, + }; + } + + public function withTolerance(ITolerance $tolerance): IRevolverConfigBuilder + { + $clone = clone $this; + $clone->tolerance = $tolerance; + return $clone; + } +} diff --git a/src/Spinner/Core/Config/Builder/WidgetConfigBuilder.php b/src/Spinner/Core/Config/Builder/WidgetConfigBuilder.php deleted file mode 100644 index d5646c6e..00000000 --- a/src/Spinner/Core/Config/Builder/WidgetConfigBuilder.php +++ /dev/null @@ -1,85 +0,0 @@ -validate(); - - return - new WidgetConfig( - leadingSpacer: $this->leadingSpacer, - trailingSpacer: $this->trailingSpacer, - revolverConfig: $this->revolverConfig, - ); - } - - private function validate(): void - { - match (true) { - $this->leadingSpacer === null => throw new LogicException('Leading spacer is not set.'), - $this->trailingSpacer === null => throw new LogicException('Trailing spacer is not set.'), - $this->revolverConfig === null => throw new LogicException('Revolver config is not set.'), - default => null, - }; - } - - public function withLeadingSpacer(IFrame $frame): IWidgetConfigBuilder - { - $clone = clone $this; - $clone->leadingSpacer = $frame; - return $clone; - } - - public function withTrailingSpacer(IFrame $frame): IWidgetConfigBuilder - { - $clone = clone $this; - $clone->trailingSpacer = $frame; - return $clone; - } - - public function withStylePalette(IPalette $pattern): IWidgetConfigBuilder - { - $clone = clone $this; - $clone->stylePattern = $pattern; - return $clone; - } - - public function withCharPalette(IPalette $pattern): IWidgetConfigBuilder - { - $clone = clone $this; - $clone->charPattern = $pattern; - return $clone; - } - - public function withRevolverConfig(IWidgetRevolverConfig $revolverConfig): IWidgetConfigBuilder - { - $clone = clone $this; - $clone->revolverConfig = $revolverConfig; - return $clone; - } -} diff --git a/src/Spinner/Core/Config/Builder/WidgetRevolverConfigBuilder.php b/src/Spinner/Core/Config/Builder/WidgetRevolverConfigBuilder.php index 069fdbd7..f2f6006a 100644 --- a/src/Spinner/Core/Config/Builder/WidgetRevolverConfigBuilder.php +++ b/src/Spinner/Core/Config/Builder/WidgetRevolverConfigBuilder.php @@ -5,6 +5,7 @@ namespace AlecRabbit\Spinner\Core\Config\Builder; use AlecRabbit\Spinner\Core\Config\Contract\Builder\IWidgetRevolverConfigBuilder; +use AlecRabbit\Spinner\Core\Config\Contract\IRevolverConfig; use AlecRabbit\Spinner\Core\Config\Contract\IWidgetRevolverConfig; use AlecRabbit\Spinner\Core\Config\WidgetRevolverConfig; use AlecRabbit\Spinner\Core\Palette\Contract\IPalette; @@ -17,19 +18,17 @@ final class WidgetRevolverConfigBuilder implements IWidgetRevolverConfigBuilder { private ?IPalette $stylePalette = null; private ?IPalette $charPalette = null; + private ?IRevolverConfig $revolverConfig = null; - /** - * @inheritDoc - */ public function build(): IWidgetRevolverConfig { $this->validate(); - return - new WidgetRevolverConfig( - stylePalette: $this->stylePalette, - charPalette: $this->charPalette, - ); + return new WidgetRevolverConfig( + stylePalette: $this->stylePalette, + charPalette: $this->charPalette, + revolverConfig: $this->revolverConfig, + ); } private function validate(): void @@ -37,6 +36,7 @@ private function validate(): void match (true) { $this->stylePalette === null => throw new LogicException('Style palette is not set.'), $this->charPalette === null => throw new LogicException('Char palette is not set.'), + $this->revolverConfig === null => throw new LogicException('Revolver config is not set.'), default => null, }; } @@ -54,4 +54,11 @@ public function withCharPalette(IPalette $palette): IWidgetRevolverConfigBuilder $clone->charPalette = $palette; return $clone; } + + public function withRevolverConfig(IRevolverConfig $revolverConfig): IWidgetRevolverConfigBuilder + { + $clone = clone $this; + $clone->revolverConfig = $revolverConfig; + return $clone; + } } diff --git a/src/Spinner/Core/Config/Config.php b/src/Spinner/Core/Config/Config.php deleted file mode 100644 index 7d264abc..00000000 --- a/src/Spinner/Core/Config/Config.php +++ /dev/null @@ -1,65 +0,0 @@ -, IConfigElement> - */ - protected ArrayObject $configElements; - - public function __construct( - ArrayObject $configElements = new ArrayObject(), - ) { - $this->configElements = $configElements; - } - - /** @inheritDoc */ - public function set(IConfigElement ...$configElements): void - { - foreach ($configElements as $configElement) { - $identifier = $configElement->getIdentifier(); - self::assertIdentifier($identifier); - $this->configElements->offsetSet($identifier, $configElement); - } - } - - /** - * @param class-string $id - * @throws InvalidArgumentException - */ - private static function assertIdentifier(string $id): void - { - if (!interface_exists($id)) { - throw new InvalidArgumentException( - sprintf('Identifier "%s" is not an interface.', $id) - ); - } - if (!is_a($id, IConfigElement::class, true)) { - throw new InvalidArgumentException( - sprintf('Identifier "%s" is not an instance of "%s".', $id, IConfigElement::class) - ); - } - } - - /** @inheritDoc */ - public function get(string $id): IConfigElement - { - self::assertIdentifier($id); - if (!$this->configElements->offsetExists($id)) { - throw new InvalidArgumentException( - sprintf('Identifier "%s" is not set.', $id) - ); - } - return $this->configElements->offsetGet($id); - } - -} diff --git a/src/Spinner/Core/Config/Contract/Builder/IDriverConfigBuilder.php b/src/Spinner/Core/Config/Contract/Builder/IDriverConfigBuilder.php index 93a82f03..60911582 100644 --- a/src/Spinner/Core/Config/Contract/Builder/IDriverConfigBuilder.php +++ b/src/Spinner/Core/Config/Contract/Builder/IDriverConfigBuilder.php @@ -4,9 +4,9 @@ namespace AlecRabbit\Spinner\Core\Config\Contract\Builder; -use AlecRabbit\Spinner\Contract\Mode\InitializationMode; -use AlecRabbit\Spinner\Contract\Mode\LinkerMode; +use AlecRabbit\Spinner\Contract\Mode\DriverMode; use AlecRabbit\Spinner\Core\Config\Contract\IDriverConfig; +use AlecRabbit\Spinner\Core\Contract\IDriverMessages; use AlecRabbit\Spinner\Exception\LogicException; interface IDriverConfigBuilder @@ -16,5 +16,7 @@ interface IDriverConfigBuilder */ public function build(): IDriverConfig; - public function withLinkerMode(LinkerMode $linkerMode): IDriverConfigBuilder; + public function withDriverMessages(IDriverMessages $driverMessages): IDriverConfigBuilder; + + public function withDriverMode(DriverMode $driverMode): IDriverConfigBuilder; } diff --git a/src/Spinner/Core/Config/Contract/Builder/IGeneralConfigBuilder.php b/src/Spinner/Core/Config/Contract/Builder/IGeneralConfigBuilder.php new file mode 100644 index 00000000..dc8bd92a --- /dev/null +++ b/src/Spinner/Core/Config/Contract/Builder/IGeneralConfigBuilder.php @@ -0,0 +1,19 @@ + $id - * @psalm-return T - * - * @throws InvalidArgumentException - */ - public function get(string $id): IConfigElement; -} diff --git a/src/Spinner/Core/Config/Contract/IConfigElement.php b/src/Spinner/Core/Config/Contract/IConfigElement.php index 9438906b..09971842 100644 --- a/src/Spinner/Core/Config/Contract/IConfigElement.php +++ b/src/Spinner/Core/Config/Contract/IConfigElement.php @@ -4,9 +4,6 @@ namespace AlecRabbit\Spinner\Core\Config\Contract; -use AlecRabbit\Spinner\Contract\Mode\InitializationMode; -use AlecRabbit\Spinner\Core\Config\OutputConfig; - interface IConfigElement { /** diff --git a/src/Spinner/Core/Config/Contract/IDriverConfig.php b/src/Spinner/Core/Config/Contract/IDriverConfig.php index 6cc7b42a..5184558a 100644 --- a/src/Spinner/Core/Config/Contract/IDriverConfig.php +++ b/src/Spinner/Core/Config/Contract/IDriverConfig.php @@ -4,10 +4,12 @@ namespace AlecRabbit\Spinner\Core\Config\Contract; -use AlecRabbit\Spinner\Contract\Mode\InitializationMode; -use AlecRabbit\Spinner\Contract\Mode\LinkerMode; +use AlecRabbit\Spinner\Contract\Mode\DriverMode; +use AlecRabbit\Spinner\Core\Contract\IDriverMessages; interface IDriverConfig extends IConfigElement { - public function getLinkerMode(): LinkerMode; + public function getDriverMessages(): IDriverMessages; + + public function getDriverMode(): DriverMode; } diff --git a/src/Spinner/Core/Config/Contract/IGeneralConfig.php b/src/Spinner/Core/Config/Contract/IGeneralConfig.php new file mode 100644 index 00000000..dc6b1671 --- /dev/null +++ b/src/Spinner/Core/Config/Contract/IGeneralConfig.php @@ -0,0 +1,12 @@ +loopConfig->getAutoStartMode() === AutoStartMode::ENABLED; + } +} diff --git a/src/Spinner/Core/Config/Detector/DriverModeDetector.php b/src/Spinner/Core/Config/Detector/DriverModeDetector.php new file mode 100644 index 00000000..b7bed9b6 --- /dev/null +++ b/src/Spinner/Core/Config/Detector/DriverModeDetector.php @@ -0,0 +1,22 @@ +driverConfig->getDriverMode() === DriverMode::ENABLED; + } +} diff --git a/src/Spinner/Core/Config/Detector/InitializationModeDetector.php b/src/Spinner/Core/Config/Detector/InitializationModeDetector.php new file mode 100644 index 00000000..6033b3cb --- /dev/null +++ b/src/Spinner/Core/Config/Detector/InitializationModeDetector.php @@ -0,0 +1,22 @@ +outputConfig->getInitializationMode() === InitializationMode::ENABLED; + } +} diff --git a/src/Spinner/Core/Config/Detector/LinkerModeDetector.php b/src/Spinner/Core/Config/Detector/LinkerModeDetector.php new file mode 100644 index 00000000..8214eec4 --- /dev/null +++ b/src/Spinner/Core/Config/Detector/LinkerModeDetector.php @@ -0,0 +1,22 @@ +linkerConfig->getLinkerMode() === LinkerMode::ENABLED; + } +} diff --git a/src/Spinner/Core/Config/DriverConfig.php b/src/Spinner/Core/Config/DriverConfig.php index 5525f86b..b7779c63 100644 --- a/src/Spinner/Core/Config/DriverConfig.php +++ b/src/Spinner/Core/Config/DriverConfig.php @@ -4,27 +4,30 @@ namespace AlecRabbit\Spinner\Core\Config; -use AlecRabbit\Spinner\Contract\Mode\InitializationMode; -use AlecRabbit\Spinner\Contract\Mode\LinkerMode; +use AlecRabbit\Spinner\Contract\Mode\DriverMode; use AlecRabbit\Spinner\Core\Config\Contract\IDriverConfig; +use AlecRabbit\Spinner\Core\Contract\IDriverMessages; final readonly class DriverConfig implements IDriverConfig { public function __construct( - protected LinkerMode $linkerMode, + private IDriverMessages $driverMessages, + private DriverMode $driverMode, ) { } - public function getLinkerMode(): LinkerMode + public function getIdentifier(): string { - return $this->linkerMode; + return IDriverConfig::class; } - /** - * @return class-string - */ - public function getIdentifier(): string + public function getDriverMessages(): IDriverMessages { - return IDriverConfig::class; + return $this->driverMessages; + } + + public function getDriverMode(): DriverMode + { + return $this->driverMode; } } diff --git a/src/Spinner/Core/Config/Factory/AuxConfigFactory.php b/src/Spinner/Core/Config/Factory/AuxConfigFactory.php deleted file mode 100644 index 4c249525..00000000 --- a/src/Spinner/Core/Config/Factory/AuxConfigFactory.php +++ /dev/null @@ -1,35 +0,0 @@ -auxConfigBuilder - ->withRunMethodMode( - $this->runMethodModeSolver->solve() - ) - ->withNormalizerMode( - $this->normalizerModeSolver->solve() - ) - ->build() - ; - } -} diff --git a/src/Spinner/Core/Config/Factory/ConfigFactory.php b/src/Spinner/Core/Config/Factory/ConfigFactory.php deleted file mode 100644 index 5e68983c..00000000 --- a/src/Spinner/Core/Config/Factory/ConfigFactory.php +++ /dev/null @@ -1,52 +0,0 @@ -fill($config); - - return - $config; - } - - private function fill(IConfig $config): void - { - $config->set( - $this->auxConfigFactory->create(), - $this->loopConfigFactory->create(), - $this->outputConfigFactory->create(), - $this->driverConfigFactory->create(), - $this->widgetConfigFactory->create(), - $this->rootWidgetConfigFactory->create(), - ); - } - - -} diff --git a/src/Spinner/Core/Config/Factory/ConfigProviderFactory.php b/src/Spinner/Core/Config/Factory/ConfigProviderFactory.php deleted file mode 100644 index 8664f7ab..00000000 --- a/src/Spinner/Core/Config/Factory/ConfigProviderFactory.php +++ /dev/null @@ -1,28 +0,0 @@ -configFactory->create(); - - return - new ConfigProvider( - config: $config, - ); - } -} diff --git a/src/Spinner/Core/Config/Factory/DriverConfigFactory.php b/src/Spinner/Core/Config/Factory/DriverConfigFactory.php index d3e1d24e..a008a4c5 100644 --- a/src/Spinner/Core/Config/Factory/DriverConfigFactory.php +++ b/src/Spinner/Core/Config/Factory/DriverConfigFactory.php @@ -7,26 +7,28 @@ use AlecRabbit\Spinner\Core\Config\Contract\Builder\IDriverConfigBuilder; use AlecRabbit\Spinner\Core\Config\Contract\Factory\IDriverConfigFactory; use AlecRabbit\Spinner\Core\Config\Contract\IDriverConfig; -use AlecRabbit\Spinner\Core\Config\Solver\Contract\IInitializationModeSolver; -use AlecRabbit\Spinner\Core\Config\Solver\Contract\ILinkerModeSolver; +use AlecRabbit\Spinner\Core\Config\Solver\Contract\IDriverMessagesSolver; +use AlecRabbit\Spinner\Core\Config\Solver\Contract\IDriverModeSolver; final readonly class DriverConfigFactory implements IDriverConfigFactory { public function __construct( - protected ILinkerModeSolver $linkerModeSolver, protected IDriverConfigBuilder $driverConfigBuilder, + protected IDriverMessagesSolver $driverMessagesSolver, + protected IDriverModeSolver $driverModeSolver, ) { } - public function create(): IDriverConfig { - return - $this->driverConfigBuilder - ->withLinkerMode( - $this->linkerModeSolver->solve(), - ) - ->build() + return $this->driverConfigBuilder + ->withDriverMessages( + $this->driverMessagesSolver->solve() + ) + ->withDriverMode( + $this->driverModeSolver->solve() + ) + ->build() ; } } diff --git a/src/Spinner/Core/Config/Factory/GeneralConfigFactory.php b/src/Spinner/Core/Config/Factory/GeneralConfigFactory.php new file mode 100644 index 00000000..460ee532 --- /dev/null +++ b/src/Spinner/Core/Config/Factory/GeneralConfigFactory.php @@ -0,0 +1,29 @@ +generalConfigBuilder + ->withRunMethodMode( + $this->runMethodModeSolver->solve() + ) + ->build() + ; + } +} diff --git a/src/Spinner/Core/Config/Factory/InitialRootWidgetConfigFactory.php b/src/Spinner/Core/Config/Factory/InitialRootWidgetConfigFactory.php new file mode 100644 index 00000000..3613c974 --- /dev/null +++ b/src/Spinner/Core/Config/Factory/InitialRootWidgetConfigFactory.php @@ -0,0 +1,101 @@ +rootWidgetSettingsSolver->solve(); + $widgetSettings = $this->widgetSettingsSolver->solve(); + + return new RootWidgetConfig( + leadingSpacer: $this->getLeadingSpacer($rootWidgetSettings, $widgetSettings), + trailingSpacer: $this->getTrailingSpacer($rootWidgetSettings, $widgetSettings), + revolverConfig: $this->getWidgetRevolverConfig($rootWidgetSettings, $widgetSettings), + ); + } + + private function getLeadingSpacer( + IRootWidgetSettings $rootWidgetSettings, + IWidgetSettings $widgetSettings + ): IFrame { + return $rootWidgetSettings->getLeadingSpacer() + ?? + $widgetSettings->getLeadingSpacer() + ?? + throw new DomainException('Leading spacer expected to be set.'); + } + + private function getTrailingSpacer( + IRootWidgetSettings $rootWidgetSettings, + IWidgetSettings $widgetSettings + ): IFrame { + return $rootWidgetSettings->getTrailingSpacer() + ?? + $widgetSettings->getTrailingSpacer() + ?? + throw new DomainException('Trailing spacer expected to be set.'); + } + + private function getWidgetRevolverConfig( + IRootWidgetSettings $rootWidgetSettings, + IWidgetSettings $widgetSettings + ): IWidgetRevolverConfig { + return new WidgetRevolverConfig( + stylePalette: $this->getStylePalette($rootWidgetSettings, $widgetSettings), + charPalette: $this->getCharPalette($rootWidgetSettings, $widgetSettings), + revolverConfig: $this->getRevolverConfig(), + ); + } + + private function getStylePalette( + IRootWidgetSettings $rootWidgetSettings, + IWidgetSettings $widgetSettings + ): IPalette { + return $rootWidgetSettings->getStylePalette() + ?? + $widgetSettings->getStylePalette() + ?? + throw new DomainException('Style palette expected to be set.'); + } + + private function getCharPalette( + IRootWidgetSettings $rootWidgetSettings, + IWidgetSettings $widgetSettings + ): IPalette { + return $rootWidgetSettings->getCharPalette() + ?? + $widgetSettings->getCharPalette() + ?? + throw new DomainException('Char palette expected to be set.'); + } + + private function getRevolverConfig(): IRevolverConfig + { + return new RevolverConfig(); + } +} diff --git a/src/Spinner/Core/Config/Factory/InitialWidgetConfigFactory.php b/src/Spinner/Core/Config/Factory/InitialWidgetConfigFactory.php new file mode 100644 index 00000000..d371d207 --- /dev/null +++ b/src/Spinner/Core/Config/Factory/InitialWidgetConfigFactory.php @@ -0,0 +1,79 @@ +widgetSettingsSolver->solve(); + + return new WidgetConfig( + leadingSpacer: $this->getLeadingSpacer($widgetSettings), + trailingSpacer: $this->getTrailingSpacer($widgetSettings), + revolverConfig: $this->getWidgetRevolverConfig($widgetSettings), + ); + } + + private function getLeadingSpacer(IWidgetSettings $widgetSettings): IFrame + { + return $widgetSettings->getLeadingSpacer() + ?? + throw new DomainException('Leading spacer expected to be set.'); + } + + private function getTrailingSpacer(IWidgetSettings $widgetSettings): IFrame + { + return $widgetSettings->getTrailingSpacer() + ?? + throw new DomainException('Trailing spacer expected to be set.'); + } + + private function getWidgetRevolverConfig(IWidgetSettings $widgetSettings): IWidgetRevolverConfig + { + return new WidgetRevolverConfig( + stylePalette: $this->getStylePalette($widgetSettings), + charPalette: $this->getCharPalette($widgetSettings), + revolverConfig: $this->getRevolverConfig(), + ); + } + + private function getStylePalette(IWidgetSettings $widgetSettings): IPalette + { + return $widgetSettings->getStylePalette() + ?? + throw new DomainException('Style palette expected to be set.'); + } + + private function getCharPalette(IWidgetSettings $widgetSettings): IPalette + { + return $widgetSettings->getCharPalette() + ?? + throw new DomainException('Char palette expected to be set.'); + } + + private function getRevolverConfig(): IRevolverConfig + { + return new RevolverConfig(); + } +} diff --git a/src/Spinner/Core/Config/Factory/LinkerConfigFactory.php b/src/Spinner/Core/Config/Factory/LinkerConfigFactory.php new file mode 100644 index 00000000..00d79fc2 --- /dev/null +++ b/src/Spinner/Core/Config/Factory/LinkerConfigFactory.php @@ -0,0 +1,29 @@ +linkerConfigBuilder + ->withLinkerMode( + $this->linkerModeSolver->solve(), + ) + ->build() + ; + } +} diff --git a/src/Spinner/Core/Config/Factory/LoopConfigFactory.php b/src/Spinner/Core/Config/Factory/LoopConfigFactory.php index 043b8a30..24cb9f5c 100644 --- a/src/Spinner/Core/Config/Factory/LoopConfigFactory.php +++ b/src/Spinner/Core/Config/Factory/LoopConfigFactory.php @@ -11,7 +11,7 @@ use AlecRabbit\Spinner\Core\Config\Solver\Contract\ISignalHandlersContainerSolver; use AlecRabbit\Spinner\Core\Config\Solver\Contract\ISignalHandlingModeSolver; -final class LoopConfigFactory implements ILoopConfigFactory +final readonly class LoopConfigFactory implements ILoopConfigFactory { public function __construct( protected IAutoStartModeSolver $autoStartModeSolver, @@ -23,18 +23,17 @@ public function __construct( public function create(): ILoopConfig { - return - $this->loopConfigBuilder - ->withAutoStartMode( - $this->autoStartModeSolver->solve() - ) - ->withSignalHandlingMode( - $this->signalHandlersModeSolver->solve() - ) - ->withSignalHandlersContainer( - $this->signalHandlersContainerSolver->solve() - ) - ->build() + return $this->loopConfigBuilder + ->withAutoStartMode( + $this->autoStartModeSolver->solve() + ) + ->withSignalHandlingMode( + $this->signalHandlersModeSolver->solve() + ) + ->withSignalHandlersContainer( + $this->signalHandlersContainerSolver->solve() + ) + ->build() ; } } diff --git a/src/Spinner/Core/Config/Factory/NormalizerConfigFactory.php b/src/Spinner/Core/Config/Factory/NormalizerConfigFactory.php new file mode 100644 index 00000000..ee9dfb68 --- /dev/null +++ b/src/Spinner/Core/Config/Factory/NormalizerConfigFactory.php @@ -0,0 +1,29 @@ +normalizerConfigBuilder + ->withNormalizerMode( + $this->normalizerModeSolver->solve() + ) + ->build() + ; + } +} diff --git a/src/Spinner/Core/Config/Factory/OutputConfigFactory.php b/src/Spinner/Core/Config/Factory/OutputConfigFactory.php index a6327317..cd33b468 100644 --- a/src/Spinner/Core/Config/Factory/OutputConfigFactory.php +++ b/src/Spinner/Core/Config/Factory/OutputConfigFactory.php @@ -25,21 +25,20 @@ public function __construct( public function create(): IOutputConfig { - return - $this->outputConfigBuilder - ->withStylingMethodMode( - $this->stylingMethodModeSolver->solve(), - ) - ->withCursorVisibilityMode( - $this->cursorVisibilityModeSolver->solve(), - ) - ->withInitializationMode( - $this->initializationModeSolver->solve(), - ) - ->withStream( - $this->streamSolver->solve(), - ) - ->build() + return $this->outputConfigBuilder + ->withStylingMethodMode( + $this->stylingMethodModeSolver->solve(), + ) + ->withCursorVisibilityMode( + $this->cursorVisibilityModeSolver->solve(), + ) + ->withInitializationMode( + $this->initializationModeSolver->solve(), + ) + ->withStream( + $this->streamSolver->solve(), + ) + ->build() ; } } diff --git a/src/Spinner/Core/Config/Factory/RevolverConfigFactory.php b/src/Spinner/Core/Config/Factory/RevolverConfigFactory.php new file mode 100644 index 00000000..bbcf678a --- /dev/null +++ b/src/Spinner/Core/Config/Factory/RevolverConfigFactory.php @@ -0,0 +1,29 @@ +revolverConfigBuilder + ->withTolerance( + $this->toleranceSolver->solve() + ) + ->build() + ; + } +} diff --git a/src/Spinner/Core/Config/Factory/RootWidgetConfigFactory.php b/src/Spinner/Core/Config/Factory/RootWidgetConfigFactory.php new file mode 100644 index 00000000..98bde72e --- /dev/null +++ b/src/Spinner/Core/Config/Factory/RootWidgetConfigFactory.php @@ -0,0 +1,68 @@ +rootWidgetConfig; + } + + if ($widgetSettings instanceof IWidgetConfig) { + return new RootWidgetConfig( + leadingSpacer: $widgetSettings->getLeadingSpacer(), + trailingSpacer: $widgetSettings->getTrailingSpacer(), + revolverConfig: $widgetSettings->getWidgetRevolverConfig(), + ); + } + + return new RootWidgetConfig( + leadingSpacer: $this->getLeadingSpacer($widgetSettings), + trailingSpacer: $this->getTrailingSpacer($widgetSettings), + revolverConfig: $this->getWidgetRevolverConfig($widgetSettings), + ); + } + + private function getLeadingSpacer(IWidgetSettings $widgetSettings): IFrame + { + return $widgetSettings->getLeadingSpacer() + ?? + $this->rootWidgetConfig->getLeadingSpacer(); + } + + private function getTrailingSpacer(IWidgetSettings $widgetSettings): IFrame + { + return $widgetSettings->getTrailingSpacer() + ?? + $this->rootWidgetConfig->getTrailingSpacer(); + } + + private function getWidgetRevolverConfig(IWidgetSettings $widgetSettings): IWidgetRevolverConfig + { + $wRConfig = $this->rootWidgetConfig->getWidgetRevolverConfig(); + + return new WidgetRevolverConfig( + stylePalette: $widgetSettings->getStylePalette() ?? $wRConfig->getStylePalette(), + charPalette: $widgetSettings->getCharPalette() ?? $wRConfig->getCharPalette(), + revolverConfig: $wRConfig->getRevolverConfig(), + ); + } +} diff --git a/src/Spinner/Core/Config/Factory/RuntimeRootWidgetConfigFactory.php b/src/Spinner/Core/Config/Factory/RuntimeRootWidgetConfigFactory.php deleted file mode 100644 index 3760d793..00000000 --- a/src/Spinner/Core/Config/Factory/RuntimeRootWidgetConfigFactory.php +++ /dev/null @@ -1,74 +0,0 @@ -widgetConfig; - } - - if ($widgetSettings instanceof IWidgetConfig) { - return - new RootWidgetConfig( - leadingSpacer: $widgetSettings->getLeadingSpacer(), - trailingSpacer: $widgetSettings->getTrailingSpacer(), - revolverConfig: $widgetSettings->getWidgetRevolverConfig(), - ); - } - - return - new RootWidgetConfig( - leadingSpacer: $this->getLeadingSpacer($widgetSettings), - trailingSpacer: $this->getTrailingSpacer($widgetSettings), - revolverConfig: $this->getWidgetRevolverConfig($widgetSettings), - ); - } - - - protected function getLeadingSpacer(IWidgetSettings $widgetSettings): IFrame - { - return - $widgetSettings->getLeadingSpacer() - ?? - $this->widgetConfig->getLeadingSpacer(); - } - - protected function getTrailingSpacer(IWidgetSettings $widgetSettings): IFrame - { - return - $widgetSettings->getTrailingSpacer() - ?? - $this->widgetConfig->getTrailingSpacer(); - } - - private function getWidgetRevolverConfig(IWidgetSettings $widgetSettings): IWidgetRevolverConfig - { - $config = $this->widgetConfig->getWidgetRevolverConfig(); - - return - new WidgetRevolverConfig( - stylePalette: $widgetSettings->getStylePalette() ?? $config->getStylePalette(), - charPalette: $widgetSettings->getCharPalette() ?? $config->getCharPalette(), - revolverConfig: $config->getRevolverConfig(), - ); - } -} diff --git a/src/Spinner/Core/Config/Factory/RuntimeWidgetConfigFactory.php b/src/Spinner/Core/Config/Factory/RuntimeWidgetConfigFactory.php deleted file mode 100644 index 6794ef80..00000000 --- a/src/Spinner/Core/Config/Factory/RuntimeWidgetConfigFactory.php +++ /dev/null @@ -1,71 +0,0 @@ -widgetConfig; - } - - $leadingSpacer = $this->getLeadingSpacer($widgetSettings); - $trailingSpacer = $this->getTrailingSpacer($widgetSettings); - $revolverConfig = $this->getWidgetRevolverConfig($widgetSettings); - - return - new WidgetConfig( - leadingSpacer: $leadingSpacer, - trailingSpacer: $trailingSpacer, - revolverConfig: $revolverConfig, - ); - } - - protected function getLeadingSpacer(IWidgetSettings $widgetSettings): IFrame - { - return - $widgetSettings->getLeadingSpacer() - ?? - $this->widgetConfig->getLeadingSpacer(); - } - - protected function getTrailingSpacer(IWidgetSettings $widgetSettings): IFrame - { - return - $widgetSettings->getTrailingSpacer() - ?? - $this->widgetConfig->getTrailingSpacer(); - } - - private function getWidgetRevolverConfig(IWidgetSettings $widgetSettings): IWidgetRevolverConfig - { - $config = $this->widgetConfig->getWidgetRevolverConfig(); - - return - new WidgetRevolverConfig( - stylePalette: $widgetSettings->getStylePalette() ?? $config->getStylePalette(), - charPalette: $widgetSettings->getCharPalette() ?? $config->getCharPalette(), - revolverConfig: $config->getRevolverConfig(), - ); - } -} diff --git a/src/Spinner/Core/Config/Factory/WidgetConfigFactory.php b/src/Spinner/Core/Config/Factory/WidgetConfigFactory.php index 73e5bbbe..fb81a321 100644 --- a/src/Spinner/Core/Config/Factory/WidgetConfigFactory.php +++ b/src/Spinner/Core/Config/Factory/WidgetConfigFactory.php @@ -8,76 +8,60 @@ use AlecRabbit\Spinner\Core\Config\Contract\Factory\IWidgetConfigFactory; use AlecRabbit\Spinner\Core\Config\Contract\IWidgetConfig; use AlecRabbit\Spinner\Core\Config\Contract\IWidgetRevolverConfig; -use AlecRabbit\Spinner\Core\Config\RevolverConfig; -use AlecRabbit\Spinner\Core\Config\Solver\Contract\IWidgetSettingsSolver; use AlecRabbit\Spinner\Core\Config\WidgetConfig; use AlecRabbit\Spinner\Core\Config\WidgetRevolverConfig; use AlecRabbit\Spinner\Core\Settings\Contract\IWidgetSettings; -use AlecRabbit\Spinner\Exception\DomainException; -final class WidgetConfigFactory implements IWidgetConfigFactory +final readonly class WidgetConfigFactory implements IWidgetConfigFactory { public function __construct( - protected IWidgetSettingsSolver $widgetSettingsSolver, + protected IWidgetConfig $widgetConfig, ) { } public function create(IWidgetConfig|IWidgetSettings|null $widgetSettings = null): IWidgetConfig { - self::assertWidgetSettings($widgetSettings); + if ($widgetSettings instanceof IWidgetConfig) { + return $widgetSettings; + } - $widgetSettings = $this->widgetSettingsSolver->solve(); + if ($widgetSettings === null) { + return $this->widgetConfig; + } - return - new WidgetConfig( - leadingSpacer: $this->getLeadingSpacer($widgetSettings), - trailingSpacer: $this->getTrailingSpacer($widgetSettings), - revolverConfig: $this->getWidgetRevolverConfig($widgetSettings), - ); - } + $leadingSpacer = $this->getLeadingSpacer($widgetSettings); + $trailingSpacer = $this->getTrailingSpacer($widgetSettings); + $revolverConfig = $this->getWidgetRevolverConfig($widgetSettings); - private static function assertWidgetSettings(IWidgetConfig|IWidgetSettings|null $widgetSettings): void - { - match (true) { - $widgetSettings instanceof IWidgetSettings => throw new DomainException('Widget settings is not expected.'), - $widgetSettings instanceof IWidgetConfig => throw new DomainException('Widget config is not expected.'), - default => null, - }; + return new WidgetConfig( + leadingSpacer: $leadingSpacer, + trailingSpacer: $trailingSpacer, + revolverConfig: $revolverConfig, + ); } - protected function getLeadingSpacer(IWidgetSettings $widgetSettings): IFrame + private function getLeadingSpacer(IWidgetSettings $widgetSettings): IFrame { - return - $widgetSettings->getLeadingSpacer() + return $widgetSettings->getLeadingSpacer() ?? - throw new DomainException('Leading spacer expected to be set.'); + $this->widgetConfig->getLeadingSpacer(); } - protected function getTrailingSpacer(IWidgetSettings $widgetSettings): IFrame + private function getTrailingSpacer(IWidgetSettings $widgetSettings): IFrame { - return - $widgetSettings->getTrailingSpacer() + return $widgetSettings->getTrailingSpacer() ?? - throw new DomainException('Trailing spacer expected to be set.'); + $this->widgetConfig->getTrailingSpacer(); } private function getWidgetRevolverConfig(IWidgetSettings $widgetSettings): IWidgetRevolverConfig { - $stylePalette = - $widgetSettings->getStylePalette() - ?? - throw new DomainException('Style palette expected to be set.'); - - $charPalette = - $widgetSettings->getCharPalette() - ?? - throw new DomainException('Char palette expected to be set.'); + $config = $this->widgetConfig->getWidgetRevolverConfig(); - return - new WidgetRevolverConfig( - stylePalette: $stylePalette, - charPalette: $charPalette, - revolverConfig: new RevolverConfig(), - ); + return new WidgetRevolverConfig( + stylePalette: $widgetSettings->getStylePalette() ?? $config->getStylePalette(), + charPalette: $widgetSettings->getCharPalette() ?? $config->getCharPalette(), + revolverConfig: $config->getRevolverConfig(), + ); } } diff --git a/src/Spinner/Core/Config/GeneralConfig.php b/src/Spinner/Core/Config/GeneralConfig.php new file mode 100644 index 00000000..1b5b9e11 --- /dev/null +++ b/src/Spinner/Core/Config/GeneralConfig.php @@ -0,0 +1,29 @@ +runMethodMode; + } + + /** + * @return class-string + */ + public function getIdentifier(): string + { + return IGeneralConfig::class; + } +} diff --git a/src/Spinner/Core/Config/LinkerConfig.php b/src/Spinner/Core/Config/LinkerConfig.php new file mode 100644 index 00000000..488b23ec --- /dev/null +++ b/src/Spinner/Core/Config/LinkerConfig.php @@ -0,0 +1,29 @@ +linkerMode; + } + + /** + * @return class-string + */ + public function getIdentifier(): string + { + return ILinkerConfig::class; + } +} diff --git a/src/Spinner/Core/Config/LoopConfig.php b/src/Spinner/Core/Config/LoopConfig.php index 68f34248..f0df56c1 100644 --- a/src/Spinner/Core/Config/LoopConfig.php +++ b/src/Spinner/Core/Config/LoopConfig.php @@ -5,10 +5,9 @@ namespace AlecRabbit\Spinner\Core\Config; use AlecRabbit\Spinner\Contract\Mode\AutoStartMode; -use AlecRabbit\Spinner\Contract\Mode\InitializationMode; use AlecRabbit\Spinner\Contract\Mode\SignalHandlingMode; use AlecRabbit\Spinner\Core\Config\Contract\ILoopConfig; -use AlecRabbit\Spinner\Core\ISignalHandlersContainer; +use AlecRabbit\Spinner\Core\Contract\ISignalHandlersContainer; final readonly class LoopConfig implements ILoopConfig { diff --git a/src/Spinner/Core/Config/NormalizerConfig.php b/src/Spinner/Core/Config/NormalizerConfig.php new file mode 100644 index 00000000..79766df9 --- /dev/null +++ b/src/Spinner/Core/Config/NormalizerConfig.php @@ -0,0 +1,29 @@ +normalizerMode; + } + + /** + * @return class-string + */ + public function getIdentifier(): string + { + return INormalizerConfig::class; + } +} diff --git a/src/Spinner/Core/Config/OutputConfig.php b/src/Spinner/Core/Config/OutputConfig.php index f8f9abbd..481dea09 100644 --- a/src/Spinner/Core/Config/OutputConfig.php +++ b/src/Spinner/Core/Config/OutputConfig.php @@ -12,10 +12,10 @@ final readonly class OutputConfig implements IOutputConfig { public function __construct( - protected StylingMethodMode $stylingMethodMode, - protected CursorVisibilityMode $cursorVisibilityMode, - protected InitializationMode $initializationMode, - protected mixed $stream, + private StylingMethodMode $stylingMethodMode, + private CursorVisibilityMode $cursorVisibilityMode, + private InitializationMode $initializationMode, + private mixed $stream, ) { } diff --git a/src/Spinner/Core/Config/RootWidgetConfig.php b/src/Spinner/Core/Config/RootWidgetConfig.php index d0875202..f14ba1ec 100644 --- a/src/Spinner/Core/Config/RootWidgetConfig.php +++ b/src/Spinner/Core/Config/RootWidgetConfig.php @@ -5,7 +5,6 @@ namespace AlecRabbit\Spinner\Core\Config; use AlecRabbit\Spinner\Contract\IFrame; -use AlecRabbit\Spinner\Contract\Mode\InitializationMode; use AlecRabbit\Spinner\Core\Config\Contract\IRootWidgetConfig; use AlecRabbit\Spinner\Core\Config\Contract\IWidgetRevolverConfig; diff --git a/src/Spinner/Core/Config/Solver/A/ASolver.php b/src/Spinner/Core/Config/Solver/A/ASolver.php index c8acb3be..4aaf0859 100644 --- a/src/Spinner/Core/Config/Solver/A/ASolver.php +++ b/src/Spinner/Core/Config/Solver/A/ASolver.php @@ -16,13 +16,14 @@ public function __construct( ) { } - /** @inheritDoc */ abstract public function solve(): mixed; /** - * @psalm-template T as ISettingsElement + * @psalm-template T of ISettingsElement + * * @psalm-param ISettings $settings * @psalm-param class-string $id + * * @psalm-return T|null */ protected function extractSettingsElement(ISettings $settings, string $id): ?ISettingsElement diff --git a/src/Spinner/Core/Config/Solver/AutoStartModeSolver.php b/src/Spinner/Core/Config/Solver/AutoStartModeSolver.php index c3e57765..7d3998dc 100644 --- a/src/Spinner/Core/Config/Solver/AutoStartModeSolver.php +++ b/src/Spinner/Core/Config/Solver/AutoStartModeSolver.php @@ -10,19 +10,17 @@ use AlecRabbit\Spinner\Core\Config\Solver\Contract\IAutoStartModeSolver; use AlecRabbit\Spinner\Core\Settings\Contract\ILoopSettings; use AlecRabbit\Spinner\Core\Settings\Contract\ISettings; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; final readonly class AutoStartModeSolver extends ASolver implements IAutoStartModeSolver { - public function solve(): AutoStartMode { - return - $this->doSolve( - $this->extractOption($this->settingsProvider->getUserSettings()), - $this->extractOption($this->settingsProvider->getDetectedSettings()), - $this->extractOption($this->settingsProvider->getDefaultSettings()), - ); + return $this->doSolve( + $this->extractOption($this->settingsProvider->getUserSettings()), + $this->extractOption($this->settingsProvider->getDetectedSettings()), + $this->extractOption($this->settingsProvider->getDefaultSettings()), + ); } private function doSolve( @@ -31,141 +29,140 @@ private function doSolve( ?AutoStartOption $defaultOption ): AutoStartMode { $options = [$userOption, $detectedOption, $defaultOption]; - return - match ($options) { - [ - AutoStartOption::AUTO, - AutoStartOption::ENABLED, - AutoStartOption::DISABLED, - ], - [ - AutoStartOption::AUTO, - AutoStartOption::ENABLED, - AutoStartOption::ENABLED, - ], - [ - AutoStartOption::ENABLED, - AutoStartOption::ENABLED, - AutoStartOption::ENABLED, - ], - [ - AutoStartOption::ENABLED, - AutoStartOption::DISABLED, - AutoStartOption::DISABLED, - ], - [ - AutoStartOption::ENABLED, - null, - null, - ], - [ - AutoStartOption::AUTO, - AutoStartOption::ENABLED, - null, - ], - [ - AutoStartOption::AUTO, - null, - AutoStartOption::ENABLED, - ], - [ - null, - AutoStartOption::AUTO, - AutoStartOption::ENABLED, - ], - [ - null, - AutoStartOption::ENABLED, - null, - ], - [ - null, - AutoStartOption::ENABLED, - AutoStartOption::ENABLED, - ], - [ - null, - null, - AutoStartOption::ENABLED, - ] => AutoStartMode::ENABLED, - [ - AutoStartOption::AUTO, - AutoStartOption::DISABLED, - AutoStartOption::DISABLED, - ], - [ - AutoStartOption::DISABLED, - AutoStartOption::DISABLED, - AutoStartOption::DISABLED, - ], - [ - AutoStartOption::DISABLED, - AutoStartOption::ENABLED, - AutoStartOption::ENABLED, - ], - [ - AutoStartOption::AUTO, - AutoStartOption::DISABLED, - null, - ], - [ - AutoStartOption::AUTO, - null, - AutoStartOption::DISABLED, - ], - [ - null, - AutoStartOption::AUTO, - AutoStartOption::DISABLED, - ], - [ - null, - AutoStartOption::DISABLED, - null, - ], - [ - null, - AutoStartOption::DISABLED, - AutoStartOption::DISABLED, - ], - [ - null, - AutoStartOption::DISABLED, - AutoStartOption::ENABLED, - ], - [ - null, - AutoStartOption::ENABLED, - AutoStartOption::DISABLED, - ], - [ - AutoStartOption::DISABLED, - AutoStartOption::DISABLED, - AutoStartOption::ENABLED, - ], - [ - AutoStartOption::DISABLED, - null, - null, - ], - [ - null, - null, - AutoStartOption::DISABLED, - ] => AutoStartMode::DISABLED, - default => throw new InvalidArgumentException( + return match ($options) { + [ + AutoStartOption::AUTO, + AutoStartOption::ENABLED, + AutoStartOption::DISABLED, + ], + [ + AutoStartOption::AUTO, + AutoStartOption::ENABLED, + AutoStartOption::ENABLED, + ], + [ + AutoStartOption::ENABLED, + AutoStartOption::ENABLED, + AutoStartOption::ENABLED, + ], + [ + AutoStartOption::ENABLED, + AutoStartOption::DISABLED, + AutoStartOption::DISABLED, + ], + [ + AutoStartOption::ENABLED, + null, + null, + ], + [ + AutoStartOption::AUTO, + AutoStartOption::ENABLED, + null, + ], + [ + AutoStartOption::AUTO, + null, + AutoStartOption::ENABLED, + ], + [ + null, + AutoStartOption::AUTO, + AutoStartOption::ENABLED, + ], + [ + null, + AutoStartOption::ENABLED, + null, + ], + [ + null, + AutoStartOption::ENABLED, + AutoStartOption::ENABLED, + ], + [ + null, + null, + AutoStartOption::ENABLED, + ] => AutoStartMode::ENABLED, + [ + AutoStartOption::AUTO, + AutoStartOption::DISABLED, + AutoStartOption::DISABLED, + ], + [ + AutoStartOption::DISABLED, + AutoStartOption::DISABLED, + AutoStartOption::DISABLED, + ], + [ + AutoStartOption::DISABLED, + AutoStartOption::ENABLED, + AutoStartOption::ENABLED, + ], + [ + AutoStartOption::AUTO, + AutoStartOption::DISABLED, + null, + ], + [ + AutoStartOption::AUTO, + null, + AutoStartOption::DISABLED, + ], + [ + null, + AutoStartOption::AUTO, + AutoStartOption::DISABLED, + ], + [ + null, + AutoStartOption::DISABLED, + null, + ], + [ + null, + AutoStartOption::DISABLED, + AutoStartOption::DISABLED, + ], + [ + null, + AutoStartOption::DISABLED, + AutoStartOption::ENABLED, + ], + [ + null, + AutoStartOption::ENABLED, + AutoStartOption::DISABLED, + ], + [ + AutoStartOption::DISABLED, + AutoStartOption::DISABLED, + AutoStartOption::ENABLED, + ], + [ + AutoStartOption::DISABLED, + null, + null, + ], + [ + null, + null, + AutoStartOption::DISABLED, + ] => AutoStartMode::DISABLED, + default => throw new InvalidArgument( + sprintf( + 'Unable to solve "%s". From values %s.', + AutoStartMode::class, sprintf( - 'Unable to solve "%s". From values %s.', - AutoStartMode::class, - sprintf( - '[%s, %s, %s]', - $userOption?->name ?? 'null', - $detectedOption?->name ?? 'null', - $defaultOption?->name ?? 'null', - ), - ) + '[%s, %s, %s]', + $userOption?->name ?? 'null', + $detectedOption?->name ?? 'null', + $defaultOption?->name ?? 'null', + ), ) - }; + ) + }; } protected function extractOption(ISettings $settings): ?AutoStartOption diff --git a/src/Spinner/Core/Config/Solver/Contract/IDriverMessagesSolver.php b/src/Spinner/Core/Config/Solver/Contract/IDriverMessagesSolver.php new file mode 100644 index 00000000..49309023 --- /dev/null +++ b/src/Spinner/Core/Config/Solver/Contract/IDriverMessagesSolver.php @@ -0,0 +1,12 @@ +doSolve( - $this->extractOption($this->settingsProvider->getUserSettings()), - $this->extractOption($this->settingsProvider->getDetectedSettings()), - $this->extractOption($this->settingsProvider->getDefaultSettings()), - ); + return $this->doSolve( + $this->extractOption($this->settingsProvider->getUserSettings()), + $this->extractOption($this->settingsProvider->getDetectedSettings()), + $this->extractOption($this->settingsProvider->getDefaultSettings()), + ); } private function doSolve( @@ -30,129 +29,125 @@ private function doSolve( ): CursorVisibilityMode { $options = [$userOption, $detectedOption, $defaultOption]; - return - match ($options) { - [ - CursorVisibilityOption::VISIBLE, - CursorVisibilityOption::VISIBLE, - CursorVisibilityOption::VISIBLE, - ], - [ - CursorVisibilityOption::VISIBLE, - CursorVisibilityOption::AUTO, - CursorVisibilityOption::HIDDEN, - ], - [ - CursorVisibilityOption::AUTO, - CursorVisibilityOption::VISIBLE, - CursorVisibilityOption::VISIBLE, - ], - [ - CursorVisibilityOption::AUTO, - CursorVisibilityOption::VISIBLE, - null, - ], - [ - CursorVisibilityOption::AUTO, - null, - CursorVisibilityOption::VISIBLE, - ], - [ - null, - null, - CursorVisibilityOption::VISIBLE, - ], - [ - null, - CursorVisibilityOption::AUTO, - CursorVisibilityOption::VISIBLE, - ], - [ - null, - CursorVisibilityOption::VISIBLE, - null, - ], - [ - null, - CursorVisibilityOption::VISIBLE, - CursorVisibilityOption::VISIBLE, - ], - [ - CursorVisibilityOption::VISIBLE, - null, - null, - ] - => CursorVisibilityMode::VISIBLE, - [ - CursorVisibilityOption::AUTO, - CursorVisibilityOption::HIDDEN, - CursorVisibilityOption::VISIBLE, - ], - [ - CursorVisibilityOption::HIDDEN, - CursorVisibilityOption::VISIBLE, - CursorVisibilityOption::VISIBLE, - ], - [ - CursorVisibilityOption::AUTO, - CursorVisibilityOption::HIDDEN, - null, - ], - [ - CursorVisibilityOption::AUTO, - null, - CursorVisibilityOption::HIDDEN, - ], - [ - CursorVisibilityOption::AUTO, - CursorVisibilityOption::AUTO, - CursorVisibilityOption::HIDDEN, - ], - [ - null, - null, - CursorVisibilityOption::HIDDEN, - ], - [ - null, - CursorVisibilityOption::AUTO, - CursorVisibilityOption::HIDDEN, - ], - [ - null, - CursorVisibilityOption::HIDDEN, - null, - ], - [ - null, - CursorVisibilityOption::HIDDEN, - CursorVisibilityOption::HIDDEN, - ], - [ - CursorVisibilityOption::HIDDEN, - null, - null, - ] - => CursorVisibilityMode::HIDDEN, - default // DEFAULT BRANCH - => throw new InvalidArgumentException( + return match ($options) { + [ + CursorVisibilityOption::VISIBLE, + CursorVisibilityOption::VISIBLE, + CursorVisibilityOption::VISIBLE, + ], + [ + CursorVisibilityOption::VISIBLE, + CursorVisibilityOption::AUTO, + CursorVisibilityOption::HIDDEN, + ], + [ + CursorVisibilityOption::AUTO, + CursorVisibilityOption::VISIBLE, + CursorVisibilityOption::VISIBLE, + ], + [ + CursorVisibilityOption::AUTO, + CursorVisibilityOption::VISIBLE, + null, + ], + [ + CursorVisibilityOption::AUTO, + null, + CursorVisibilityOption::VISIBLE, + ], + [ + null, + null, + CursorVisibilityOption::VISIBLE, + ], + [ + null, + CursorVisibilityOption::AUTO, + CursorVisibilityOption::VISIBLE, + ], + [ + null, + CursorVisibilityOption::VISIBLE, + null, + ], + [ + null, + CursorVisibilityOption::VISIBLE, + CursorVisibilityOption::VISIBLE, + ], + [ + CursorVisibilityOption::VISIBLE, + null, + null, + ] => CursorVisibilityMode::VISIBLE, + [ + CursorVisibilityOption::AUTO, + CursorVisibilityOption::HIDDEN, + CursorVisibilityOption::VISIBLE, + ], + [ + CursorVisibilityOption::HIDDEN, + CursorVisibilityOption::VISIBLE, + CursorVisibilityOption::VISIBLE, + ], + [ + CursorVisibilityOption::AUTO, + CursorVisibilityOption::HIDDEN, + null, + ], + [ + CursorVisibilityOption::AUTO, + null, + CursorVisibilityOption::HIDDEN, + ], + [ + CursorVisibilityOption::AUTO, + CursorVisibilityOption::AUTO, + CursorVisibilityOption::HIDDEN, + ], + [ + null, + null, + CursorVisibilityOption::HIDDEN, + ], + [ + null, + CursorVisibilityOption::AUTO, + CursorVisibilityOption::HIDDEN, + ], + [ + null, + CursorVisibilityOption::HIDDEN, + null, + ], + [ + null, + CursorVisibilityOption::HIDDEN, + CursorVisibilityOption::HIDDEN, + ], + [ + CursorVisibilityOption::HIDDEN, + null, + null, + ] => CursorVisibilityMode::HIDDEN, + default // DEFAULT BRANCH + => throw new InvalidArgument( + sprintf( + 'Unable to solve "%s". From values %s.', + CursorVisibilityMode::class, sprintf( - 'Unable to solve "%s". From values %s.', - CursorVisibilityMode::class, - sprintf( - '[%s, %s, %s]', - $userOption?->name ?? 'null', - $detectedOption?->name ?? 'null', - $defaultOption?->name ?? 'null', - ), - ) - ), - }; + '[%s, %s, %s]', + $userOption?->name ?? 'null', + $detectedOption?->name ?? 'null', + $defaultOption?->name ?? 'null', + ), + ) + ), + }; } protected function extractOption(ISettings $settings): ?CursorVisibilityOption { return $this->extractSettingsElement($settings, IOutputSettings::class)?->getCursorVisibilityOption(); } - } diff --git a/src/Spinner/Core/Config/Solver/DriverMessagesSolver.php b/src/Spinner/Core/Config/Solver/DriverMessagesSolver.php new file mode 100644 index 00000000..fd52542a --- /dev/null +++ b/src/Spinner/Core/Config/Solver/DriverMessagesSolver.php @@ -0,0 +1,58 @@ +doSolve( + $this->extractMessages($this->settingsProvider->getUserSettings()), + $this->extractMessages($this->settingsProvider->getDetectedSettings()), + $this->extractMessages($this->settingsProvider->getDefaultSettings()), + ); + } + + private function doSolve( + ?IMessages $userMessages, + ?IMessages $detectedMessages, + ?IMessages $defaultMessages, + ): IDriverMessages { + $finalMessage = + $userMessages?->getFinalMessage() + ?? $detectedMessages?->getFinalMessage() + ?? $defaultMessages?->getFinalMessage() + ?? throw new LogicException( + sprintf('Unable to solve "%s". (for final message)', IDriverMessages::class) + ); + + $interruptionMessage = + $userMessages?->getInterruptionMessage() + ?? $detectedMessages?->getInterruptionMessage() + ?? $defaultMessages?->getInterruptionMessage() + ?? throw new LogicException( + sprintf('Unable to solve "%s". (for interrupt message)', IDriverMessages::class) + ); + + return new DriverMessages( + finalMessage: $finalMessage, + interruptionMessage: $interruptionMessage, + ); + } + + private function extractMessages(ISettings $settings): ?IMessages + { + return $this->extractSettingsElement($settings, IDriverSettings::class)?->getMessages(); + } +} diff --git a/src/Spinner/Core/Config/Solver/DriverModeSolver.php b/src/Spinner/Core/Config/Solver/DriverModeSolver.php new file mode 100644 index 00000000..2965fb15 --- /dev/null +++ b/src/Spinner/Core/Config/Solver/DriverModeSolver.php @@ -0,0 +1,176 @@ +doSolve( + $this->extractOption($this->settingsProvider->getUserSettings()), + $this->extractOption($this->settingsProvider->getDetectedSettings()), + $this->extractOption($this->settingsProvider->getDefaultSettings()), + ); + } + + private function doSolve( + ?DriverOption $userOption, + ?DriverOption $detectedOption, + ?DriverOption $defaultOption + ): DriverMode { + $options = [$userOption, $detectedOption, $defaultOption]; + return match ($options) { + [ + DriverOption::AUTO, + DriverOption::ENABLED, + DriverOption::DISABLED, + ], + [ + DriverOption::AUTO, + DriverOption::ENABLED, + DriverOption::ENABLED, + ], + [ + DriverOption::ENABLED, + DriverOption::ENABLED, + DriverOption::ENABLED, + ], + [ + DriverOption::ENABLED, + null, + DriverOption::ENABLED, + ], + [ + DriverOption::ENABLED, + DriverOption::DISABLED, + DriverOption::DISABLED, + ], + [ + DriverOption::ENABLED, + null, + null, + ], + [ + DriverOption::AUTO, + DriverOption::ENABLED, + null, + ], + [ + DriverOption::AUTO, + null, + DriverOption::ENABLED, + ], + [ + null, + DriverOption::AUTO, + DriverOption::ENABLED, + ], + [ + null, + DriverOption::ENABLED, + null, + ], + [ + null, + DriverOption::ENABLED, + DriverOption::ENABLED, + ], + [ + null, + null, + DriverOption::ENABLED, + ] => DriverMode::ENABLED, + [ + DriverOption::AUTO, + DriverOption::DISABLED, + DriverOption::DISABLED, + ], + [ + DriverOption::DISABLED, + DriverOption::DISABLED, + DriverOption::DISABLED, + ], + [ + DriverOption::DISABLED, + null, + DriverOption::ENABLED, + ], + [ + DriverOption::AUTO, + DriverOption::DISABLED, + null, + ], + [ + DriverOption::AUTO, + null, + DriverOption::DISABLED, + ], + [ + null, + DriverOption::AUTO, + DriverOption::DISABLED, + ], + [ + null, + DriverOption::DISABLED, + null, + ], + [ + null, + DriverOption::DISABLED, + DriverOption::DISABLED, + ], + [ + null, + DriverOption::DISABLED, + DriverOption::ENABLED, + ], + [ + null, + DriverOption::ENABLED, + DriverOption::DISABLED, + ], + [ + DriverOption::DISABLED, + DriverOption::ENABLED, + DriverOption::ENABLED, + ], + [ + DriverOption::DISABLED, + null, + null, + ], + [ + null, + null, + DriverOption::DISABLED, + ] => DriverMode::DISABLED, + default => throw new InvalidArgument( + sprintf( + 'Unable to solve "%s". From values %s.', + DriverMode::class, + sprintf( + '[%s, %s, %s]', + $userOption?->name ?? 'null', + $detectedOption?->name ?? 'null', + $defaultOption?->name ?? 'null', + ), + ) + ) + }; + } + + protected function extractOption(ISettings $settings): ?DriverOption + { + return $this->extractSettingsElement($settings, IDriverSettings::class)?->getDriverOption(); + } +} diff --git a/src/Spinner/Core/Config/Solver/InitializationModeSolver.php b/src/Spinner/Core/Config/Solver/InitializationModeSolver.php index 60a4e2e4..f90aef3e 100644 --- a/src/Spinner/Core/Config/Solver/InitializationModeSolver.php +++ b/src/Spinner/Core/Config/Solver/InitializationModeSolver.php @@ -7,21 +7,19 @@ use AlecRabbit\Spinner\Contract\Mode\InitializationMode; use AlecRabbit\Spinner\Contract\Option\InitializationOption; use AlecRabbit\Spinner\Core\Config\Solver\A\ASolver; -use AlecRabbit\Spinner\Core\Settings\Contract\IDriverSettings; use AlecRabbit\Spinner\Core\Settings\Contract\IOutputSettings; use AlecRabbit\Spinner\Core\Settings\Contract\ISettings; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; final readonly class InitializationModeSolver extends ASolver implements Contract\IInitializationModeSolver { public function solve(): InitializationMode { - return - $this->doSolve( - $this->extractOption($this->settingsProvider->getUserSettings()), - $this->extractOption($this->settingsProvider->getDetectedSettings()), - $this->extractOption($this->settingsProvider->getDefaultSettings()), - ); + return $this->doSolve( + $this->extractOption($this->settingsProvider->getUserSettings()), + $this->extractOption($this->settingsProvider->getDetectedSettings()), + $this->extractOption($this->settingsProvider->getDefaultSettings()), + ); } private function doSolve( @@ -30,126 +28,125 @@ private function doSolve( ?InitializationOption $defaultOption ): InitializationMode { $options = [$userOption, $detectedOption, $defaultOption]; - return - match ($options) { - [ - InitializationOption::AUTO, - InitializationOption::ENABLED, - InitializationOption::DISABLED, - ], - [ - InitializationOption::AUTO, - InitializationOption::AUTO, - InitializationOption::ENABLED, - ], - [ - InitializationOption::ENABLED, - InitializationOption::DISABLED, - InitializationOption::DISABLED, - ], - [ - InitializationOption::ENABLED, - null, - null, - ], - [ - InitializationOption::AUTO, - InitializationOption::ENABLED, - null, - ], - [ - InitializationOption::AUTO, - null, - InitializationOption::ENABLED, - ], - [ - null, - InitializationOption::AUTO, - InitializationOption::ENABLED, - ], - [ - null, - InitializationOption::ENABLED, - null, - ], - [ - null, - InitializationOption::ENABLED, - InitializationOption::ENABLED, - ], - [ - null, - null, - InitializationOption::ENABLED, - ] => InitializationMode::ENABLED, - [ - InitializationOption::AUTO, - InitializationOption::DISABLED, - InitializationOption::DISABLED, - ], - [ - InitializationOption::DISABLED, - InitializationOption::DISABLED, - InitializationOption::DISABLED, - ], - [ - InitializationOption::AUTO, - InitializationOption::DISABLED, - null, - ], - [ - InitializationOption::AUTO, - null, - InitializationOption::DISABLED, - ], - [ - null, - InitializationOption::AUTO, - InitializationOption::DISABLED, - ], - [ - null, - InitializationOption::DISABLED, - null, - ], - [ - null, - InitializationOption::DISABLED, - InitializationOption::DISABLED, - ], - [ - null, - InitializationOption::DISABLED, - InitializationOption::ENABLED, - ], - [ - null, - InitializationOption::ENABLED, - InitializationOption::DISABLED, - ], - [ - InitializationOption::DISABLED, - null, - null, - ], - [ - null, - null, - InitializationOption::DISABLED, - ] => InitializationMode::DISABLED, - default => throw new InvalidArgumentException( + return match ($options) { + [ + InitializationOption::AUTO, + InitializationOption::ENABLED, + InitializationOption::DISABLED, + ], + [ + InitializationOption::AUTO, + InitializationOption::AUTO, + InitializationOption::ENABLED, + ], + [ + InitializationOption::ENABLED, + InitializationOption::DISABLED, + InitializationOption::DISABLED, + ], + [ + InitializationOption::ENABLED, + null, + null, + ], + [ + InitializationOption::AUTO, + InitializationOption::ENABLED, + null, + ], + [ + InitializationOption::AUTO, + null, + InitializationOption::ENABLED, + ], + [ + null, + InitializationOption::AUTO, + InitializationOption::ENABLED, + ], + [ + null, + InitializationOption::ENABLED, + null, + ], + [ + null, + InitializationOption::ENABLED, + InitializationOption::ENABLED, + ], + [ + null, + null, + InitializationOption::ENABLED, + ] => InitializationMode::ENABLED, + [ + InitializationOption::AUTO, + InitializationOption::DISABLED, + InitializationOption::DISABLED, + ], + [ + InitializationOption::DISABLED, + InitializationOption::DISABLED, + InitializationOption::DISABLED, + ], + [ + InitializationOption::AUTO, + InitializationOption::DISABLED, + null, + ], + [ + InitializationOption::AUTO, + null, + InitializationOption::DISABLED, + ], + [ + null, + InitializationOption::AUTO, + InitializationOption::DISABLED, + ], + [ + null, + InitializationOption::DISABLED, + null, + ], + [ + null, + InitializationOption::DISABLED, + InitializationOption::DISABLED, + ], + [ + null, + InitializationOption::DISABLED, + InitializationOption::ENABLED, + ], + [ + null, + InitializationOption::ENABLED, + InitializationOption::DISABLED, + ], + [ + InitializationOption::DISABLED, + null, + null, + ], + [ + null, + null, + InitializationOption::DISABLED, + ] => InitializationMode::DISABLED, + default => throw new InvalidArgument( + sprintf( + 'Unable to solve "%s". From values %s.', + InitializationMode::class, sprintf( - 'Unable to solve "%s". From values %s.', - InitializationMode::class, - sprintf( - '[%s, %s, %s]', - $userOption?->name ?? 'null', - $detectedOption?->name ?? 'null', - $defaultOption?->name ?? 'null', - ), - ) + '[%s, %s, %s]', + $userOption?->name ?? 'null', + $detectedOption?->name ?? 'null', + $defaultOption?->name ?? 'null', + ), ) - }; + ) + }; } protected function extractOption(ISettings $settings): ?InitializationOption diff --git a/src/Spinner/Core/Config/Solver/LinkerModeSolver.php b/src/Spinner/Core/Config/Solver/LinkerModeSolver.php index d7c2642b..1f10a288 100644 --- a/src/Spinner/Core/Config/Solver/LinkerModeSolver.php +++ b/src/Spinner/Core/Config/Solver/LinkerModeSolver.php @@ -7,20 +7,19 @@ use AlecRabbit\Spinner\Contract\Mode\LinkerMode; use AlecRabbit\Spinner\Contract\Option\LinkerOption; use AlecRabbit\Spinner\Core\Config\Solver\A\ASolver; -use AlecRabbit\Spinner\Core\Settings\Contract\IDriverSettings; +use AlecRabbit\Spinner\Core\Settings\Contract\ILinkerSettings; use AlecRabbit\Spinner\Core\Settings\Contract\ISettings; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; final readonly class LinkerModeSolver extends ASolver implements Contract\ILinkerModeSolver { public function solve(): LinkerMode { - return - $this->doSolve( - $this->extractOption($this->settingsProvider->getUserSettings()), - $this->extractOption($this->settingsProvider->getDetectedSettings()), - $this->extractOption($this->settingsProvider->getDefaultSettings()), - ); + return $this->doSolve( + $this->extractOption($this->settingsProvider->getUserSettings()), + $this->extractOption($this->settingsProvider->getDetectedSettings()), + $this->extractOption($this->settingsProvider->getDefaultSettings()), + ); } private function doSolve( @@ -29,130 +28,139 @@ private function doSolve( ?LinkerOption $defaultOption ): LinkerMode { $options = [$userOption, $detectedOption, $defaultOption]; - return - match ($options) { - [ - LinkerOption::AUTO, - LinkerOption::ENABLED, - LinkerOption::DISABLED, - ], - [ - LinkerOption::AUTO, - LinkerOption::ENABLED, - LinkerOption::ENABLED, - ], - [ - LinkerOption::ENABLED, - LinkerOption::DISABLED, - LinkerOption::DISABLED, - ], - [ - LinkerOption::ENABLED, - null, - null, - ], - [ - LinkerOption::AUTO, - LinkerOption::ENABLED, - null, - ], - [ - LinkerOption::AUTO, - null, - LinkerOption::ENABLED, - ], - [ - null, - LinkerOption::AUTO, - LinkerOption::ENABLED, - ], - [ - null, - LinkerOption::ENABLED, - null, - ], - [ - null, - LinkerOption::ENABLED, - LinkerOption::ENABLED, - ], - [ - null, - null, - LinkerOption::ENABLED, - ] => LinkerMode::ENABLED, - [ - LinkerOption::AUTO, - LinkerOption::DISABLED, - LinkerOption::DISABLED, - ], - [ - LinkerOption::DISABLED, - LinkerOption::DISABLED, - LinkerOption::DISABLED, - ], - [ - LinkerOption::AUTO, - LinkerOption::DISABLED, - null, - ], - [ - LinkerOption::AUTO, - null, - LinkerOption::DISABLED, - ], - [ - null, - LinkerOption::AUTO, - LinkerOption::DISABLED, - ], - [ - null, - LinkerOption::DISABLED, - null, - ], - [ - null, - LinkerOption::DISABLED, - LinkerOption::DISABLED, - ], - [ - null, - LinkerOption::DISABLED, - LinkerOption::ENABLED, - ], - [ - null, - LinkerOption::ENABLED, - LinkerOption::DISABLED, - ], - [ - LinkerOption::DISABLED, - null, - null, - ], - [ - null, - null, - LinkerOption::DISABLED, - ] => LinkerMode::DISABLED, - default => throw new InvalidArgumentException( + return match ($options) { + [ + LinkerOption::AUTO, + LinkerOption::ENABLED, + LinkerOption::DISABLED, + ], + [ + LinkerOption::AUTO, + LinkerOption::ENABLED, + LinkerOption::ENABLED, + ], + [ + LinkerOption::ENABLED, + LinkerOption::ENABLED, + LinkerOption::ENABLED, + ], + [ + LinkerOption::ENABLED, + LinkerOption::DISABLED, + LinkerOption::DISABLED, + ], + [ + LinkerOption::ENABLED, + null, + null, + ], + [ + LinkerOption::AUTO, + LinkerOption::ENABLED, + null, + ], + [ + LinkerOption::AUTO, + null, + LinkerOption::ENABLED, + ], + [ + null, + LinkerOption::AUTO, + LinkerOption::ENABLED, + ], + [ + null, + LinkerOption::ENABLED, + null, + ], + [ + null, + LinkerOption::ENABLED, + LinkerOption::ENABLED, + ], + [ + null, + null, + LinkerOption::ENABLED, + ] => LinkerMode::ENABLED, + [ + LinkerOption::AUTO, + LinkerOption::DISABLED, + LinkerOption::DISABLED, + ], + [ + LinkerOption::DISABLED, + LinkerOption::DISABLED, + LinkerOption::DISABLED, + ], + [ + LinkerOption::AUTO, + LinkerOption::DISABLED, + null, + ], + [ + LinkerOption::AUTO, + null, + LinkerOption::DISABLED, + ], + [ + null, + LinkerOption::AUTO, + LinkerOption::DISABLED, + ], + [ + null, + LinkerOption::DISABLED, + null, + ], + [ + null, + LinkerOption::DISABLED, + LinkerOption::DISABLED, + ], + [ + null, + LinkerOption::DISABLED, + LinkerOption::ENABLED, + ], + [ + null, + LinkerOption::ENABLED, + LinkerOption::DISABLED, + ], + [ + LinkerOption::DISABLED, + LinkerOption::ENABLED, + LinkerOption::ENABLED, + ], + [ + LinkerOption::DISABLED, + null, + null, + ], + [ + null, + null, + LinkerOption::DISABLED, + ] => LinkerMode::DISABLED, + default => throw new InvalidArgument( + sprintf( + 'Unable to solve "%s". From values %s.', + LinkerMode::class, sprintf( - 'Unable to solve "%s". From values %s.', - LinkerMode::class, - sprintf( - '[%s, %s, %s]', - $userOption?->name ?? 'null', - $detectedOption?->name ?? 'null', - $defaultOption?->name ?? 'null', - ), - ) + '[%s, %s, %s]', + $userOption?->name ?? 'null', + $detectedOption?->name ?? 'null', + $defaultOption?->name ?? 'null', + ), ) - }; + ) + }; } protected function extractOption(ISettings $settings): ?LinkerOption { - return $this->extractSettingsElement($settings, IDriverSettings::class)?->getLinkerOption(); + return $this->extractSettingsElement($settings, ILinkerSettings::class)?->getLinkerOption(); } } diff --git a/src/Spinner/Core/Config/Solver/NormalizerModeSolver.php b/src/Spinner/Core/Config/Solver/NormalizerModeSolver.php index 75f3f02a..7fa5a789 100644 --- a/src/Spinner/Core/Config/Solver/NormalizerModeSolver.php +++ b/src/Spinner/Core/Config/Solver/NormalizerModeSolver.php @@ -8,20 +8,19 @@ use AlecRabbit\Spinner\Contract\Option\NormalizerOption; use AlecRabbit\Spinner\Core\Config\Solver\A\ASolver; use AlecRabbit\Spinner\Core\Config\Solver\Contract\INormalizerModeSolver; -use AlecRabbit\Spinner\Core\Settings\Contract\IAuxSettings; +use AlecRabbit\Spinner\Core\Settings\Contract\INormalizerSettings; use AlecRabbit\Spinner\Core\Settings\Contract\ISettings; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; final readonly class NormalizerModeSolver extends ASolver implements INormalizerModeSolver { public function solve(): NormalizerMode { - return - $this->doSolve( - $this->extractOption($this->settingsProvider->getUserSettings()), - $this->extractOption($this->settingsProvider->getDetectedSettings()), - $this->extractOption($this->settingsProvider->getDefaultSettings()), - ); + return $this->doSolve( + $this->extractOption($this->settingsProvider->getUserSettings()), + $this->extractOption($this->settingsProvider->getDetectedSettings()), + $this->extractOption($this->settingsProvider->getDefaultSettings()), + ); } private function doSolve( @@ -39,7 +38,7 @@ private function doSolve( return $mode; } - throw new InvalidArgumentException( + throw new InvalidArgument( sprintf( 'Unable to solve "%s". From values %s.', NormalizerMode::class, @@ -55,19 +54,18 @@ private function doSolve( private function createModeFromOption(?NormalizerOption $option): ?NormalizerMode { - return - match ($option) { - NormalizerOption::SMOOTH => NormalizerMode::SMOOTH, - NormalizerOption::BALANCED => NormalizerMode::BALANCED, - NormalizerOption::PERFORMANCE => NormalizerMode::PERFORMANCE, - NormalizerOption::SLOW => NormalizerMode::SLOW, - NormalizerOption::STILL => NormalizerMode::STILL, - default => null, - }; + return match ($option) { + NormalizerOption::SMOOTH => NormalizerMode::SMOOTH, + NormalizerOption::BALANCED => NormalizerMode::BALANCED, + NormalizerOption::PERFORMANCE => NormalizerMode::PERFORMANCE, + NormalizerOption::SLOW => NormalizerMode::SLOW, + NormalizerOption::STILL => NormalizerMode::STILL, + default => null, + }; } protected function extractOption(ISettings $settings): ?NormalizerOption { - return $this->extractSettingsElement($settings, IAuxSettings::class)?->getNormalizerOption(); + return $this->extractSettingsElement($settings, INormalizerSettings::class)?->getNormalizerOption(); } } diff --git a/src/Spinner/Core/Config/Solver/RootWidgetSettingsSolver.php b/src/Spinner/Core/Config/Solver/RootWidgetSettingsSolver.php new file mode 100644 index 00000000..e49eacc7 --- /dev/null +++ b/src/Spinner/Core/Config/Solver/RootWidgetSettingsSolver.php @@ -0,0 +1,104 @@ +doSolve( + $this->extractSettings($this->settingsProvider->getUserSettings()), + $this->extractSettings($this->settingsProvider->getDetectedSettings()), + $this->extractSettings($this->settingsProvider->getDefaultSettings()), + ); + } + + private function doSolve( + ?IWidgetSettings $userSettings, + ?IWidgetSettings $detectedSettings, + ?IWidgetSettings $defaultSettings, + ): IRootWidgetSettings { + $leadingSpacer = + $this->getLeadingSpacer($userSettings, $detectedSettings, $defaultSettings); + + $trailingSpacer = + $this->getTrailingSpacer($userSettings, $detectedSettings, $defaultSettings); + + $stylePalette = + $this->getStylePalette($userSettings, $detectedSettings, $defaultSettings); + + $charPalette = + $this->getCharPalette($userSettings, $detectedSettings, $defaultSettings); + + return new RootWidgetSettings( + leadingSpacer: $leadingSpacer, + trailingSpacer: $trailingSpacer, + stylePalette: $stylePalette, + charPalette: $charPalette, + ); + } + + private function getLeadingSpacer( + ?IWidgetSettings $userSettings, + ?IWidgetSettings $detectedSettings, + ?IWidgetSettings $defaultSettings + ): ?IFrame { + return $userSettings?->getLeadingSpacer() + ?? + $detectedSettings?->getLeadingSpacer() + ?? + $defaultSettings?->getLeadingSpacer(); + } + + private function getTrailingSpacer( + ?IWidgetSettings $userSettings, + ?IWidgetSettings $detectedSettings, + ?IWidgetSettings $defaultSettings + ): ?IFrame { + return $userSettings?->getTrailingSpacer() + ?? + $detectedSettings?->getTrailingSpacer() + ?? + $defaultSettings?->getTrailingSpacer(); + } + + private function getStylePalette( + ?IWidgetSettings $userSettings, + ?IWidgetSettings $detectedSettings, + ?IWidgetSettings $defaultSettings + ): ?IPalette { + return $userSettings?->getStylePalette() + ?? + $detectedSettings?->getStylePalette() + ?? + $defaultSettings?->getStylePalette(); + } + + private function getCharPalette( + ?IWidgetSettings $userSettings, + ?IWidgetSettings $detectedSettings, + ?IWidgetSettings $defaultSettings + ): ?IPalette { + return $userSettings?->getCharPalette() + ?? + $detectedSettings?->getCharPalette() + ?? + $defaultSettings?->getCharPalette(); + } + + protected function extractSettings(ISettings $settings): ?IRootWidgetSettings + { + return $this->extractSettingsElement($settings, IRootWidgetSettings::class); + } +} diff --git a/src/Spinner/Core/Config/Solver/RunMethodModeSolver.php b/src/Spinner/Core/Config/Solver/RunMethodModeSolver.php index 2f06862e..f55a27a4 100644 --- a/src/Spinner/Core/Config/Solver/RunMethodModeSolver.php +++ b/src/Spinner/Core/Config/Solver/RunMethodModeSolver.php @@ -8,27 +8,25 @@ use AlecRabbit\Spinner\Contract\Option\RunMethodOption; use AlecRabbit\Spinner\Core\Config\Solver\A\ASolver; use AlecRabbit\Spinner\Core\Config\Solver\Contract\IRunMethodModeSolver; -use AlecRabbit\Spinner\Core\Settings\Contract\IAuxSettings; +use AlecRabbit\Spinner\Core\Settings\Contract\IGeneralSettings; use AlecRabbit\Spinner\Core\Settings\Contract\ISettings; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; use function sprintf; final readonly class RunMethodModeSolver extends ASolver implements IRunMethodModeSolver { - /** @inheritDoc */ public function solve(): RunMethodMode { - return - $this->doSolve( - $this->extractOption($this->settingsProvider->getUserSettings()), - $this->extractOption($this->settingsProvider->getDetectedSettings()), - $this->extractOption($this->settingsProvider->getDefaultSettings()), - ); + return $this->doSolve( + $this->extractOption($this->settingsProvider->getUserSettings()), + $this->extractOption($this->settingsProvider->getDetectedSettings()), + $this->extractOption($this->settingsProvider->getDefaultSettings()), + ); } /** - * @throws InvalidArgumentException + * @throws InvalidArgument */ private function doSolve( ?RunMethodOption $userOption, @@ -37,139 +35,135 @@ private function doSolve( ): RunMethodMode { $options = [$userOption, $detectedOption, $defaultOption]; - return - match ($options) { - [ - RunMethodOption::ASYNC, - RunMethodOption::ASYNC, - RunMethodOption::ASYNC, - ], - [ - RunMethodOption::AUTO, - RunMethodOption::ASYNC, - RunMethodOption::ASYNC, - ], - [ - RunMethodOption::AUTO, - RunMethodOption::ASYNC, - null, - ], - [ - RunMethodOption::AUTO, - null, - RunMethodOption::ASYNC, - ], - [ - null, - null, - RunMethodOption::ASYNC, - ], - [ - null, - RunMethodOption::AUTO, - RunMethodOption::ASYNC, - ], - [ - null, - RunMethodOption::ASYNC, - null, - ], - [ - null, - RunMethodOption::ASYNC, - RunMethodOption::ASYNC, - ], - [ - RunMethodOption::ASYNC, - null, - null, - ], - [ - RunMethodOption::ASYNC, - null, - RunMethodOption::ASYNC, - ] - => RunMethodMode::ASYNC, - [ - RunMethodOption::AUTO, - RunMethodOption::SYNCHRONOUS, - RunMethodOption::ASYNC, - ], - [ - RunMethodOption::SYNCHRONOUS, - RunMethodOption::ASYNC, - RunMethodOption::ASYNC, - ], - [ - RunMethodOption::AUTO, - RunMethodOption::SYNCHRONOUS, - null, - ], - [ - RunMethodOption::AUTO, - null, - RunMethodOption::SYNCHRONOUS, - ], - [ - null, - null, - RunMethodOption::SYNCHRONOUS, - ], - [ - null, - RunMethodOption::AUTO, - RunMethodOption::SYNCHRONOUS, - ], - [ - null, - RunMethodOption::SYNCHRONOUS, - null, - ], - [ - null, - RunMethodOption::SYNCHRONOUS, - RunMethodOption::SYNCHRONOUS, - ], - [ - RunMethodOption::SYNCHRONOUS, - null, - null, - ], - [ - RunMethodOption::SYNCHRONOUS, - null, - RunMethodOption::ASYNC, - ], - [ - null, - RunMethodOption::SYNCHRONOUS, - RunMethodOption::ASYNC, - ], - [ - RunMethodOption::SYNCHRONOUS, - RunMethodOption::SYNCHRONOUS, - RunMethodOption::ASYNC, - ] - => RunMethodMode::SYNCHRONOUS, - default // DEFAULT BRANCH - => throw new InvalidArgumentException( + return match ($options) { + [ + RunMethodOption::ASYNC, + RunMethodOption::ASYNC, + RunMethodOption::ASYNC, + ], + [ + RunMethodOption::AUTO, + RunMethodOption::ASYNC, + RunMethodOption::ASYNC, + ], + [ + RunMethodOption::AUTO, + RunMethodOption::ASYNC, + null, + ], + [ + RunMethodOption::AUTO, + null, + RunMethodOption::ASYNC, + ], + [ + null, + null, + RunMethodOption::ASYNC, + ], + [ + null, + RunMethodOption::AUTO, + RunMethodOption::ASYNC, + ], + [ + null, + RunMethodOption::ASYNC, + null, + ], + [ + null, + RunMethodOption::ASYNC, + RunMethodOption::ASYNC, + ], + [ + RunMethodOption::ASYNC, + null, + null, + ], + [ + RunMethodOption::ASYNC, + null, + RunMethodOption::ASYNC, + ] => RunMethodMode::ASYNC, + [ + RunMethodOption::AUTO, + RunMethodOption::SYNCHRONOUS, + RunMethodOption::ASYNC, + ], + [ + RunMethodOption::SYNCHRONOUS, + RunMethodOption::ASYNC, + RunMethodOption::ASYNC, + ], + [ + RunMethodOption::AUTO, + RunMethodOption::SYNCHRONOUS, + null, + ], + [ + RunMethodOption::AUTO, + null, + RunMethodOption::SYNCHRONOUS, + ], + [ + null, + null, + RunMethodOption::SYNCHRONOUS, + ], + [ + null, + RunMethodOption::AUTO, + RunMethodOption::SYNCHRONOUS, + ], + [ + null, + RunMethodOption::SYNCHRONOUS, + null, + ], + [ + null, + RunMethodOption::SYNCHRONOUS, + RunMethodOption::SYNCHRONOUS, + ], + [ + RunMethodOption::SYNCHRONOUS, + null, + null, + ], + [ + RunMethodOption::SYNCHRONOUS, + null, + RunMethodOption::ASYNC, + ], + [ + null, + RunMethodOption::SYNCHRONOUS, + RunMethodOption::ASYNC, + ], + [ + RunMethodOption::SYNCHRONOUS, + RunMethodOption::SYNCHRONOUS, + RunMethodOption::ASYNC, + ] => RunMethodMode::SYNCHRONOUS, + default // DEFAULT BRANCH + => throw new InvalidArgument( + sprintf( + 'Unable to solve "%s". From values %s.', + RunMethodMode::class, sprintf( - 'Unable to solve "%s". From values %s.', - RunMethodMode::class, - sprintf( - '[%s, %s, %s]', - $userOption?->name ?? 'null', - $detectedOption?->name ?? 'null', - $defaultOption?->name ?? 'null', - ), - ) - ), - }; + '[%s, %s, %s]', + $userOption?->name ?? 'null', + $detectedOption?->name ?? 'null', + $defaultOption?->name ?? 'null', + ), + ) + ), + }; } protected function extractOption(ISettings $settings): ?RunMethodOption { - return $this->extractSettingsElement($settings, IAuxSettings::class)?->getRunMethodOption(); + return $this->extractSettingsElement($settings, IGeneralSettings::class)?->getRunMethodOption(); } - } diff --git a/src/Spinner/Core/Config/Solver/SignalHandlersContainerSolver.php b/src/Spinner/Core/Config/Solver/SignalHandlersContainerSolver.php index 31389e16..46b74a09 100644 --- a/src/Spinner/Core/Config/Solver/SignalHandlersContainerSolver.php +++ b/src/Spinner/Core/Config/Solver/SignalHandlersContainerSolver.php @@ -6,13 +6,13 @@ use AlecRabbit\Spinner\Core\Config\Solver\A\ASolver; use AlecRabbit\Spinner\Core\Config\Solver\Contract\ISignalHandlersContainerSolver; -use AlecRabbit\Spinner\Core\ISignalHandlersContainer; +use AlecRabbit\Spinner\Core\Contract\ISignalHandlersContainer; use AlecRabbit\Spinner\Core\Settings\Contract\IHandlerCreator; use AlecRabbit\Spinner\Core\Settings\Contract\ISettings; use AlecRabbit\Spinner\Core\Settings\Contract\ISignalHandlerCreator; use AlecRabbit\Spinner\Core\Settings\Contract\ISignalHandlerSettings; use AlecRabbit\Spinner\Core\SignalHandlersContainer; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; use ArrayObject; use Traversable; @@ -20,12 +20,11 @@ { public function solve(): ISignalHandlersContainer { - return - $this->doSolve( - $this->extractSignalHandlerCreators($this->settingsProvider->getUserSettings()), - $this->extractSignalHandlerCreators($this->settingsProvider->getDetectedSettings()), - $this->extractSignalHandlerCreators($this->settingsProvider->getDefaultSettings()), - ); + return $this->doSolve( + $this->extractSignalHandlerCreators($this->settingsProvider->getUserSettings()), + $this->extractSignalHandlerCreators($this->settingsProvider->getDetectedSettings()), + $this->extractSignalHandlerCreators($this->settingsProvider->getDefaultSettings()), + ); } private function doSolve( @@ -33,20 +32,19 @@ private function doSolve( ?Traversable $detectedCreators, ?Traversable $defaultCreators, ): ISignalHandlersContainer { - return - new SignalHandlersContainer( - $this->mergeSignalHandlerCreators( - $userCreators, - $detectedCreators, - $defaultCreators - ) - ); + return new SignalHandlersContainer( + $this->mergeSignalHandlerCreators( + $userCreators, + $detectedCreators, + $defaultCreators + ) + ); } /** * @return Traversable * - * @throws InvalidArgumentException + * @throws InvalidArgument */ private function mergeSignalHandlerCreators( ?Traversable $userCreators, @@ -58,14 +56,13 @@ private function mergeSignalHandlerCreators( iterator_to_array($this->unwrap($detectedCreators ?? new ArrayObject([]))) + iterator_to_array($this->unwrap($defaultCreators ?? new ArrayObject([]))); - return - new ArrayObject($merged); + return new ArrayObject($merged); } /** * @return Traversable * - * @throws InvalidArgumentException + * @throws InvalidArgument */ private function unwrap(Traversable $creators): Traversable { @@ -79,7 +76,7 @@ private function unwrap(Traversable $creators): Traversable private static function assertCreator(mixed $creator): void { if (!($creator instanceof ISignalHandlerCreator)) { - throw new InvalidArgumentException( + throw new InvalidArgument( sprintf( 'Creator must be instance of "%s", "%s" given.', ISignalHandlerCreator::class, diff --git a/src/Spinner/Core/Config/Solver/SignalHandlingModeSolver.php b/src/Spinner/Core/Config/Solver/SignalHandlingModeSolver.php index 5e86d9b5..c4dfc950 100644 --- a/src/Spinner/Core/Config/Solver/SignalHandlingModeSolver.php +++ b/src/Spinner/Core/Config/Solver/SignalHandlingModeSolver.php @@ -9,18 +9,17 @@ use AlecRabbit\Spinner\Core\Config\Solver\A\ASolver; use AlecRabbit\Spinner\Core\Settings\Contract\ILoopSettings; use AlecRabbit\Spinner\Core\Settings\Contract\ISettings; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; final readonly class SignalHandlingModeSolver extends ASolver implements Contract\ISignalHandlingModeSolver { public function solve(): SignalHandlingMode { - return - $this->doSolve( - $this->extractOption($this->settingsProvider->getUserSettings()), - $this->extractOption($this->settingsProvider->getDetectedSettings()), - $this->extractOption($this->settingsProvider->getDefaultSettings()), - ); + return $this->doSolve( + $this->extractOption($this->settingsProvider->getUserSettings()), + $this->extractOption($this->settingsProvider->getDetectedSettings()), + $this->extractOption($this->settingsProvider->getDefaultSettings()), + ); } private function doSolve( @@ -29,171 +28,170 @@ private function doSolve( ?SignalHandlingOption $defaultOption ): SignalHandlingMode { $options = [$userOption, $detectedOption, $defaultOption]; - return - match ($options) { - [ - SignalHandlingOption::AUTO, - SignalHandlingOption::ENABLED, - SignalHandlingOption::DISABLED, - ], - [ - SignalHandlingOption::AUTO, - SignalHandlingOption::ENABLED, - SignalHandlingOption::ENABLED, - ], - [ - SignalHandlingOption::ENABLED, - null, - SignalHandlingOption::ENABLED, - ], - [ - SignalHandlingOption::AUTO, - SignalHandlingOption::AUTO, - SignalHandlingOption::ENABLED, - ], - [ - SignalHandlingOption::ENABLED, - SignalHandlingOption::ENABLED, - SignalHandlingOption::ENABLED, - ], - [ - SignalHandlingOption::ENABLED, - SignalHandlingOption::DISABLED, - SignalHandlingOption::DISABLED, - ], - [ - SignalHandlingOption::ENABLED, - null, - null, - ], - [ - SignalHandlingOption::AUTO, - SignalHandlingOption::ENABLED, - null, - ], - [ - SignalHandlingOption::AUTO, - null, - SignalHandlingOption::ENABLED, - ], - [ - null, - SignalHandlingOption::AUTO, - SignalHandlingOption::ENABLED, - ], - [ - null, - SignalHandlingOption::ENABLED, - null, - ], - [ - null, - SignalHandlingOption::ENABLED, - SignalHandlingOption::ENABLED, - ], - [ - null, - null, - SignalHandlingOption::ENABLED, - ] => SignalHandlingMode::ENABLED, - [ - SignalHandlingOption::AUTO, - SignalHandlingOption::DISABLED, - SignalHandlingOption::DISABLED, - ], - [ - SignalHandlingOption::DISABLED, - SignalHandlingOption::DISABLED, - SignalHandlingOption::DISABLED, - ], - [ - SignalHandlingOption::DISABLED, - SignalHandlingOption::ENABLED, - SignalHandlingOption::ENABLED, - ], - [ - SignalHandlingOption::DISABLED, - SignalHandlingOption::ENABLED, - SignalHandlingOption::DISABLED, - ], - [ - SignalHandlingOption::AUTO, - SignalHandlingOption::DISABLED, - null, - ], - [ - SignalHandlingOption::AUTO, - null, - SignalHandlingOption::DISABLED, - ], - [ - null, - SignalHandlingOption::AUTO, - SignalHandlingOption::DISABLED, - ], - [ - null, - SignalHandlingOption::DISABLED, - null, - ], - [ - null, - SignalHandlingOption::DISABLED, - SignalHandlingOption::DISABLED, - ], - [ - null, - SignalHandlingOption::ENABLED, - SignalHandlingOption::DISABLED, - ], - [ - null, - SignalHandlingOption::DISABLED, - SignalHandlingOption::ENABLED, - ], - [ - SignalHandlingOption::DISABLED, - SignalHandlingOption::DISABLED, - SignalHandlingOption::ENABLED, - ], - [ - SignalHandlingOption::AUTO, - SignalHandlingOption::DISABLED, - SignalHandlingOption::ENABLED, - ], - [ - SignalHandlingOption::ENABLED, - SignalHandlingOption::DISABLED, - SignalHandlingOption::ENABLED, - ], - [ - SignalHandlingOption::DISABLED, - null, - null, - ], - [ - SignalHandlingOption::DISABLED, - null, - SignalHandlingOption::ENABLED, - ], - [ - null, - null, - SignalHandlingOption::DISABLED, - ] => SignalHandlingMode::DISABLED, - default => throw new InvalidArgumentException( + return match ($options) { + [ + SignalHandlingOption::AUTO, + SignalHandlingOption::ENABLED, + SignalHandlingOption::DISABLED, + ], + [ + SignalHandlingOption::AUTO, + SignalHandlingOption::ENABLED, + SignalHandlingOption::ENABLED, + ], + [ + SignalHandlingOption::ENABLED, + null, + SignalHandlingOption::ENABLED, + ], + [ + SignalHandlingOption::AUTO, + SignalHandlingOption::AUTO, + SignalHandlingOption::ENABLED, + ], + [ + SignalHandlingOption::ENABLED, + SignalHandlingOption::ENABLED, + SignalHandlingOption::ENABLED, + ], + [ + SignalHandlingOption::ENABLED, + SignalHandlingOption::DISABLED, + SignalHandlingOption::DISABLED, + ], + [ + SignalHandlingOption::ENABLED, + null, + null, + ], + [ + SignalHandlingOption::AUTO, + SignalHandlingOption::ENABLED, + null, + ], + [ + SignalHandlingOption::AUTO, + null, + SignalHandlingOption::ENABLED, + ], + [ + null, + SignalHandlingOption::AUTO, + SignalHandlingOption::ENABLED, + ], + [ + null, + SignalHandlingOption::ENABLED, + null, + ], + [ + null, + SignalHandlingOption::ENABLED, + SignalHandlingOption::ENABLED, + ], + [ + null, + null, + SignalHandlingOption::ENABLED, + ] => SignalHandlingMode::ENABLED, + [ + SignalHandlingOption::AUTO, + SignalHandlingOption::DISABLED, + SignalHandlingOption::DISABLED, + ], + [ + SignalHandlingOption::DISABLED, + SignalHandlingOption::DISABLED, + SignalHandlingOption::DISABLED, + ], + [ + SignalHandlingOption::DISABLED, + SignalHandlingOption::ENABLED, + SignalHandlingOption::ENABLED, + ], + [ + SignalHandlingOption::DISABLED, + SignalHandlingOption::ENABLED, + SignalHandlingOption::DISABLED, + ], + [ + SignalHandlingOption::AUTO, + SignalHandlingOption::DISABLED, + null, + ], + [ + SignalHandlingOption::AUTO, + null, + SignalHandlingOption::DISABLED, + ], + [ + null, + SignalHandlingOption::AUTO, + SignalHandlingOption::DISABLED, + ], + [ + null, + SignalHandlingOption::DISABLED, + null, + ], + [ + null, + SignalHandlingOption::DISABLED, + SignalHandlingOption::DISABLED, + ], + [ + null, + SignalHandlingOption::ENABLED, + SignalHandlingOption::DISABLED, + ], + [ + null, + SignalHandlingOption::DISABLED, + SignalHandlingOption::ENABLED, + ], + [ + SignalHandlingOption::DISABLED, + SignalHandlingOption::DISABLED, + SignalHandlingOption::ENABLED, + ], + [ + SignalHandlingOption::AUTO, + SignalHandlingOption::DISABLED, + SignalHandlingOption::ENABLED, + ], + [ + SignalHandlingOption::ENABLED, + SignalHandlingOption::DISABLED, + SignalHandlingOption::ENABLED, + ], + [ + SignalHandlingOption::DISABLED, + null, + null, + ], + [ + SignalHandlingOption::DISABLED, + null, + SignalHandlingOption::ENABLED, + ], + [ + null, + null, + SignalHandlingOption::DISABLED, + ] => SignalHandlingMode::DISABLED, + default => throw new InvalidArgument( + sprintf( + 'Unable to solve "%s". From values %s.', + SignalHandlingMode::class, sprintf( - 'Unable to solve "%s". From values %s.', - SignalHandlingMode::class, - sprintf( - '[%s, %s, %s]', - $userOption?->name ?? 'null', - $detectedOption?->name ?? 'null', - $defaultOption?->name ?? 'null', - ), - ) + '[%s, %s, %s]', + $userOption?->name ?? 'null', + $detectedOption?->name ?? 'null', + $defaultOption?->name ?? 'null', + ), ) - }; + ) + }; } protected function extractOption(ISettings $settings): ?SignalHandlingOption diff --git a/src/Spinner/Core/Config/Solver/StreamSolver.php b/src/Spinner/Core/Config/Solver/StreamSolver.php index 7e0f29cc..9b022b8d 100644 --- a/src/Spinner/Core/Config/Solver/StreamSolver.php +++ b/src/Spinner/Core/Config/Solver/StreamSolver.php @@ -7,18 +7,17 @@ use AlecRabbit\Spinner\Core\Config\Solver\A\ASolver; use AlecRabbit\Spinner\Core\Settings\Contract\IOutputSettings; use AlecRabbit\Spinner\Core\Settings\Contract\ISettings; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; final readonly class StreamSolver extends ASolver implements Contract\IStreamSolver { public function solve(): mixed { - return - $this->doSolve( - $this->extractStream($this->settingsProvider->getUserSettings()), - $this->extractStream($this->settingsProvider->getDetectedSettings()), - $this->extractStream($this->settingsProvider->getDefaultSettings()), - ); + return $this->doSolve( + $this->extractStream($this->settingsProvider->getUserSettings()), + $this->extractStream($this->settingsProvider->getDetectedSettings()), + $this->extractStream($this->settingsProvider->getDefaultSettings()), + ); } private function doSolve( @@ -26,11 +25,10 @@ private function doSolve( mixed $detectedStream, mixed $defaultStream ): mixed { - return - $userStream + return $userStream ?? $detectedStream ?? $defaultStream - ?? throw new InvalidArgumentException( + ?? throw new InvalidArgument( 'Unable to solve "stream".' ); } diff --git a/src/Spinner/Core/Config/Solver/StylingMethodModeSolver.php b/src/Spinner/Core/Config/Solver/StylingMethodModeSolver.php index 30c0dc06..2aff2f34 100644 --- a/src/Spinner/Core/Config/Solver/StylingMethodModeSolver.php +++ b/src/Spinner/Core/Config/Solver/StylingMethodModeSolver.php @@ -10,18 +10,17 @@ use AlecRabbit\Spinner\Core\Config\Solver\Contract\IStylingMethodModeSolver; use AlecRabbit\Spinner\Core\Settings\Contract\IOutputSettings; use AlecRabbit\Spinner\Core\Settings\Contract\ISettings; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; final readonly class StylingMethodModeSolver extends ASolver implements IStylingMethodModeSolver { public function solve(): StylingMethodMode { - return - $this->doSolve( - $this->extractOption($this->settingsProvider->getUserSettings()), - $this->extractOption($this->settingsProvider->getDetectedSettings()), - $this->extractOption($this->settingsProvider->getDefaultSettings()), - ); + return $this->doSolve( + $this->extractOption($this->settingsProvider->getUserSettings()), + $this->extractOption($this->settingsProvider->getDetectedSettings()), + $this->extractOption($this->settingsProvider->getDefaultSettings()), + ); } private function doSolve( @@ -42,6 +41,10 @@ private function doSolve( $mode = $detectedMode ?? $defaultMode; } + /** + * @psalm-suppress TypeDoesNotContainNull + * @psalm-suppress RedundantCondition + */ if ($detectedMode !== null && ($mode?->value > $detectedMode?->value)) { $mode = $detectedMode; } @@ -50,7 +53,7 @@ private function doSolve( return $mode; } - throw new InvalidArgumentException( + throw new InvalidArgument( sprintf( 'Unable to solve "%s". From values %s.', StylingMethodMode::class, @@ -66,14 +69,13 @@ private function doSolve( private function createModeFromOption(?StylingMethodOption $option): ?StylingMethodMode { - return - match ($option) { - StylingMethodOption::NONE => StylingMethodMode::NONE, - StylingMethodOption::ANSI4 => StylingMethodMode::ANSI4, - StylingMethodOption::ANSI8 => StylingMethodMode::ANSI8, - StylingMethodOption::ANSI24 => StylingMethodMode::ANSI24, - default => null, - }; + return match ($option) { + StylingMethodOption::NONE => StylingMethodMode::NONE, + StylingMethodOption::ANSI4 => StylingMethodMode::ANSI4, + StylingMethodOption::ANSI8 => StylingMethodMode::ANSI8, + StylingMethodOption::ANSI24 => StylingMethodMode::ANSI24, + default => null, + }; } protected function extractOption(ISettings $settings): ?StylingMethodOption diff --git a/src/Spinner/Core/Config/Solver/ToleranceSolver.php b/src/Spinner/Core/Config/Solver/ToleranceSolver.php new file mode 100644 index 00000000..c3b8e4db --- /dev/null +++ b/src/Spinner/Core/Config/Solver/ToleranceSolver.php @@ -0,0 +1,48 @@ +doSolve( + $this->extractTolerance($this->settingsProvider->getUserSettings()), + $this->extractTolerance($this->settingsProvider->getDetectedSettings()), + $this->extractTolerance($this->settingsProvider->getDefaultSettings()), + ); + } + + private function doSolve( + ?ITolerance $userTolerance, + ?ITolerance $detectedTolerance, + ?ITolerance $defaultTolerance, + ): ITolerance { + $tolerance = + $userTolerance + ?? $detectedTolerance + ?? $defaultTolerance + ?? throw new LogicException( + sprintf('Unable to solve "%s".', ITolerance::class) + ); + + return new Tolerance( + value: $tolerance->toMilliseconds(), + ); + } + + private function extractTolerance(ISettings $settings): ?ITolerance + { + return $this->extractSettingsElement($settings, IRevolverSettings::class)?->getTolerance(); + } +} diff --git a/src/Spinner/Core/Config/Solver/WidgetSettingsSolver.php b/src/Spinner/Core/Config/Solver/WidgetSettingsSolver.php new file mode 100644 index 00000000..dd56ab8a --- /dev/null +++ b/src/Spinner/Core/Config/Solver/WidgetSettingsSolver.php @@ -0,0 +1,115 @@ +doSolve( + $this->extractSettings($this->settingsProvider->getUserSettings()), + $this->extractSettings($this->settingsProvider->getDetectedSettings()), + $this->extractSettings($this->settingsProvider->getDefaultSettings()), + ); + } + + /** + * @throws LogicException + */ + private function doSolve( + ?IWidgetSettings $userSettings, + ?IWidgetSettings $detectedSettings, + ?IWidgetSettings $defaultSettings, + ): IWidgetSettings { + $leadingSpacer = + $this->getLeadingSpacer($userSettings, $detectedSettings, $defaultSettings); + + $trailingSpacer = + $this->getTrailingSpacer($userSettings, $detectedSettings, $defaultSettings); + + $stylePalette = + $this->getStylePalette($userSettings, $detectedSettings, $defaultSettings); + + $charPalette = + $this->getCharPalette($userSettings, $detectedSettings, $defaultSettings); + + return new WidgetSettings( + leadingSpacer: $leadingSpacer, + trailingSpacer: $trailingSpacer, + stylePalette: $stylePalette, + charPalette: $charPalette, + ); + } + + private function getLeadingSpacer( + ?IWidgetSettings $userSettings, + ?IWidgetSettings $detectedSettings, + ?IWidgetSettings $defaultSettings + ): IFrame { + return $userSettings?->getLeadingSpacer() + ?? + $detectedSettings?->getLeadingSpacer() + ?? + $defaultSettings?->getLeadingSpacer() + ?? + throw new LogicException('Leading spacer expected to be set.'); + } + + private function getTrailingSpacer( + ?IWidgetSettings $userSettings, + ?IWidgetSettings $detectedSettings, + ?IWidgetSettings $defaultSettings + ): IFrame { + return $userSettings?->getTrailingSpacer() + ?? + $detectedSettings?->getTrailingSpacer() + ?? + $defaultSettings?->getTrailingSpacer() + ?? + throw new LogicException('Trailing spacer expected to be set.'); + } + + private function getStylePalette( + ?IWidgetSettings $userSettings, + ?IWidgetSettings $detectedSettings, + ?IWidgetSettings $defaultSettings + ): IPalette { + return $userSettings?->getStylePalette() + ?? + $detectedSettings?->getStylePalette() + ?? + $defaultSettings?->getStylePalette() + ?? + throw new LogicException('Style palette expected to be set.'); + } + + private function getCharPalette( + ?IWidgetSettings $userSettings, + ?IWidgetSettings $detectedSettings, + ?IWidgetSettings $defaultSettings + ): IPalette { + return $userSettings?->getCharPalette() + ?? + $detectedSettings?->getCharPalette() + ?? + $defaultSettings?->getCharPalette() + ?? + throw new LogicException('Char palette expected to be set.'); + } + + protected function extractSettings(ISettings $settings): ?IWidgetSettings + { + return $this->extractSettingsElement($settings, IWidgetSettings::class); + } +} diff --git a/src/Spinner/Core/Config/WidgetConfig.php b/src/Spinner/Core/Config/WidgetConfig.php index 92ac2780..b7b3a246 100644 --- a/src/Spinner/Core/Config/WidgetConfig.php +++ b/src/Spinner/Core/Config/WidgetConfig.php @@ -5,7 +5,6 @@ namespace AlecRabbit\Spinner\Core\Config; use AlecRabbit\Spinner\Contract\IFrame; -use AlecRabbit\Spinner\Contract\Mode\InitializationMode; use AlecRabbit\Spinner\Core\Config\Contract\IWidgetConfig; use AlecRabbit\Spinner\Core\Config\Contract\IWidgetRevolverConfig; diff --git a/src/Spinner/Core/Config/WidgetRevolverConfig.php b/src/Spinner/Core/Config/WidgetRevolverConfig.php index bfc7845a..98d3cb17 100644 --- a/src/Spinner/Core/Config/WidgetRevolverConfig.php +++ b/src/Spinner/Core/Config/WidgetRevolverConfig.php @@ -13,7 +13,7 @@ public function __construct( protected IPalette $stylePalette, protected IPalette $charPalette, - protected IRevolverConfig $revolverConfig = new RevolverConfig(), + protected IRevolverConfig $revolverConfig, ) { } diff --git a/src/Spinner/Core/ConfigProvider.php b/src/Spinner/Core/ConfigProvider.php deleted file mode 100644 index 787fdc84..00000000 --- a/src/Spinner/Core/ConfigProvider.php +++ /dev/null @@ -1,21 +0,0 @@ -config; - } -} diff --git a/src/Spinner/Core/Contract/IConfigProvider.php b/src/Spinner/Core/Contract/IConfigProvider.php deleted file mode 100644 index 0a37addd..00000000 --- a/src/Spinner/Core/Contract/IConfigProvider.php +++ /dev/null @@ -1,12 +0,0 @@ - + * @extends IteratorAggregate + */ interface IWeakMap extends ArrayAccess, Countable, IteratorAggregate { - } diff --git a/src/Spinner/Core/Contract/IWidgetContextToIntervalMap.php b/src/Spinner/Core/Contract/IWidgetContextToIntervalMap.php index 3ba7f3e0..55013997 100644 --- a/src/Spinner/Core/Contract/IWidgetContextToIntervalMap.php +++ b/src/Spinner/Core/Contract/IWidgetContextToIntervalMap.php @@ -5,11 +5,48 @@ namespace AlecRabbit\Spinner\Core\Contract; use AlecRabbit\Spinner\Contract\IInterval; +use AlecRabbit\Spinner\Core\Widget\Contract\IWidgetContext; use ArrayAccess; use Countable; use IteratorAggregate; +use Traversable; +/** + * @template TKey of IWIdgetContext + * @template TValue of IInterval|null + * + * @extends ArrayAccess + * @extends IteratorAggregate + */ interface IWidgetContextToIntervalMap extends ArrayAccess, Countable, IteratorAggregate { + /** + * @psalm-return Traversable + */ + public function getIterator(): Traversable; + + /** + * @psalm-param TKey $offset + * + * @psalm-return TValue + */ public function offsetGet(mixed $offset): ?IInterval; + + /** + * @psalm-param TKey $offset + */ + public function offsetUnset(mixed $offset): void; + + /** + * @psalm-param TKey $offset + * @psalm-param TValue $value + */ + public function offsetSet(mixed $offset, mixed $value): void; + + /** + * @psalm-param TKey $offset + * + * @psalm-return bool + */ + public function offsetExists(mixed $offset): bool; } diff --git a/src/Spinner/Core/DeltaTimer.php b/src/Spinner/Core/DeltaTimer.php new file mode 100644 index 00000000..ca83e34a --- /dev/null +++ b/src/Spinner/Core/DeltaTimer.php @@ -0,0 +1,27 @@ +current = $startTime; + } + + public function getDelta(): float + { + $last = $this->current; + $this->current = $this->nowTimer->now(); + return $this->current - $last; + } +} diff --git a/src/Spinner/Core/Driver.php b/src/Spinner/Core/Driver.php index 9f703928..9c5c04c4 100644 --- a/src/Spinner/Core/Driver.php +++ b/src/Spinner/Core/Driver.php @@ -4,25 +4,49 @@ namespace AlecRabbit\Spinner\Core; +use AlecRabbit\Spinner\Contract\IDeltaTimer; use AlecRabbit\Spinner\Contract\IInterval; +use AlecRabbit\Spinner\Contract\IObserver; use AlecRabbit\Spinner\Contract\ISubject; use AlecRabbit\Spinner\Core\A\ADriver; +use AlecRabbit\Spinner\Core\Contract\IDriverMessages; +use AlecRabbit\Spinner\Core\Contract\IIntervalComparator; +use AlecRabbit\Spinner\Core\Contract\IRenderer; use AlecRabbit\Spinner\Core\Contract\ISpinner; -use AlecRabbit\Spinner\Core\Contract\ISpinnerState; final class Driver extends ADriver { - protected ?ISpinner $spinner = null; - protected ISpinnerState $state; + private ?ISpinner $spinner = null; + + public function __construct( + IInterval $initialInterval, + IDriverMessages $driverMessages, + IRenderer $renderer, + private readonly IIntervalComparator $intervalComparator, + IDeltaTimer $deltaTimer, + ?IObserver $observer = null + ) { + parent::__construct( + initialInterval: $initialInterval, + driverMessages: $driverMessages, + renderer: $renderer, + deltaTimer: $deltaTimer, + observer: $observer, + ); + } - /** @inheritDoc */ public function add(ISpinner $spinner): void { $this->erase(); - $this->state = new SpinnerState(); + if ($this->spinner) { + $this->doRemove($this->spinner); + } $this->spinner = $spinner; + + $this->render(); + $spinner->attach($this); $this->update($spinner); } @@ -30,55 +54,51 @@ public function add(ISpinner $spinner): void protected function erase(): void { if ($this->spinner) { - $this->output->erase($this->state); + $this->renderer->erase($this->spinner); } } - public function update(ISubject $subject): void + protected function doRemove(ISpinner $spinner): void { - if ($this->spinner === $subject) { - $this->interval = $this->recalculateInterval(); - $this->notify(); - } + $spinner->detach($this); + $this->spinner = null; + $this->interval = $this->smallestInterval(); } - protected function recalculateInterval(): IInterval + protected function smallestInterval(): IInterval { - return $this->initialInterval->smallest($this->spinner?->getInterval()); + return $this->intervalComparator->smallest( + $this->initialInterval, + $this->spinner?->getInterval(), + ); } - /** @inheritDoc */ - public function has(ISpinner $spinner): bool + public function render(?float $dt = null): void { - return $this->spinner === $spinner; + if ($this->spinner) { + $this->renderer->render($this->spinner, $dt); + } + } + + public function update(ISubject $subject): void + { + if ($this->spinner === $subject) { + $this->interval = $this->smallestInterval(); + $this->notify(); + } } - /** @inheritDoc */ public function remove(ISpinner $spinner): void { if ($this->spinner === $spinner) { $this->erase(); - $spinner->detach($this); - $this->spinner = null; - $this->interval = $this->recalculateInterval(); + $this->doRemove($spinner); $this->notify(); } } - /** @inheritDoc */ - public function render(?float $dt = null): void + public function has(ISpinner $spinner): bool { - if ($this->spinner) { - $dt ??= $this->timer->getDelta(); - $frame = $this->spinner->getFrame($dt); - $this->state = - new SpinnerState( - sequence: $frame->sequence(), - width: $frame->width(), - previousWidth: $this->state->getWidth() - ); - - $this->output->write($this->state); - } + return $this->spinner === $spinner; } } diff --git a/src/Spinner/Core/Driver/Renderer.php b/src/Spinner/Core/Driver/Renderer.php new file mode 100644 index 00000000..bcc5ce17 --- /dev/null +++ b/src/Spinner/Core/Driver/Renderer.php @@ -0,0 +1,69 @@ +state = $this->createState(); + } + + private function createState( + string $sequence = '', + int $width = 0, + int $previousWidth = 0 + ): ISequenceState { + return $this->stateBuilder + ->withSequence($sequence) + ->withWidth($width) + ->withPreviousWidth($previousWidth) + ->build() + ; + } + + public function initialize(): void + { + $this->stateWriter->initialize(); + } + + public function render(ISpinner $spinner, ?float $dt = null): void + { + $frame = + $spinner->getFrame( + $dt ?? $this->deltaTimer->getDelta() + ); + + $this->state = $this->createState( + $frame->getSequence(), + $frame->getWidth(), + $this->state->getWidth(), + ); + + $this->stateWriter->write($this->state); + } + + public function finalize(?string $finalMessage = null): void + { + $this->stateWriter->finalize($finalMessage); + } + + public function erase(ISpinner $spinner): void + { + $this->stateWriter->erase($this->state); + } +} diff --git a/src/Spinner/Core/DriverLinker.php b/src/Spinner/Core/DriverLinker.php index 788fd1c7..a01999cf 100644 --- a/src/Spinner/Core/DriverLinker.php +++ b/src/Spinner/Core/DriverLinker.php @@ -8,11 +8,12 @@ use AlecRabbit\Spinner\Core\Contract\IDriver; use AlecRabbit\Spinner\Core\Contract\IDriverLinker; use AlecRabbit\Spinner\Core\Loop\Contract\ILoop; +use AlecRabbit\Spinner\Exception\DriverCanNotBeReplaced; use AlecRabbit\Spinner\Exception\LogicException; final class DriverLinker implements IDriverLinker { - private mixed $timer = null; + private mixed $renderTimer = null; private ?IDriver $driver = null; public function __construct( @@ -20,7 +21,6 @@ public function __construct( ) { } - /** @inheritDoc */ public function link(IDriver $driver): void { $this->assertDriverCanBeLinked($driver); @@ -28,7 +28,8 @@ public function link(IDriver $driver): void $this->linkTimer($driver); if ($this->driver === null) { - $this->observeDriver($driver); + $this->driver = $driver; + $driver->attach($this); } } @@ -40,38 +41,24 @@ private function assertDriverCanBeLinked(IDriver $driver): void if ($this->driver === null || $this->driver === $driver) { return; } - throw new LogicException( + throw new DriverCanNotBeReplaced( 'Other instance of driver is already linked.' ); } private function linkTimer(IDriver $driver): void { - $this->unlinkTimer(); - - $interval = $driver->getInterval()->toSeconds(); + if ($this->renderTimer) { + $this->loop->cancel($this->renderTimer); + } - $this->timer = + $this->renderTimer = $this->loop->repeat( - $interval, - static fn() => $driver->render() + $driver->getInterval()->toSeconds(), + static fn() => $driver->render(), ); } - private function unlinkTimer(): void - { - if ($this->timer) { - $this->loop->cancel($this->timer); - $this->timer = null; - } - } - - private function observeDriver(IDriver $driver): void - { - $this->driver = $driver; - $driver->attach($this); - } - public function update(ISubject $subject): void { if ($subject === $this->driver) { diff --git a/src/Spinner/Core/DriverMessages.php b/src/Spinner/Core/DriverMessages.php new file mode 100644 index 00000000..483d52fe --- /dev/null +++ b/src/Spinner/Core/DriverMessages.php @@ -0,0 +1,26 @@ +finalMessage; + } + + public function getInterruptionMessage(): string + { + return $this->interruptionMessage; + } +} diff --git a/src/Spinner/Core/DriverProvider.php b/src/Spinner/Core/DriverProvider.php index e042db30..84ec83f6 100644 --- a/src/Spinner/Core/DriverProvider.php +++ b/src/Spinner/Core/DriverProvider.php @@ -7,10 +7,10 @@ use AlecRabbit\Spinner\Core\Contract\IDriver; use AlecRabbit\Spinner\Core\Contract\IDriverProvider; -final class DriverProvider implements IDriverProvider +final readonly class DriverProvider implements IDriverProvider { public function __construct( - protected IDriver $driver, + private IDriver $driver, ) { } diff --git a/src/Spinner/Core/DriverSetup.php b/src/Spinner/Core/DriverSetup.php index ecaf86c0..5410c5c1 100644 --- a/src/Spinner/Core/DriverSetup.php +++ b/src/Spinner/Core/DriverSetup.php @@ -12,8 +12,8 @@ final readonly class DriverSetup implements IDriverSetup { public function __construct( - protected IDriverLinker $driverLinker, - protected ISignalHandlingSetup $signalHandlingSetup, + private IDriverLinker $driverLinker, + private ISignalHandlingSetup $signalHandlingSetup, ) { } diff --git a/src/Spinner/Core/Factory/A/ADriverProviderFactory.php b/src/Spinner/Core/Factory/A/ADriverProviderFactory.php index 536b323f..cc134c5a 100644 --- a/src/Spinner/Core/Factory/A/ADriverProviderFactory.php +++ b/src/Spinner/Core/Factory/A/ADriverProviderFactory.php @@ -29,7 +29,6 @@ public function create(): IDriverProvider $this->driverSetup->setup($driver); - return - $driverProvider; + return $driverProvider; } } diff --git a/src/Spinner/Core/Factory/CharFrameRevolverFactory.php b/src/Spinner/Core/Factory/CharFrameRevolverFactory.php index 319fa0b1..1981534e 100644 --- a/src/Spinner/Core/Factory/CharFrameRevolverFactory.php +++ b/src/Spinner/Core/Factory/CharFrameRevolverFactory.php @@ -5,45 +5,37 @@ namespace AlecRabbit\Spinner\Core\Factory; use AlecRabbit\Spinner\Contract\Pattern\IPattern; -use AlecRabbit\Spinner\Core\Contract\ITolerance; +use AlecRabbit\Spinner\Core\Config\Contract\IRevolverConfig; use AlecRabbit\Spinner\Core\Factory\Contract\ICharFrameRevolverFactory; use AlecRabbit\Spinner\Core\Factory\Contract\IFrameCollectionFactory; -use AlecRabbit\Spinner\Core\Factory\Contract\IIntervalFactory; use AlecRabbit\Spinner\Core\Revolver\Contract\IFrameRevolver; use AlecRabbit\Spinner\Core\Revolver\Contract\IFrameRevolverBuilder; -use AlecRabbit\Spinner\Core\Revolver\Tolerance; final class CharFrameRevolverFactory implements ICharFrameRevolverFactory { public function __construct( protected IFrameRevolverBuilder $frameRevolverBuilder, protected IFrameCollectionFactory $frameCollectionFactory, - protected IIntervalFactory $intervalFactory, + protected IRevolverConfig $revolverConfig, ) { } public function create(IPattern $pattern): IFrameRevolver { - return - $this->frameRevolverBuilder - ->withFrameCollection( - $this->frameCollectionFactory->create( + return $this->frameRevolverBuilder + ->withFrameCollection( + $this->frameCollectionFactory + ->create( $pattern->getFrames() ) - ) - ->withInterval( - $pattern->getInterval() - ) - ->withTolerance( - $this->getTolerance() - ) - ->build() + ) + ->withInterval( + $pattern->getInterval() + ) + ->withTolerance( + $this->revolverConfig->getTolerance() + ) + ->build() ; } - - private function getTolerance(): ITolerance - { - // TODO (2023-04-26 14:21) [Alec Rabbit]: make it configurable [fd86d318-9069-47e2-b60d-a68f537be4a3] - return new Tolerance(); - } } diff --git a/src/Spinner/Core/Factory/Contract/IDeltaTimerFactory.php b/src/Spinner/Core/Factory/Contract/IDeltaTimerFactory.php new file mode 100644 index 00000000..f5fd01ec --- /dev/null +++ b/src/Spinner/Core/Factory/Contract/IDeltaTimerFactory.php @@ -0,0 +1,12 @@ +timerBuilder + ->withStartTime($this->startTime) + ->withNowTimer($this->nowTimer) + ->build() + ; + } +} diff --git a/src/Spinner/Core/Factory/DriverFactory.php b/src/Spinner/Core/Factory/DriverFactory.php index ff757a03..00007d81 100644 --- a/src/Spinner/Core/Factory/DriverFactory.php +++ b/src/Spinner/Core/Factory/DriverFactory.php @@ -6,35 +6,38 @@ use AlecRabbit\Spinner\Core\Contract\IDriver; use AlecRabbit\Spinner\Core\Contract\IDriverBuilder; +use AlecRabbit\Spinner\Core\Contract\IDriverMessages; +use AlecRabbit\Spinner\Core\Contract\IIntervalComparator; +use AlecRabbit\Spinner\Core\Contract\IRenderer; +use AlecRabbit\Spinner\Core\Factory\Contract\IDeltaTimerFactory; use AlecRabbit\Spinner\Core\Factory\Contract\IDriverFactory; -use AlecRabbit\Spinner\Core\Factory\Contract\IDriverOutputFactory; use AlecRabbit\Spinner\Core\Factory\Contract\IIntervalFactory; -use AlecRabbit\Spinner\Core\Factory\Contract\ITimerFactory; -final class DriverFactory implements IDriverFactory +final readonly class DriverFactory implements IDriverFactory { public function __construct( - protected IDriverBuilder $driverBuilder, - protected IIntervalFactory $intervalFactory, - protected IDriverOutputFactory $driverOutputFactory, - protected ITimerFactory $timerFactory, + private IDriverMessages $driverMessages, + private IDriverBuilder $driverBuilder, + private IIntervalFactory $intervalFactory, + private IDeltaTimerFactory $timerFactory, + private IIntervalComparator $intervalComparator, + private IRenderer $renderer, ) { } public function create(): IDriver { - return - $this->driverBuilder - ->withDriverOutput( - $this->driverOutputFactory->create() - ) - ->withTimer( - $this->timerFactory->create() - ) - ->withInitialInterval( - $this->intervalFactory->createStill() - ) - ->build() + return $this->driverBuilder + ->withDeltaTimer( + $this->timerFactory->create() + ) + ->withInitialInterval( + $this->intervalFactory->createStill() + ) + ->withDriverMessages($this->driverMessages) + ->withIntervalComparator($this->intervalComparator) + ->withRenderer($this->renderer) + ->build() ; } } diff --git a/src/Spinner/Core/Factory/DriverLinkerFactory.php b/src/Spinner/Core/Factory/DriverLinkerFactory.php index 91c54aa7..7ab3d372 100644 --- a/src/Spinner/Core/Factory/DriverLinkerFactory.php +++ b/src/Spinner/Core/Factory/DriverLinkerFactory.php @@ -4,38 +4,34 @@ namespace AlecRabbit\Spinner\Core\Factory; -use AlecRabbit\Spinner\Contract\Mode\LinkerMode; -use AlecRabbit\Spinner\Core\Config\Contract\IDriverConfig; use AlecRabbit\Spinner\Core\Contract\IDriverLinker; use AlecRabbit\Spinner\Core\DriverLinker; use AlecRabbit\Spinner\Core\DummyDriverLinker; use AlecRabbit\Spinner\Core\Factory\Contract\IDriverLinkerFactory; +use AlecRabbit\Spinner\Core\Feature\Resolver\Contract\ILinkerResolver; use AlecRabbit\Spinner\Core\Loop\Contract\ILoopProvider; -final class DriverLinkerFactory implements IDriverLinkerFactory +final readonly class DriverLinkerFactory implements IDriverLinkerFactory { public function __construct( - protected ILoopProvider $loopProvider, - protected IDriverConfig $driverConfig, + private ILoopProvider $loopProvider, + private ILinkerResolver $linkerResolver, ) { } public function create(): IDriverLinker { - if ($this->loopProvider->hasLoop() && $this->isLinkerEnabled()) { - return - new DriverLinker( - $this->loopProvider->getLoop(), - ); + if ($this->isLinkerEnabled()) { + return new DriverLinker( + loop: $this->loopProvider->getLoop(), + ); } - return - new DummyDriverLinker(); + return new DummyDriverLinker(); } - protected function isLinkerEnabled(): bool + private function isLinkerEnabled(): bool { - return - $this->driverConfig->getLinkerMode() === LinkerMode::ENABLED; + return $this->loopProvider->hasLoop() && $this->linkerResolver->isEnabled(); } } diff --git a/src/Spinner/Core/Factory/DriverOutputFactory.php b/src/Spinner/Core/Factory/DriverOutputFactory.php deleted file mode 100644 index 1336c938..00000000 --- a/src/Spinner/Core/Factory/DriverOutputFactory.php +++ /dev/null @@ -1,36 +0,0 @@ -driverOutputBuilder - ->withOutput( - $this->bufferedOutput - ) - ->withCursor( - $this->cursorFactory->create() - ) - ->build() - ; - } -} diff --git a/src/Spinner/Core/Factory/IntervalFactory.php b/src/Spinner/Core/Factory/IntervalFactory.php index 21d4bf35..680e27b9 100644 --- a/src/Spinner/Core/Factory/IntervalFactory.php +++ b/src/Spinner/Core/Factory/IntervalFactory.php @@ -9,46 +9,40 @@ use AlecRabbit\Spinner\Core\Factory\Contract\IIntervalFactory; use AlecRabbit\Spinner\Core\Interval; -final class IntervalFactory implements IIntervalFactory +final readonly class IntervalFactory implements IIntervalFactory { private const DEFAULT_INTERVAL = 1000; - private static ?IInterval $normalizedDefaultInterval = null; - private static ?IInterval $normalizedStillInterval = null; + private IInterval $normalizedDefault; + private IInterval $normalizedStill; public function __construct( protected IIntervalNormalizer $intervalNormalizer, ) { + $this->normalizedDefault = + $this->intervalNormalizer->normalize( + new Interval(self::DEFAULT_INTERVAL), + ); + $this->normalizedStill = + $this->intervalNormalizer->normalize( + new Interval(), + ); } public function createNormalized(?int $interval): IInterval { - return - $interval === null - ? $this->createStill() - : $this->intervalNormalizer->normalize(new Interval($interval)); + return $interval === null + ? $this->createStill() + : $this->intervalNormalizer->normalize(new Interval($interval)); } public function createStill(): IInterval { - if (self::$normalizedStillInterval === null) { - self::$normalizedStillInterval = - $this->intervalNormalizer->normalize( - new Interval(), - ); - } - return self::$normalizedStillInterval; + return $this->normalizedStill; } public function createDefault(): IInterval { - if (self::$normalizedDefaultInterval === null) { - self::$normalizedDefaultInterval = - $this->intervalNormalizer->normalize( - new Interval(self::DEFAULT_INTERVAL), - ); - } - - return self::$normalizedDefaultInterval; + return $this->normalizedDefault; } } diff --git a/src/Spinner/Core/Factory/IntervalNormalizerFactory.php b/src/Spinner/Core/Factory/IntervalNormalizerFactory.php index 94474ab1..7b361d77 100644 --- a/src/Spinner/Core/Factory/IntervalNormalizerFactory.php +++ b/src/Spinner/Core/Factory/IntervalNormalizerFactory.php @@ -21,10 +21,9 @@ public function __construct( public function create(): IIntervalNormalizer { - return - new IntervalNormalizer( - $this->buildIntegerNormalizer(), - ); + return new IntervalNormalizer( + $this->buildIntegerNormalizer(), + ); } private function buildIntegerNormalizer(): IIntegerNormalizer @@ -40,13 +39,12 @@ private function buildIntegerNormalizer(): IIntegerNormalizer private function getDivisor(): int { - return - match ($this->normalizerMode) { - NormalizerMode::SMOOTH => 20, - NormalizerMode::BALANCED => 50, - NormalizerMode::PERFORMANCE => 100, - NormalizerMode::SLOW => 1000, - NormalizerMode::STILL => 900000, - }; + return match ($this->normalizerMode) { + NormalizerMode::SMOOTH => 40, + NormalizerMode::BALANCED => 100, + NormalizerMode::PERFORMANCE => 200, + NormalizerMode::SLOW => 1000, + NormalizerMode::STILL => 900000, + }; } } diff --git a/src/Spinner/Core/Factory/SequenceStateWriterFactory.php b/src/Spinner/Core/Factory/SequenceStateWriterFactory.php new file mode 100644 index 00000000..37ad4622 --- /dev/null +++ b/src/Spinner/Core/Factory/SequenceStateWriterFactory.php @@ -0,0 +1,39 @@ +sequenceStateWriterBuilder + ->withOutput( + $this->bufferedOutput + ) + ->withCursor( + $this->cursorFactory->create() + ) + ->withInitializationResolver( + $this->initializationResolver + ) + ->build() + ; + } +} diff --git a/src/Spinner/Core/Factory/SignalHandlingSetupFactory.php b/src/Spinner/Core/Factory/SignalHandlingSetupFactory.php index 5cb6affc..d40d3b29 100644 --- a/src/Spinner/Core/Factory/SignalHandlingSetupFactory.php +++ b/src/Spinner/Core/Factory/SignalHandlingSetupFactory.php @@ -22,20 +22,17 @@ public function __construct( public function create(): ISignalHandlingSetup { if ($this->loopProvider->hasLoop() && $this->isSignalHandlingEnabled()) { - return - new SignalHandlingSetup( - $this->loopProvider->getLoop(), - $this->loopConfig, - ); + return new SignalHandlingSetup( + $this->loopProvider->getLoop(), + $this->loopConfig, + ); } - return - new DummySignalHandlingSetup(); + return new DummySignalHandlingSetup(); } private function isSignalHandlingEnabled(): bool { - return - $this->loopConfig->getSignalHandlingMode() === SignalHandlingMode::ENABLED; + return $this->loopConfig->getSignalHandlingMode() === SignalHandlingMode::ENABLED; } } diff --git a/src/Spinner/Core/Factory/SpinnerFactory.php b/src/Spinner/Core/Factory/SpinnerFactory.php index 49e9153f..d4979dbb 100644 --- a/src/Spinner/Core/Factory/SpinnerFactory.php +++ b/src/Spinner/Core/Factory/SpinnerFactory.php @@ -4,7 +4,7 @@ namespace AlecRabbit\Spinner\Core\Factory; -use AlecRabbit\Spinner\Core\Config\Contract\IRootWidgetConfig; +use AlecRabbit\Spinner\Core\Config\Contract\Factory\IRootWidgetConfigFactory; use AlecRabbit\Spinner\Core\Contract\ISpinner; use AlecRabbit\Spinner\Core\Factory\Contract\ISpinnerFactory; use AlecRabbit\Spinner\Core\Settings\Contract\ISpinnerSettings; @@ -13,11 +13,11 @@ use AlecRabbit\Spinner\Core\Widget\Contract\IWidget; use AlecRabbit\Spinner\Core\Widget\Factory\Contract\IWidgetFactory; -final class SpinnerFactory implements ISpinnerFactory +final readonly class SpinnerFactory implements ISpinnerFactory { public function __construct( protected IWidgetFactory $widgetFactory, - protected IRootWidgetConfig $rootWidgetConfig, + protected IRootWidgetConfigFactory $widgetConfigFactory, ) { } @@ -28,16 +28,15 @@ public function create(?ISpinnerSettings $spinnerSettings = null): ISpinner $spinnerSettings?->getWidgetSettings() ); - return - new Spinner($widget); + return new Spinner( + widget: $widget + ); } - protected function createWidget(?IWidgetSettings $widgetSettings): IWidget + private function createWidget(?IWidgetSettings $widgetSettings): IWidget { - return - $this->widgetFactory->create( - $widgetSettings ?? $this->rootWidgetConfig - ); - } + $widgetConfig = $this->widgetConfigFactory->create($widgetSettings); + return $this->widgetFactory->create($widgetConfig); + } } diff --git a/src/Spinner/Core/Factory/StyleFrameRevolverFactory.php b/src/Spinner/Core/Factory/StyleFrameRevolverFactory.php index 279a39e5..1e4b0ffe 100644 --- a/src/Spinner/Core/Factory/StyleFrameRevolverFactory.php +++ b/src/Spinner/Core/Factory/StyleFrameRevolverFactory.php @@ -5,45 +5,42 @@ namespace AlecRabbit\Spinner\Core\Factory; use AlecRabbit\Spinner\Contract\Pattern\IPattern; +use AlecRabbit\Spinner\Core\Config\Contract\IRevolverConfig; use AlecRabbit\Spinner\Core\Contract\ITolerance; use AlecRabbit\Spinner\Core\Factory\Contract\IFrameCollectionFactory; -use AlecRabbit\Spinner\Core\Factory\Contract\IIntervalFactory; use AlecRabbit\Spinner\Core\Factory\Contract\IStyleFrameRevolverFactory; use AlecRabbit\Spinner\Core\Revolver\Contract\IFrameRevolver; use AlecRabbit\Spinner\Core\Revolver\Contract\IFrameRevolverBuilder; -use AlecRabbit\Spinner\Core\Revolver\Tolerance; final class StyleFrameRevolverFactory implements IStyleFrameRevolverFactory { public function __construct( protected IFrameRevolverBuilder $frameRevolverBuilder, protected IFrameCollectionFactory $frameCollectionFactory, - protected IIntervalFactory $intervalFactory, + protected IRevolverConfig $revolverConfig, ) { } public function create(IPattern $pattern): IFrameRevolver { - return - $this->frameRevolverBuilder - ->withFrameCollection( - $this->frameCollectionFactory->create( - $pattern->getFrames() - ) + return $this->frameRevolverBuilder + ->withFrameCollection( + $this->frameCollectionFactory->create( + $pattern->getFrames() ) - ->withInterval( - $pattern->getInterval() - ) - ->withTolerance( - $this->getTolerance() - ) - ->build() + ) + ->withInterval( + $pattern->getInterval() + ) + ->withTolerance( + $this->getTolerance() + ) + ->build() ; } private function getTolerance(): ITolerance { - // TODO (2023-04-26 14:21) [Alec Rabbit]: make it configurable [fd86d318-9069-47e2-b60d-a68f537be4a3] - return new Tolerance(); + return $this->revolverConfig->getTolerance(); } } diff --git a/src/Spinner/Core/Factory/TimerFactory.php b/src/Spinner/Core/Factory/TimerFactory.php deleted file mode 100644 index b958615a..00000000 --- a/src/Spinner/Core/Factory/TimerFactory.php +++ /dev/null @@ -1,29 +0,0 @@ -timerBuilder - ->withStartTime(0.0) - ->withTimeFunction( - static fn(): float => hrtime(true) * self::COEFFICIENT // returns milliseconds - ) - ->build() - ; - } -} diff --git a/src/Spinner/Core/Feature/Resolver/AutoStartResolver.php b/src/Spinner/Core/Feature/Resolver/AutoStartResolver.php new file mode 100644 index 00000000..11761fb3 --- /dev/null +++ b/src/Spinner/Core/Feature/Resolver/AutoStartResolver.php @@ -0,0 +1,23 @@ +driverModeDetector->isEnabled() && $this->autoStartModeDetector->isEnabled(); + } +} diff --git a/src/Spinner/Core/Feature/Resolver/Contract/IAutoStartResolver.php b/src/Spinner/Core/Feature/Resolver/Contract/IAutoStartResolver.php new file mode 100644 index 00000000..1ce48f14 --- /dev/null +++ b/src/Spinner/Core/Feature/Resolver/Contract/IAutoStartResolver.php @@ -0,0 +1,11 @@ +driverModeDetector->isEnabled() && $this->initializationModeDetector->isEnabled(); + } +} diff --git a/src/Spinner/Core/Feature/Resolver/LinkerResolver.php b/src/Spinner/Core/Feature/Resolver/LinkerResolver.php new file mode 100644 index 00000000..d5303abd --- /dev/null +++ b/src/Spinner/Core/Feature/Resolver/LinkerResolver.php @@ -0,0 +1,23 @@ +linkerModeDetector->isEnabled() && $this->driverModeDetector->isEnabled(); + } +} diff --git a/src/Spinner/Core/FrameCollection.php b/src/Spinner/Core/FrameCollection.php index c5250cec..a6c0ba05 100644 --- a/src/Spinner/Core/FrameCollection.php +++ b/src/Spinner/Core/FrameCollection.php @@ -6,7 +6,8 @@ use AlecRabbit\Spinner\Contract\IFrame; use AlecRabbit\Spinner\Core\Contract\IFrameCollection; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; +use AlecRabbit\Spinner\Exception\LogicException; use ArrayObject; use Traversable; @@ -19,8 +20,10 @@ */ final class FrameCollection extends ArrayObject implements IFrameCollection { + private const COLLECTION_IS_EMPTY = 'Collection is empty.'; + /** - * @throws InvalidArgumentException + * @throws InvalidArgument */ public function __construct(Traversable $frames) { @@ -30,7 +33,7 @@ public function __construct(Traversable $frames) } /** - * @throws InvalidArgumentException + * @throws InvalidArgument */ private function initialize(Traversable $frames): void { @@ -44,12 +47,12 @@ private function initialize(Traversable $frames): void } /** - * @throws InvalidArgumentException + * @throws InvalidArgument */ private static function assertFrame(mixed $frame): void { if (!$frame instanceof IFrame) { - throw new InvalidArgumentException( + throw new InvalidArgument( sprintf( '"%s" expected, "%s" given.', IFrame::class, @@ -62,13 +65,22 @@ private static function assertFrame(mixed $frame): void private static function assertIsNotEmpty(IFrameCollection $collection): void { if ($collection->count() === 0) { - throw new InvalidArgumentException('Collection is empty.'); + throw new InvalidArgument(self::COLLECTION_IS_EMPTY); } } public function lastIndex(): int { - return array_key_last($this->getArrayCopy()); + $index = array_key_last($this->getArrayCopy()); + + // @codeCoverageIgnoreStart + if ($index === null) { + // should not be thrown + throw new LogicException(self::COLLECTION_IS_EMPTY); + } + // @codeCoverageIgnoreEnd + + return $index; } public function get(int $index): IFrame diff --git a/src/Spinner/Core/IntegerNormalizer.php b/src/Spinner/Core/IntegerNormalizer.php index e93d5e2c..c9c0f29c 100644 --- a/src/Spinner/Core/IntegerNormalizer.php +++ b/src/Spinner/Core/IntegerNormalizer.php @@ -5,33 +5,33 @@ namespace AlecRabbit\Spinner\Core; use AlecRabbit\Spinner\Core\Contract\IIntegerNormalizer; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; -final class IntegerNormalizer implements IIntegerNormalizer +final readonly class IntegerNormalizer implements IIntegerNormalizer { private const DEFAULT_DIVISOR = 1; private const DEFAULT_MIN = 0; private const MAX_DIVISOR = 1000000; /** - * @throws InvalidArgumentException + * @throws InvalidArgument */ public function __construct( - protected int $divisor = self::DEFAULT_DIVISOR, - protected int $min = self::DEFAULT_MIN, + private int $divisor = self::DEFAULT_DIVISOR, + private int $min = self::DEFAULT_MIN, ) { self::assertDivisor($divisor); self::assertMin($min); } /** - * @throws InvalidArgumentException + * @throws InvalidArgument */ private static function assertDivisor(int $divisor): void { match (true) { - 0 >= $divisor => throw new InvalidArgumentException('Divisor should be greater than 0.'), - $divisor > self::MAX_DIVISOR => throw new InvalidArgumentException( + 0 >= $divisor => throw new InvalidArgument('Divisor should be greater than 0.'), + $divisor > self::MAX_DIVISOR => throw new InvalidArgument( sprintf('Divisor should be less than %s.', self::MAX_DIVISOR) ), default => null, @@ -39,12 +39,12 @@ private static function assertDivisor(int $divisor): void } /** - * @throws InvalidArgumentException + * @throws InvalidArgument */ private static function assertMin(int $min): void { if (0 > $min) { - throw new InvalidArgumentException('Min should be greater than 0.'); + throw new InvalidArgument('Min should be greater than 0.'); } } diff --git a/src/Spinner/Core/Interval.php b/src/Spinner/Core/Interval.php index a220f5a9..5c17c6c5 100644 --- a/src/Spinner/Core/Interval.php +++ b/src/Spinner/Core/Interval.php @@ -5,14 +5,14 @@ namespace AlecRabbit\Spinner\Core; use AlecRabbit\Spinner\Contract\IInterval; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; -final class Interval implements IInterval +final readonly class Interval implements IInterval { private float $milliseconds; /** - * @throws InvalidArgumentException + * @throws InvalidArgument */ public function __construct(?float $milliseconds = null) { @@ -31,18 +31,18 @@ private static function max(): int|float } /** - * @throws InvalidArgumentException + * @throws InvalidArgument */ private static function assert(self $interval): void { match (true) { - $interval->milliseconds < self::min() => throw new InvalidArgumentException( + $interval->milliseconds < self::min() => throw new InvalidArgument( sprintf( 'Interval should be greater than or equal to %s.', self::min() ) ), - $interval->milliseconds > self::max() => throw new InvalidArgumentException( + $interval->milliseconds > self::max() => throw new InvalidArgument( sprintf( 'Interval should be less than or equal to %s.', self::max() @@ -67,14 +67,6 @@ public function toMicroseconds(): float return $this->milliseconds * 1000; } - public function smallest(mixed $other): IInterval - { - if ($other instanceof IInterval && $this->milliseconds > $other->toMilliseconds()) { - return $other; - } - return $this; - } - public function toMilliseconds(): float { return $this->milliseconds; diff --git a/src/Spinner/Core/IntervalComparator.php b/src/Spinner/Core/IntervalComparator.php new file mode 100644 index 00000000..358d8bf9 --- /dev/null +++ b/src/Spinner/Core/IntervalComparator.php @@ -0,0 +1,37 @@ +toMicroseconds() < $smallest->toMicroseconds()) { + $smallest = $interval; + } + } + + return $smallest; + } + + public function largest(IInterval $first, ?IInterval ...$other): IInterval + { + $largest = $first; + + foreach ($other as $interval) { + if ($interval instanceof IInterval && $interval->toMicroseconds() > $largest->toMicroseconds()) { + $largest = $interval; + } + } + + return $largest; + } +} diff --git a/src/Spinner/Core/IntervalNormalizer.php b/src/Spinner/Core/IntervalNormalizer.php index 5dfb8e6a..160cd8a0 100644 --- a/src/Spinner/Core/IntervalNormalizer.php +++ b/src/Spinner/Core/IntervalNormalizer.php @@ -8,10 +8,10 @@ use AlecRabbit\Spinner\Core\Contract\IIntegerNormalizer; use AlecRabbit\Spinner\Core\Contract\IIntervalNormalizer; -final class IntervalNormalizer implements IIntervalNormalizer +final readonly class IntervalNormalizer implements IIntervalNormalizer { public function __construct( - protected IIntegerNormalizer $integerNormalizer, + private IIntegerNormalizer $integerNormalizer, ) { } diff --git a/src/Spinner/Core/Loop/Contract/A/ALoopProbe.php b/src/Spinner/Core/Loop/Contract/A/ALoopProbe.php index b95a9c1a..9d8a90de 100644 --- a/src/Spinner/Core/Loop/Contract/A/ALoopProbe.php +++ b/src/Spinner/Core/Loop/Contract/A/ALoopProbe.php @@ -7,6 +7,9 @@ use AlecRabbit\Spinner\Core\Loop\Contract\ILoopCreator; use AlecRabbit\Spinner\Core\Loop\Contract\ILoopProbe; +/** + * @codeCoverageIgnore + */ abstract class ALoopProbe implements ILoopProbe { abstract public static function isSupported(): bool; diff --git a/src/Spinner/Core/Loop/Contract/ILoopCreatorClassExtractor.php b/src/Spinner/Core/Loop/Contract/ILoopCreatorClassExtractor.php index 8b5e5600..7c733ce9 100644 --- a/src/Spinner/Core/Loop/Contract/ILoopCreatorClassExtractor.php +++ b/src/Spinner/Core/Loop/Contract/ILoopCreatorClassExtractor.php @@ -4,14 +4,16 @@ namespace AlecRabbit\Spinner\Core\Loop\Contract; +use AlecRabbit\Spinner\Contract\ICreator; +use AlecRabbit\Spinner\Contract\Probe\IStaticProbe; use Traversable; interface ILoopCreatorClassExtractor { /** - * @psalm-param Traversable $probes + * @psalm-param Traversable> $probes * - * @psalm-return class-string|null + * @psalm-return class-string|null */ public function extract(Traversable $probes): ?string; } diff --git a/src/Spinner/Core/Loop/Factory/LoopFactory.php b/src/Spinner/Core/Loop/Factory/LoopFactory.php index d556d4ed..14d7c3de 100644 --- a/src/Spinner/Core/Loop/Factory/LoopFactory.php +++ b/src/Spinner/Core/Loop/Factory/LoopFactory.php @@ -10,6 +10,8 @@ use AlecRabbit\Spinner\Core\Loop\Contract\ILoopCreatorClassProvider; use AlecRabbit\Spinner\Exception\LoopException; +use function is_subclass_of; + final readonly class LoopFactory implements ILoopFactory { public function __construct( @@ -19,28 +21,29 @@ public function __construct( public function create(): ILoop { + /** @var class-string $class */ $class = $this->classProvider->getCreatorClass(); self::assertClass($class); - /** @var class-string $class */ - return (new $class)->create(); + return (new $class())->create(); } /** - * @param null|class-string $loopCreator + * @param class-string|null $loopCreator + * * @throws LoopException */ private static function assertClass(?string $loopCreator): void { - if (null === $loopCreator) { + if ($loopCreator === null) { throw new LoopException('Loop creator class is not provided.'); } if (is_subclass_of($loopCreator, ILoopCreator::class) === false) { throw new LoopException( sprintf( - 'Class "%s" must implement "%s" interface.', + 'Class "%s" must be a subclass of "%s" interface.', $loopCreator, ILoopCreator::class ), diff --git a/src/Spinner/Core/Loop/Factory/LoopProviderFactory.php b/src/Spinner/Core/Loop/Factory/LoopProviderFactory.php index 9d320c47..e0b2b55d 100644 --- a/src/Spinner/Core/Loop/Factory/LoopProviderFactory.php +++ b/src/Spinner/Core/Loop/Factory/LoopProviderFactory.php @@ -24,10 +24,9 @@ public function __construct( public function create(): ILoopProvider { - return - new LoopProvider( - loop: $this->createLoop(), - ); + return new LoopProvider( + loop: $this->createLoop(), + ); } private function createLoop(): ?ILoop @@ -45,5 +44,4 @@ private function createLoop(): ?ILoop return null; } } - } diff --git a/src/Spinner/Core/Loop/LoopCreatorClassExtractor.php b/src/Spinner/Core/Loop/LoopCreatorClassExtractor.php index d88fb8d2..043b4ec1 100644 --- a/src/Spinner/Core/Loop/LoopCreatorClassExtractor.php +++ b/src/Spinner/Core/Loop/LoopCreatorClassExtractor.php @@ -4,16 +4,17 @@ namespace AlecRabbit\Spinner\Core\Loop; +use AlecRabbit\Spinner\Contract\Probe\IStaticProbe; use AlecRabbit\Spinner\Core\Loop\Contract\ILoopCreatorClassExtractor; use AlecRabbit\Spinner\Core\Loop\Contract\ILoopProbe; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; use Traversable; final class LoopCreatorClassExtractor implements ILoopCreatorClassExtractor { - /** @inheritDoc */ public function extract(Traversable $probes): ?string { + /** @var IStaticProbe $probe */ foreach ($probes as $probe) { self::assertProbe($probe); if ($probe::isSupported()) { @@ -23,10 +24,10 @@ public function extract(Traversable $probes): ?string return null; } - protected static function assertProbe(mixed $probe): void + private static function assertProbe(mixed $probe): void { if (!is_a($probe, ILoopProbe::class, true)) { - throw new InvalidArgumentException( + throw new InvalidArgument( sprintf( 'Probe must be an instance of "%s" interface.', ILoopProbe::class diff --git a/src/Spinner/Core/Loop/LoopCreatorClassProvider.php b/src/Spinner/Core/Loop/LoopCreatorClassProvider.php index 43e20756..026b77ed 100644 --- a/src/Spinner/Core/Loop/LoopCreatorClassProvider.php +++ b/src/Spinner/Core/Loop/LoopCreatorClassProvider.php @@ -4,22 +4,28 @@ namespace AlecRabbit\Spinner\Core\Loop; +use AlecRabbit\Spinner\Contract\ICreator; use AlecRabbit\Spinner\Core\Loop\Contract\ILoopCreator; use AlecRabbit\Spinner\Core\Loop\Contract\ILoopCreatorClassProvider; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Exception\InvalidArgument; + +use function is_a; +use function sprintf; final class LoopCreatorClassProvider implements ILoopCreatorClassProvider { /** @var class-string|null */ - protected ?string $creatorClass = null; + private ?string $creatorClass = null; /** - * @param class-string|null $creatorClass - * @throws InvalidArgumentException + * @param class-string|null $creatorClass + * + * @throws InvalidArgument */ public function __construct(?string $creatorClass) { self::assertClass($creatorClass); + /** @var class-string|null $creatorClass */ $this->creatorClass = $creatorClass; } @@ -29,9 +35,9 @@ private static function assertClass(?string $creatorClass): void return; } if (!is_a($creatorClass, ILoopCreator::class, true)) { - throw new InvalidArgumentException( + throw new InvalidArgument( sprintf( - 'Creator class must be an instance of "%s" interface.', + 'Creator class must be of "%s" interface.', ILoopCreator::class ) ); diff --git a/src/Spinner/Core/Loop/LoopProvider.php b/src/Spinner/Core/Loop/LoopProvider.php index 825cdeb7..571cdda8 100644 --- a/src/Spinner/Core/Loop/LoopProvider.php +++ b/src/Spinner/Core/Loop/LoopProvider.php @@ -20,10 +20,8 @@ public function hasLoop(): bool return $this->loop instanceof ILoop; } - /** @inheritDoc */ public function getLoop(): ILoop { - return - $this->loop ?? throw new DomainException('Loop is not set.'); + return $this->loop ?? throw new DomainException('Loop is not set.'); } } diff --git a/src/Spinner/Core/Loop/LoopSetup.php b/src/Spinner/Core/Loop/LoopSetup.php index 19a4f983..1d007088 100644 --- a/src/Spinner/Core/Loop/LoopSetup.php +++ b/src/Spinner/Core/Loop/LoopSetup.php @@ -4,21 +4,20 @@ namespace AlecRabbit\Spinner\Core\Loop; -use AlecRabbit\Spinner\Contract\Mode\AutoStartMode; -use AlecRabbit\Spinner\Core\Config\Contract\ILoopConfig; +use AlecRabbit\Spinner\Core\Feature\Resolver\Contract\IAutoStartResolver; use AlecRabbit\Spinner\Core\Loop\Contract\ILoop; use AlecRabbit\Spinner\Core\Loop\Contract\ILoopSetup; -final class LoopSetup implements ILoopSetup +final readonly class LoopSetup implements ILoopSetup { public function __construct( - protected ILoopConfig $loopConfig, + private IAutoStartResolver $autoStartResolver, ) { } public function setup(ILoop $loop): void { - if ($this->loopConfig->getAutoStartMode() === AutoStartMode::ENABLED) { + if ($this->autoStartResolver->isEnabled()) { $loop->autoStart(); } } diff --git a/src/Spinner/Core/Output/A/AOutput.php b/src/Spinner/Core/Output/A/AOutput.php deleted file mode 100644 index c9e48c3d..00000000 --- a/src/Spinner/Core/Output/A/AOutput.php +++ /dev/null @@ -1,57 +0,0 @@ -write($messages, true, $options); - } - - public function write(iterable|string $messages, bool $newline = false, int $options = 0): void - { - $this->doWrite($this->homogenize($messages, $newline)); - } - - /** - * @param Traversable $data - */ - protected function doWrite(Traversable $data): void - { - $this->stream->write($data); - } - - /** - * @codeCoverageIgnore Generator is not iterated through during tests. - * - * @return Generator - */ - protected function homogenize(iterable|string $messages, bool $newline = false): Generator - { - if (!is_iterable($messages)) { - $messages = [$messages]; - } - /** @var mixed $message */ - foreach ($messages as $message) { - if (is_string($message)) { - if ($newline) { - $message .= PHP_EOL; - } - yield $message; - } - } - } -} diff --git a/src/Spinner/Core/Output/BufferedOutput.php b/src/Spinner/Core/Output/BufferedOutput.php index d514a5ff..08287acc 100644 --- a/src/Spinner/Core/Output/BufferedOutput.php +++ b/src/Spinner/Core/Output/BufferedOutput.php @@ -21,7 +21,6 @@ public function flush(): void $this->output->write($this->buffer->flush()); } - /** @inheritDoc */ public function append(iterable|string $messages): IBufferedOutput { $this->buffer->append($messages); diff --git a/src/Spinner/Core/Output/ConsoleCursor.php b/src/Spinner/Core/Output/ConsoleCursor.php index a13eebac..08a9861e 100644 --- a/src/Spinner/Core/Output/ConsoleCursor.php +++ b/src/Spinner/Core/Output/ConsoleCursor.php @@ -5,9 +5,8 @@ namespace AlecRabbit\Spinner\Core\Output; use AlecRabbit\Spinner\Contract\Mode\CursorVisibilityMode; -use AlecRabbit\Spinner\Contract\Output\IBufferedOutput; -use AlecRabbit\Spinner\Core\Output\Contract\IConsoleCursor; use AlecRabbit\Spinner\Core\Output\Contract\IBuffer; +use AlecRabbit\Spinner\Core\Output\Contract\IConsoleCursor; final readonly class ConsoleCursor implements IConsoleCursor { diff --git a/src/Spinner/Core/Output/Contract/Factory/IResourceStreamFactory.php b/src/Spinner/Core/Output/Contract/Factory/IResourceStreamFactory.php index a36ba931..2ea28f6b 100644 --- a/src/Spinner/Core/Output/Contract/Factory/IResourceStreamFactory.php +++ b/src/Spinner/Core/Output/Contract/Factory/IResourceStreamFactory.php @@ -4,9 +4,9 @@ namespace AlecRabbit\Spinner\Core\Output\Contract\Factory; -use AlecRabbit\Spinner\Contract\Output\IResourceStream; +use AlecRabbit\Spinner\Contract\Output\IWritableStream; interface IResourceStreamFactory { - public function create(): IResourceStream; + public function create(): IWritableStream; } diff --git a/src/Spinner/Core/Output/Contract/IDriverOutput.php b/src/Spinner/Core/Output/Contract/IDriverOutput.php deleted file mode 100644 index 92df06bf..00000000 --- a/src/Spinner/Core/Output/Contract/IDriverOutput.php +++ /dev/null @@ -1,18 +0,0 @@ -outputConfig->getStream() - ); + return new WritableStream( + $this->outputConfig->getStream() + ); } } diff --git a/src/Spinner/Core/Output/Output.php b/src/Spinner/Core/Output/Output.php index 1bbc51e0..f7d5dd1f 100644 --- a/src/Spinner/Core/Output/Output.php +++ b/src/Spinner/Core/Output/Output.php @@ -5,14 +5,13 @@ namespace AlecRabbit\Spinner\Core\Output; use AlecRabbit\Spinner\Contract\Output\IOutput; -use AlecRabbit\Spinner\Contract\Output\IResourceStream; -use Generator; +use AlecRabbit\Spinner\Contract\Output\IWritableStream; use Traversable; final readonly class Output implements IOutput { public function __construct( - protected IResourceStream $stream, + protected IWritableStream $stream, ) { } @@ -31,12 +30,13 @@ public function write(iterable|string $messages, bool $newline = false, int $opt /** * @return Traversable */ - protected function homogenize(iterable|string $messages, bool $newline = false): Traversable + private function homogenize(iterable|string $messages, bool $newline = false): Traversable { if (!is_iterable($messages)) { $messages = [$messages]; } - /** @var mixed $message */ + + /** @psalm-suppress MixedAssignment */ foreach ($messages as $message) { if (is_string($message)) { if ($newline) { diff --git a/src/Spinner/Core/Output/DriverOutput.php b/src/Spinner/Core/Output/SequenceStateWriter.php similarity index 52% rename from src/Spinner/Core/Output/DriverOutput.php rename to src/Spinner/Core/Output/SequenceStateWriter.php index 4ed75fca..316090fc 100644 --- a/src/Spinner/Core/Output/DriverOutput.php +++ b/src/Spinner/Core/Output/SequenceStateWriter.php @@ -5,39 +5,43 @@ namespace AlecRabbit\Spinner\Core\Output; use AlecRabbit\Spinner\Contract\Output\IBufferedOutput; -use AlecRabbit\Spinner\Core\Contract\ISpinnerState; +use AlecRabbit\Spinner\Core\Contract\ISequenceState; +use AlecRabbit\Spinner\Core\Feature\Resolver\Contract\IInitializationResolver; use AlecRabbit\Spinner\Core\Output\Contract\IConsoleCursor; -use AlecRabbit\Spinner\Core\Output\Contract\IDriverOutput; +use AlecRabbit\Spinner\Core\Output\Contract\ISequenceStateWriter; -final class DriverOutput implements IDriverOutput +final class SequenceStateWriter implements ISequenceStateWriter { private bool $initialized = false; public function __construct( - protected readonly IBufferedOutput $output, - protected readonly IConsoleCursor $cursor, + private readonly IBufferedOutput $output, + private readonly IConsoleCursor $cursor, + private readonly IInitializationResolver $initializationResolver, ) { } public function finalize(?string $finalMessage = null): void { if ($this->initialized) { - $this->cursor->show(); $finalMessage && $this->output->append($finalMessage); + $this->cursor->show(); + $this->output->flush(); + $this->initialized = false; } } - public function write(ISpinnerState $spinnerState): void + public function write(ISequenceState $state): void { if ($this->initialized) { - $this->output->append($spinnerState->getSequence()); + $this->output->append($state->getSequence()); - $width = $spinnerState->getWidth(); + $width = $state->getWidth(); $eraseWidth = - max($spinnerState->getPreviousWidth() - $width, 0); + max($state->getPreviousWidth() - $width, 0); $this->cursor ->erase($eraseWidth) @@ -48,11 +52,11 @@ public function write(ISpinnerState $spinnerState): void } } - public function erase(ISpinnerState $spinnerState): void + public function erase(ISequenceState $state): void { if ($this->initialized) { $this->cursor->erase( - $spinnerState->getPreviousWidth() + $state->getPreviousWidth() ); $this->output->flush(); @@ -61,10 +65,12 @@ public function erase(ISpinnerState $spinnerState): void public function initialize(): void { - $this->initialized = true; + if ($this->initializationResolver->isEnabled()) { + $this->initialized = true; - $this->cursor->hide(); + $this->cursor->hide(); - $this->output->flush(); + $this->output->flush(); + } } } diff --git a/src/Spinner/Core/Output/ResourceStream.php b/src/Spinner/Core/Output/WritableStream.php similarity index 74% rename from src/Spinner/Core/Output/ResourceStream.php rename to src/Spinner/Core/Output/WritableStream.php index 9987b695..8f43ff41 100644 --- a/src/Spinner/Core/Output/ResourceStream.php +++ b/src/Spinner/Core/Output/WritableStream.php @@ -4,12 +4,12 @@ namespace AlecRabbit\Spinner\Core\Output; -use AlecRabbit\Spinner\Contract\Output\IResourceStream; -use AlecRabbit\Spinner\Exception\InvalidArgumentException; +use AlecRabbit\Spinner\Contract\Output\IWritableStream; +use AlecRabbit\Spinner\Exception\InvalidArgument; use AlecRabbit\Spinner\Exception\RuntimeException; use Traversable; -final class ResourceStream implements IResourceStream +final class WritableStream implements IWritableStream { /** * @var resource @@ -17,14 +17,12 @@ final class ResourceStream implements IResourceStream private $stream; /** - * @param mixed $stream - * - * @throws InvalidArgumentException + * @throws InvalidArgument */ public function __construct(mixed $stream) { if (!is_resource($stream) || get_resource_type($stream) !== 'stream') { - throw new InvalidArgumentException( + throw new InvalidArgument( sprintf( 'Argument is expected to be a stream(resource), "%s" given.', get_debug_type($stream) @@ -37,6 +35,8 @@ public function __construct(mixed $stream) /** * @codeCoverageIgnore + * + * @inheritDoc */ public function write(Traversable $data): void { @@ -46,6 +46,6 @@ public function write(Traversable $data): void throw new RuntimeException('Was unable to write to a stream.'); } } - fflush($this->stream); + // fflush($this->stream); } } diff --git a/src/Spinner/Core/Palette/A/ACharPalette.php b/src/Spinner/Core/Palette/A/ACharPalette.php new file mode 100644 index 00000000..609020c5 --- /dev/null +++ b/src/Spinner/Core/Palette/A/ACharPalette.php @@ -0,0 +1,49 @@ + + */ + protected function getEntries(?IPaletteMode $mode = null): Traversable + { + /** @var string $element */ + foreach ($this->sequence() as $element) { + yield $this->createFrame($element); + } + } + + /** + * @return Traversable + */ + abstract protected function sequence(): Traversable; + + abstract protected function createFrame(string $element): ICharFrame; + + protected function getOptions(?IPaletteMode $mode = null): IPaletteOptions + { + $this->options = + new PaletteOptions( + interval: $this->options->getInterval() ?? $this->getInterval(), + reversed: $this->options->getReversed(), + ); + + return parent::getOptions($mode); + } + + protected function getInterval(): ?int + { + return 100; + } +} diff --git a/src/Spinner/Core/Palette/A/APalette.php b/src/Spinner/Core/Palette/A/APalette.php index 8a700d2d..35d52b4a 100644 --- a/src/Spinner/Core/Palette/A/APalette.php +++ b/src/Spinner/Core/Palette/A/APalette.php @@ -4,6 +4,7 @@ namespace AlecRabbit\Spinner\Core\Palette\A; +use AlecRabbit\Spinner\Contract\IFrame; use AlecRabbit\Spinner\Core\Palette\Contract\IPalette; use AlecRabbit\Spinner\Core\Palette\Contract\IPaletteMode; use AlecRabbit\Spinner\Core\Palette\Contract\IPaletteOptions; @@ -21,19 +22,19 @@ public function __construct( public function getTemplate(?IPaletteMode $mode = null): IPaletteTemplate { - $options = $this->getOptions($mode); - - return - new PaletteTemplate( - $this->getEntries($mode), - $options, - ); + return new PaletteTemplate( + $this->getEntries($mode), + $this->getOptions($mode), + ); } + /** + * @return Traversable