Full Code of crunzphp/crunz for AI

3.10 98dc47f7997a cached
174 files
386.9 KB
99.8k tokens
758 symbols
1 requests
Download .txt
Showing preview only (431K chars total). Download the full file or copy to clipboard to get everything.
Repository: crunzphp/crunz
Branch: 3.10
Commit: 98dc47f7997a
Files: 174
Total size: 386.9 KB

Directory structure:
gitextract_lvj5ppvr/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── 1_Bug_report.md
│   │   └── 2_Feature_request.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       └── php.yaml
├── .gitignore
├── .php-cs-fixer.dist.php
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── UPGRADE.md
├── bootstrap.php
├── composer.json
├── config/
│   └── services.php
├── crunz
├── docker/
│   └── php82/
│       ├── Dockerfile
│       └── php.ini
├── docker-compose.yml
├── phpstan-baseline.neon
├── phpstan.neon
├── phpunit.xml
├── resources/
│   └── config/
│       └── crunz.yml
├── src/
│   ├── Application/
│   │   ├── Cron/
│   │   │   ├── CronExpressionFactoryInterface.php
│   │   │   └── CronExpressionInterface.php
│   │   ├── Query/
│   │   │   └── TaskInformation/
│   │   │       ├── TaskInformation.php
│   │   │       ├── TaskInformationHandler.php
│   │   │       └── TaskInformationView.php
│   │   └── Service/
│   │       ├── ClosureSerializerInterface.php
│   │       ├── ConfigurationInterface.php
│   │       └── LoggerFactoryInterface.php
│   ├── Application.php
│   ├── CacheDirectoryFactory/
│   │   └── CacheDirectoryFactory.php
│   ├── Clock/
│   │   ├── Clock.php
│   │   └── ClockInterface.php
│   ├── Configuration/
│   │   ├── ConfigFileNotExistsException.php
│   │   ├── ConfigFileNotReadableException.php
│   │   ├── Configuration.php
│   │   ├── ConfigurationParser.php
│   │   ├── ConfigurationParserInterface.php
│   │   ├── Definition.php
│   │   └── FileParser.php
│   ├── Console/
│   │   └── Command/
│   │       ├── Command.php
│   │       ├── ConfigGeneratorCommand.php
│   │       ├── ScheduleListCommand.php
│   │       ├── ScheduleRunCommand.php
│   │       └── TaskGeneratorCommand.php
│   ├── EnvFlags/
│   │   └── EnvFlags.php
│   ├── Event.php
│   ├── EventRunner.php
│   ├── Exception/
│   │   ├── CrunzException.php
│   │   ├── EmptyTimezoneException.php
│   │   ├── MailerException.php
│   │   ├── NotImplementedException.php
│   │   ├── TaskNotExistException.php
│   │   └── WrongTaskNumberException.php
│   ├── Filesystem/
│   │   ├── Filesystem.php
│   │   └── FilesystemInterface.php
│   ├── Finder/
│   │   ├── Finder.php
│   │   └── FinderInterface.php
│   ├── HttpClient/
│   │   ├── CurlHttpClient.php
│   │   ├── FallbackHttpClient.php
│   │   ├── HttpClientException.php
│   │   ├── HttpClientInterface.php
│   │   ├── HttpClientLoggerDecorator.php
│   │   └── StreamHttpClient.php
│   ├── Infrastructure/
│   │   ├── Dragonmantank/
│   │   │   └── CronExpression/
│   │   │       ├── DragonmantankCronExpression.php
│   │   │       └── DragonmantankCronExpressionFactory.php
│   │   ├── Laravel/
│   │   │   └── LaravelClosureSerializer.php
│   │   └── Psr/
│   │       └── Logger/
│   │           ├── EnabledLoggerDecorator.php
│   │           ├── PsrStreamLogger.php
│   │           └── PsrStreamLoggerFactory.php
│   ├── Invoker.php
│   ├── Logger/
│   │   ├── ConsoleLogger.php
│   │   ├── ConsoleLoggerInterface.php
│   │   ├── Logger.php
│   │   └── LoggerFactory.php
│   ├── Mailer.php
│   ├── Output/
│   │   └── OutputFactory.php
│   ├── Path/
│   │   └── Path.php
│   ├── Pinger/
│   │   ├── PingableException.php
│   │   ├── PingableInterface.php
│   │   └── PingableTrait.php
│   ├── Process/
│   │   └── Process.php
│   ├── Schedule/
│   │   └── ScheduleFactory.php
│   ├── Schedule.php
│   ├── Stubs/
│   │   └── BasicTask.php
│   ├── Task/
│   │   ├── Collection.php
│   │   ├── CollectionInterface.php
│   │   ├── Loader.php
│   │   ├── LoaderInterface.php
│   │   ├── TaskException.php
│   │   ├── TaskNumber.php
│   │   ├── Timezone.php
│   │   └── WrongTaskInstanceException.php
│   ├── Timezone/
│   │   ├── Provider.php
│   │   └── ProviderInterface.php
│   └── UserInterface/
│       └── Cli/
│           ├── ClosureRunCommand.php
│           └── DebugTaskCommand.php
└── tests/
    ├── EndToEnd/
    │   ├── ClosureRunTest.php
    │   ├── ConfigProviderTest.php
    │   ├── ConfigRecognitionTest.php
    │   ├── DebugTaskTest.php
    │   ├── LoggerTest.php
    │   ├── TasksSourceRecognitionTest.php
    │   ├── VersionTest.php
    │   └── WrongTaskTest.php
    ├── Functional/
    │   ├── ConfigProviderTest.php
    │   ├── DifferentBaseCacheDirTest.php
    │   ├── ScheduleListTest.php
    │   ├── ScheduleRunTest.php
    │   └── TaskGeneratorTest.php
    ├── TestCase/
    │   ├── EndToEnd/
    │   │   └── Environment/
    │   │       ├── Environment.php
    │   │       └── EnvironmentBuilder.php
    │   ├── EndToEndTestCase.php
    │   ├── FakeConfiguration.php
    │   ├── FakeLoader.php
    │   ├── FakeTaskCollection.php
    │   ├── Faker.php
    │   ├── Logger/
    │   │   ├── NullLogger.php
    │   │   └── SpyPsrLogger.php
    │   ├── SerializableTaskRunnerStub.php
    │   ├── TaskRunnerStub.php
    │   ├── TemporaryFile.php
    │   ├── TestClock.php
    │   └── UnitTestCase.php
    ├── Unit/
    │   ├── Application/
    │   │   ├── Cron/
    │   │   │   └── AbstractCronExpressionTestCase.php
    │   │   └── Query/
    │   │       └── TaskInformation/
    │   │           └── TaskInformationHandlerTest.php
    │   ├── CacheDirectoryFactory/
    │   │   └── CacheDirectoryFactoryTest.php
    │   ├── Configuration/
    │   │   ├── ConfigurationParserTest.php
    │   │   ├── ConfigurationTest.php
    │   │   └── FileParserTest.php
    │   ├── Console/
    │   │   └── Command/
    │   │       ├── ScheduleListCommandTest.php
    │   │       └── ScheduleRunCommandTest.php
    │   ├── EnvFlags/
    │   │   └── EnvFlagsTest.php
    │   ├── EventRunnerTest.php
    │   ├── EventTest.php
    │   ├── Filesystem/
    │   │   └── FilesystemTest.php
    │   ├── Finder/
    │   │   └── FinderTest.php
    │   ├── HttpClient/
    │   │   └── StreamHttpClientTest.php
    │   ├── Infrastructure/
    │   │   ├── Dragonmantank/
    │   │   │   └── CronExpression/
    │   │   │       └── DragonmantankCronExpressionTestCase.php
    │   │   └── Psr/
    │   │       └── Logger/
    │   │           ├── EnabledLoggerDecoratorTest.php
    │   │           ├── PsrStreamLoggerFactoryTest.php
    │   │           └── PsrStreamLoggerTest.php
    │   ├── InvokerTest.php
    │   ├── Logger/
    │   │   ├── ConsoleLoggerTest.php
    │   │   └── LoggerFactoryTest.php
    │   ├── MailerTest.php
    │   ├── Output/
    │   │   └── OutputFactoryTest.php
    │   ├── Path/
    │   │   └── PathTest.php
    │   ├── Pingable.php
    │   ├── Pinger/
    │   │   └── PingableTest.php
    │   ├── Process/
    │   │   └── ProcessTest.php
    │   ├── Schedule/
    │   │   └── ScheduleFactoryTest.php
    │   ├── ScheduleTest.php
    │   ├── Service/
    │   │   ├── AbstractClosureSerializerTestCase.php
    │   │   ├── LaravelClosureSerializerTest.php
    │   │   └── LaravelClosureSerializerTestCase.php
    │   ├── Task/
    │   │   ├── TaskNumberTest.php
    │   │   └── TimezoneTest.php
    │   ├── Timezone/
    │   │   └── ProviderTest.php
    │   └── UserInterface/
    │       └── Cli/
    │           └── ClosureRunCommandTest.php
    ├── crunz.yml
    ├── resources/
    │   ├── fixtures/
    │   │   └── finder/
    │   │       ├── direct/
    │   │       │   └── directHere.php
    │   │       └── symlink/
    │   │           └── symlinkHere.php
    │   └── tasks/
    │       ├── ClosureTasks.php
    │       ├── CustomOutputTasks.php
    │       ├── FailTasks.php
    │       ├── NoOverlappingClosureTasks.php
    │       ├── PhpVersionTasks.php
    │       └── WrongTasks.php
    └── tasks/
        └── TestTasks.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
charset = utf-8


================================================
FILE: .gitattributes
================================================
* text=auto

/tests export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.editorconfig export-ignore
README.md export-ignore
LICENSE.md export-ignore
CHANGELOG.md export-ignore
UPGRADE.md export-ignore
.travis.yml export-ignore
appveyor.yml export-ignore
phpunit.xml export-ignore
/.github export-ignore
.php-cs-fixer.dist.php export-ignore
docker-compose.yml export-ignore
/docker export-ignore
phpstan.neon export-ignore
composer-install.php export-ignore
bootstrap.php export-ignore
/resources/docs export-ignore
phpstan-baseline.neon export-ignore


================================================
FILE: .github/FUNDING.yml
================================================
github: PabloKowalczyk


================================================
FILE: .github/ISSUE_TEMPLATE/1_Bug_report.md
================================================
---
name: "\U0001F41B Bug Report"
about: Report errors and problems

---

**Crunz version**: x.y.z

**PHP version**: x.y.z

**Operating system type and version**:

**Description**  
<!-- A clear and concise description of the problem. -->

**How to reproduce**  
<!-- Code and/or config needed to reproduce the problem. -->

**Possible Solution**  
<!--- Optional: only if you have suggestions on a fix/reason for the bug -->

**Additional context**  
<!-- Optional: any other context about the problem: log messages, screenshots, etc. -->


================================================
FILE: .github/ISSUE_TEMPLATE/2_Feature_request.md
================================================
---
name: "\U0001F680 Feature Request"
about: RFC and ideas for new features and improvements

---

**Description**  
<!-- A clear and concise description of the new feature. -->

**Example**  
<!-- A simple example of the new feature in action (include PHP code, etc.)
     If the new feature changes an existing feature, include a simple before/after comparison. -->


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
| Q             | A
| ------------- | ---
| Fixed tickets | #...   <!-- #-prefixed issue number(s), if any -->

<!--
Write a short README entry for your feature/bugfix here (replace this comment block.)
This will help people understand your PR and can be used as a start of the Doc PR.
-->


================================================
FILE: .github/workflows/php.yaml
================================================
name: PHP

on:
    pull_request:
        branches:
            - '3.9'
            - '3.10'
    push: null

permissions: {}

concurrency:
    group: '${{ github.workflow }}-${{ github.ref }}'
    cancel-in-progress: true

jobs:
    tests:
        name: '${{ matrix.php }} / Symfony ${{ matrix.symfony_version }} / ${{ matrix.dependencies }} / ${{ matrix.os }}'
        strategy:
            matrix:
                os:
                    - 'ubuntu-22.04'
                php:
                    - '8.2'
                    - '8.3'
                    - '8.4'
                    - '8.5'
                dependencies:
                    - 'lowest'
                    - 'highest'
                symfony_version:
                    - '~6.4.0'
                    - '~7.4.0'
                    - '~8.0.0'
                include:
                    - os: 'windows-2022'
                      php: '8.2'
                      dependencies: 'highest'
                      symfony_version: '~6.4.0'
                exclude:
                    - os: 'ubuntu-22.04'
                      php: '8.2'
                      dependencies: 'lowest'
                      symfony_version: '~8.0.0'
                    - os: 'ubuntu-22.04'
                      php: '8.2'
                      dependencies: 'highest'
                      symfony_version: '~8.0.0'
                    - os: 'ubuntu-22.04'
                      php: '8.3'
                      dependencies: 'lowest'
                      symfony_version: '~8.0.0'
                    - os: 'ubuntu-22.04'
                      php: '8.3'
                      dependencies: 'highest'
                      symfony_version: '~8.0.0'
        runs-on: ${{ matrix.os }}

        steps:
            -   uses: actions/checkout@v4

            -   uses: shivammathur/setup-php@v2
                with:
                    php-version: ${{ matrix.php }}
                    coverage: none

            -   id: symfony_packages
                shell: bash
                run: |
                    jq --raw-output '"with_string=" + (["--with=" + ((."require" + ."require-dev") | keys[] | select(contains("symfony/"))) + ":${{ matrix.symfony_version }}"] | join(" "))' composer.json \
                        >>"$GITHUB_OUTPUT"

            -   uses: ramsey/composer-install@v3
                with:
                    dependency-versions: ${{ matrix.dependencies }}
                    composer-options: ${{ steps.symfony_packages.outputs.with_string }}

            -   run: composer exec -- phpunit --testsuite EndToEnd

            -   run: composer exec -- phpunit --testsuite Integration

            -   run: composer exec -- phpunit --testsuite Unit

    static_analysis:
        name: Static analysis
        strategy:
            matrix:
                include:
                    - php: '8.2'
                      symfony_version: '~6.4.0'
                      dependencies: 'highest'
        runs-on: ubuntu-22.04

        steps:
            -   uses: actions/checkout@v4

            -   uses: shivammathur/setup-php@v2
                with:
                    php-version: ${{ matrix.php }}
                    coverage: none

            -   id: symfony_packages
                shell: bash
                run: |
                    jq --raw-output '"with_string=" + (["--with=" + ((."require" + ."require-dev") | keys[] | select(contains("symfony/"))) + ":${{ matrix.symfony_version }}"] | join(" "))' composer.json \
                        >>"$GITHUB_OUTPUT"

            -   uses: ramsey/composer-install@v3
                with:
                    dependency-versions: ${{ matrix.dependencies }}
                    composer-options: ${{ steps.symfony_packages.outputs.with_string }}

            -   run: composer normalize --dry-run

            -   uses: actions/cache@v4
                with:
                    path: .php-cs-fixer.cache
                    key: php-cs-fixer-cache

            -   uses: actions/cache@v4
                with:
                    path: /tmp/phpstan
                    key: phpstan-cache

            -   run: composer run crunz:analyze


================================================
FILE: .gitignore
================================================
/vendor
/tasks
composer.phar
/composer.lock
.DS_Store
*.log
.idea/
var/
.php-cs-fixer.cache
/crunz.phar
/crunz.yml
/.phpunit.result.cache


================================================
FILE: .php-cs-fixer.dist.php
================================================
<?php

declare(strict_types=1);

use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;

return (new PhpCsFixer\Config())
    ->setParallelConfig(ParallelConfigFactory::detect())
    ->setRules([
        '@Symfony' => true,
        'array_syntax' => ['syntax' => 'short'],
        'protected_to_private' => false,
        'combine_consecutive_unsets' => true,
        'combine_consecutive_issets' => true,
        'compact_nullable_type_declaration' => true,
        'declare_strict_types' => true,
        'dir_constant' => true,
        'ereg_to_preg' => true,
        'explicit_indirect_variable' => true,
        'explicit_string_variable' => true,
        'function_to_constant' => true,
        'is_null' => true,
        'modernize_types_casting' => true,
        'linebreak_after_opening_tag' => true,
        'list_syntax' => ['syntax' => 'short'],
        'mb_str_functions' => true,
        'native_function_invocation' => [
            'include' => ['@all'],
        ],
        'no_alias_functions' => true,
        'no_homoglyph_names' => true,
        'no_php4_constructor' => true,
        'no_useless_else' => true,
        'no_useless_return' => true,
        'ordered_class_elements' => true,
        'ordered_imports' => true,
        'php_unit_construct' => true,
        'php_unit_dedicate_assert' => true,
        'php_unit_expectation' => true,
        'php_unit_mock' => true,
        'php_unit_namespaced' => true,
        'php_unit_method_casing' => ['case' => 'snake_case'],
        'random_api_migration' => true,
        'strict_comparison' => true,
        'strict_param' => true,
        'ternary_to_null_coalescing' => true,
        'void_return' => true,
        'concat_space' => [
            'spacing' => 'one',
        ],
        'single_line_throw' => false,
        'php_unit_test_case_static_method_calls' => [
            'call_type' => 'self',
        ],
    ])
    ->setRiskyAllowed(true)
    ->setFinder(
        PhpCsFixer\Finder::create()
            ->in(__DIR__ . '/src')
            ->in(__DIR__ . '/tests')
            ->in(__DIR__ . '/config')
            ->append(
                [
                    __FILE__,
                    __DIR__ . '/composer-install.php',
                ]
            )
    )
;


================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased

## [v3.7.0] - 2024-08-12

### Changed

- [crunzphp#77] Require at least Symfony 6.4
- [crunzphp#79] Update PHPCSFixer
- [crunzphp#78] Allow Symfony v7

## [v3.6.0] - 2023-11-10

### Added

- [crunzphp#68] Getter for 'from', 'to' event's configuration, thanks to [@lucatacconi]

## [v3.5.1] - 2023-11-03

### Fixed

- [crunzphp#62] Task Life Time functions don't respect the timezone

## [v3.5.0] - 2023-10-15

### Added

- [crunzphp#39] Add PHP v8.2 support
- [crunzphp#64] Test Symfony v6.3
- [crunzphp#66] Add PHP v8.3 support

### Removed

- [crunzphp#38] Drop PHP v7.4 support
- [crunzphp#54] Drop Symfony v6.0 support
- [crunzphp#63] Drop Symfony v6.1 support
- [crunzphp#65] Drop Symfony v6.2 support

## [v3.4.1] - 2022-11-01

### Fixed

- [crunzphp#44] Do not require BlockingStoreInterface in preventOverlapping()

## [v3.4.0] - 2022-10-03

### Added

- [crunzphp#31] Add format option to `schedule:list`

## [v3.3.0] - 2022-06-19

### Deprecated

- [crunzphp#24] Deprecate non string parameters

## [v3.2.2] - 2022-05-28

### Fixed

- [crunzphp#5] Fix Symfony 6.1 deprecations

## [v3.2.1] - 2022-01-09

### Fixed

- [#401] Fix version number

## [v3.2.0] - 2022-01-09

### Added

- [#398] Add hourlyAt()
- [#390] Symfony 6 Support, thanks to [@bashgeek]

### Changed

- [#396] Remove `opis/closure` and use `laravel/serializable-closure` instead

### Removed

- [#393] Drop Symfony 5.2 support
- [#394] Drop Symfony 5.3 support

## [v3.1.0] - 2021-11-24

### Changed

- [#385] Replace `Swiftmailer` with `symfony/mailer`

## [v3.0.1] - 2021-05-25

### Fixed

- [#361] Log to specific event log file, thanks to [@drjayvee]

## [v2.3.1] - 2021-05-25

### Fixed

- [#361] Log to specific event log file, thanks to [@drjayvee]

## [v3.0.0] - 2021-04-25

### Changed

- [#349] Require at least PHP v7.4
- [#356] Require package "dragonmantank/cron-expression" at least "v3.1"

### Removed

- [#351] Drop Symfony v3.4 support
- [#352] Drop Symfony v5.1 support
- [#354] Remove most "Crunz\Event::every*" methods

## [v2.3.0] - 2021-03-14

### Deprecated

- [#344] Deprecate most "Event::every*" methods

### Removed

- [#323] Drop Symfony 4.3 support
- [#324] Drop Symfony 5.0 support

## [v2.2.4] - 2020-12-18

### Fixed

- [#333] Include symlinks in Finder, thanks to [@iluuu1994]

## [v2.2.3] - 2020-11-29

### Fixed

- [#334] Fix disabling logger not working

## [v2.2.2] - 2020-10-12

### Fixed

- [#326] Fix lock key on closures

## [v2.2.1] - 2020-10-08

### Fixed

- [#321] Add PHP8 support

## [v2.2.0] - 2020-06-18

### Added

- [#287] Add `task:debug` command
- [#233] Add option to ignore empty context in monolog, thanks to [@rrushton]
- [#298] Add `logger_factory` config option

### Removed

- [#292] Drop Symfony 4.2 support

## [v2.1.0] - 2020-02-02

### Added

- [#274] Symfony 5 support

### Changed

- [#240] cron-expression package, thanks to [@mareksuscak]
- [#280] Hide `closure:run` command

## [v2.0.4] - 2019-12-08

### Fixed

- [#268] Fix Symfony 4.4 deprecations

## [v1.12.4] - 2019-12-08

### Fixed

- [#268] Fix Symfony 4.4 deprecations

## [v2.0.3] - 2019-11-17

### Fixed

- [#261] Release lock on error
- [#264] Revert converting closure result to `int`

## [v1.12.3] - 2019-11-17

### Fixed

- [#261] Release lock on error

## [v2.0.2] - 2019-10-06

### Fixed

- [#251] Update PHPUnit to avoid PHP7.4 deprecations 

## [v1.12.2] - 2019-10-05

### Fixed

- [#243] Sandbox task loading
- [#245] Fix PHP 7.4 compatibility

## [v2.0.1] - 2019-05-10

### Fixed

- [#229] Fix recursive tasks scan

## [v1.12.1] - 2019-05-01

### Fixed

- [#229] Fix recursive tasks scan

## [v2.0.0] - 2019-04-24

### Changed

- [#101] Throw exception on empty timezone
- [#204] More than five parts cron expressions will throw exception
- [#221] Throw `Crunz\Task\WrongTaskInstanceException` when task is not `Schedule` instance
- [#222] Make `\Crunz\Event::setProcess` private
- [#225] Bump dependencies

### Removed

- [#103] Removed `Crunz\Output\VerbosityAwareOutput` class
- [#206] Remove legacy paths recognition
- [#224] Remove `mail` transport

## [v1.12.0] - 2019-04-07

### Added

- [#178], [#217] `timezone_log` configuration option to decide whether
configured `timezone` should be used for logs, thanks to [@SadeghPM]

### Deprecated

- Using `\Crunz\Event::setProcess` is deprecated, this method was intended to be `private`,
but for some reason is `public`.
In `v2.0` this method will became private and result in exception if you call it.
- [#199] Not returning `\Crunz\Schedule` instance from your task is deprecated.
In `v2` this will result in exception.

## [v1.11.2] - 2019-03-16

### Fixed

- [#209], [#210] Composer installs crunz executable to vendor/bin instead of symlink

## [v1.11.1] - 2019-01-27

### Fixed

- [#190] Fix Crunz bin path when running closures

## [v1.11.0] - 2019-01-24

### Fixed

- [#181] Fix missing tasks source
- [#180] Fix deprecation messages not showing

### Deprecated

- Relying on tasks' source/config file recognition related to Crunz bin 

## [v1.11.0-rc.1] - 2018-12-22

### Fixed

- [#171] Fix lock storage bug
- [#173] Remove Symfony 4.2 deprecations
- [#166] Improve task collection debugging

## [v1.11.0-beta.2] - 2018-11-10

### Fixed

- [#162] Fix command error output [closes [#161]]

## [v1.11.0-beta.1] - 2018-10-23

### Added

- [#153] Add support for `symfony/lock`, Thanks to [@digilist]

### Fixed

- [#146] Make paths relative to current working directory - "cwd".
- [#158] Accept only string task number.

## [v1.10.1] - 2018-09-22

### Fixed

- [#139] Do not require `cURL` extension

## [v1.10.0] - 2018-09-22

### Fixed

- [#137] Treat whole output of failed command as "error output".

### Removed

- [#136] Remove guzzle

## [v1.9.0] - 2018-08-18

### Changed

- [#132] Improved container caching in shared servers

### Fixed

- [#131] Crunz can be used with `dragonmantank/cron-expression` package

### Deprecated

- Passing more than five parts (e.g `* * * * * *`) to `Crunz\Event::cron()`

## [v1.8.0] - 2018-08-15

### Added

- [#120] Added `--force` option to `schedule:run` command
- [#129] Add `--task` option for `schedule:run` command

### Fixed

- [#123] Spellfix: `comand` -> `command`, Thanks to [@FallDi]

## [v1.7.3] - 2018-06-15

- [#118] Undefined index: year in `vendor/lavary/crunz/src/Event.php` on line 370, Thanks to [@mindcreations]

## [v1.7.2] - 2018-06-13

### Fixed

- [#116] Do not replace Symfony's polyfills.

## [v1.7.1] - 2018-06-01

### Fixed

- [#110] Fixed config file path guessing.

## [v1.7.0] - 2018-05-27

### Added

- [#94] Added timezone option

### Deprecated
- `timezone` option in config file is now required, lack of it will result in Exception in version `2.0`

### Removed

- [#104] Remove splitCamel helper.

## [v1.6.1] - 2018-05-13

### Fixed

- [#90] Send output by email only if it is not empty.

## [v1.6.0] - 2018-04-22

### Added

- [#69] Option for allowing line breaks in logs, Thanks to [@TomasDuda]
- [#79] Introduce DI container

### Fixed

- [#43] Typos stopping email transport of 'mail', Thanks to [@m-hume]
- [#46] sendOutputTo and appendOutputTo fix, Thanks to [@m-hume]
- [#80] Fixed prevent overlapping on windows
- [#81] Fix Event::in on windows
- [#84] Make comparing date segments strict.
- [#86] Fix closure running on windows
- [#85] Fix changing user
- [#87] Remove error handler

## [v1.5.1] - 2018-04-12

### Added

- [#76] Introduce editorconfig
- [#75] Added changelog file.

### Fixed

- [#77] Fix high cpu usage

[#401]: https://github.com/lavary/crunz/pull/401
[#398]: https://github.com/lavary/crunz/pull/398
[#396]: https://github.com/lavary/crunz/pull/396
[#394]: https://github.com/lavary/crunz/pull/394
[#393]: https://github.com/lavary/crunz/pull/393
[#390]: https://github.com/lavary/crunz/pull/390
[#385]: https://github.com/lavary/crunz/pull/385
[#361]: https://github.com/lavary/crunz/pull/361
[#356]: https://github.com/lavary/crunz/pull/356
[#354]: https://github.com/lavary/crunz/pull/354
[#352]: https://github.com/lavary/crunz/pull/352
[#351]: https://github.com/lavary/crunz/pull/351
[#349]: https://github.com/lavary/crunz/pull/349
[#344]: https://github.com/lavary/crunz/pull/344
[#334]: https://github.com/lavary/crunz/pull/334
[#333]: https://github.com/lavary/crunz/pull/333
[#326]: https://github.com/lavary/crunz/pull/326
[#324]: https://github.com/lavary/crunz/pull/324
[#323]: https://github.com/lavary/crunz/pull/323
[#321]: https://github.com/lavary/crunz/pull/321
[#298]: https://github.com/lavary/crunz/pull/298
[#292]: https://github.com/lavary/crunz/pull/292
[#287]: https://github.com/lavary/crunz/pull/287
[#280]: https://github.com/lavary/crunz/pull/280
[#274]: https://github.com/lavary/crunz/pull/274
[#268]: https://github.com/lavary/crunz/pull/268
[#264]: https://github.com/lavary/crunz/pull/264
[#261]: https://github.com/lavary/crunz/pull/261
[#251]: https://github.com/lavary/crunz/pull/251
[#245]: https://github.com/lavary/crunz/pull/245
[#243]: https://github.com/lavary/crunz/pull/243
[#240]: https://github.com/lavary/crunz/pull/240
[#233]: https://github.com/lavary/crunz/pull/233
[#229]: https://github.com/lavary/crunz/pull/229
[#225]: https://github.com/lavary/crunz/pull/225
[#224]: https://github.com/lavary/crunz/pull/224
[#222]: https://github.com/lavary/crunz/pull/222
[#221]: https://github.com/lavary/crunz/pull/221
[#217]: https://github.com/lavary/crunz/pull/217
[#210]: https://github.com/lavary/crunz/pull/210
[#209]: https://github.com/lavary/crunz/pull/209
[#206]: https://github.com/lavary/crunz/pull/206
[#204]: https://github.com/lavary/crunz/pull/204
[#199]: https://github.com/lavary/crunz/pull/199
[#190]: https://github.com/lavary/crunz/pull/190
[#181]: https://github.com/lavary/crunz/pull/181
[#180]: https://github.com/lavary/crunz/pull/180
[#178]: https://github.com/lavary/crunz/pull/178
[#173]: https://github.com/lavary/crunz/pull/173  
[#171]: https://github.com/lavary/crunz/pull/171
[#166]: https://github.com/lavary/crunz/pull/166
[#164]: https://github.com/lavary/crunz/pull/164
[#163]: https://github.com/lavary/crunz/pull/163
[#162]: https://github.com/lavary/crunz/pull/162
[#161]: https://github.com/lavary/crunz/pull/161
[#159]: https://github.com/lavary/crunz/pull/159
[#158]: https://github.com/lavary/crunz/pull/158
[#157]: https://github.com/lavary/crunz/pull/157
[#155]: https://github.com/lavary/crunz/pull/155
[#154]: https://github.com/lavary/crunz/pull/154
[#153]: https://github.com/lavary/crunz/pull/153
[#151]: https://github.com/lavary/crunz/pull/151
[#150]: https://github.com/lavary/crunz/pull/150
[#149]: https://github.com/lavary/crunz/pull/149
[#148]: https://github.com/lavary/crunz/pull/148
[#147]: https://github.com/lavary/crunz/pull/147
[#146]: https://github.com/lavary/crunz/pull/146
[#142]: https://github.com/lavary/crunz/pull/142
[#141]: https://github.com/lavary/crunz/pull/141
[#140]: https://github.com/lavary/crunz/pull/140
[#139]: https://github.com/lavary/crunz/pull/139
[#138]: https://github.com/lavary/crunz/pull/138
[#137]: https://github.com/lavary/crunz/pull/137
[#136]: https://github.com/lavary/crunz/pull/136
[#133]: https://github.com/lavary/crunz/pull/133
[#132]: https://github.com/lavary/crunz/pull/132
[#131]: https://github.com/lavary/crunz/pull/131
[#130]: https://github.com/lavary/crunz/pull/130
[#129]: https://github.com/lavary/crunz/pull/129
[#123]: https://github.com/lavary/crunz/pull/123
[#120]: https://github.com/lavary/crunz/pull/120
[#119]: https://github.com/lavary/crunz/pull/119
[#118]: https://github.com/lavary/crunz/pull/118
[#117]: https://github.com/lavary/crunz/pull/117
[#116]: https://github.com/lavary/crunz/pull/116
[#113]: https://github.com/lavary/crunz/pull/113
[#112]: https://github.com/lavary/crunz/pull/112
[#111]: https://github.com/lavary/crunz/pull/111
[#110]: https://github.com/lavary/crunz/pull/110
[#109]: https://github.com/lavary/crunz/pull/109
[#107]: https://github.com/lavary/crunz/pull/107
[#105]: https://github.com/lavary/crunz/pull/105
[#104]: https://github.com/lavary/crunz/pull/104
[#103]: https://github.com/lavary/crunz/pull/103
[#102]: https://github.com/lavary/crunz/pull/102
[#101]: https://github.com/lavary/crunz/pull/101
[#100]: https://github.com/lavary/crunz/pull/100
[#98]: https://github.com/lavary/crunz/pull/98
[#97]: https://github.com/lavary/crunz/pull/97
[#96]: https://github.com/lavary/crunz/pull/96
[#95]: https://github.com/lavary/crunz/pull/95
[#94]: https://github.com/lavary/crunz/pull/94
[#92]: https://github.com/lavary/crunz/pull/92
[#90]: https://github.com/lavary/crunz/pull/90
[#89]: https://github.com/lavary/crunz/pull/89
[#88]: https://github.com/lavary/crunz/pull/88
[#87]: https://github.com/lavary/crunz/pull/87
[#86]: https://github.com/lavary/crunz/pull/86
[#85]: https://github.com/lavary/crunz/pull/85
[#84]: https://github.com/lavary/crunz/pull/84
[#82]: https://github.com/lavary/crunz/pull/82
[#81]: https://github.com/lavary/crunz/pull/81
[#80]: https://github.com/lavary/crunz/pull/80
[#79]: https://github.com/lavary/crunz/pull/79
[#77]: https://github.com/lavary/crunz/pull/77
[#76]: https://github.com/lavary/crunz/pull/76
[#75]: https://github.com/lavary/crunz/pull/75
[#74]: https://github.com/lavary/crunz/pull/74
[#73]: https://github.com/lavary/crunz/pull/73
[#72]: https://github.com/lavary/crunz/pull/72
[#69]: https://github.com/lavary/crunz/pull/69
[#50]: https://github.com/lavary/crunz/pull/50
[#46]: https://github.com/lavary/crunz/pull/46
[#43]: https://github.com/lavary/crunz/pull/43
[#36]: https://github.com/lavary/crunz/pull/36
[#25]: https://github.com/lavary/crunz/pull/25
[#24]: https://github.com/lavary/crunz/pull/24
[#23]: https://github.com/lavary/crunz/pull/23
[#17]: https://github.com/lavary/crunz/pull/17
[#16]: https://github.com/lavary/crunz/pull/16
[crunzphp#5]: https://github.com/crunzphp/crunz/pull/5
[crunzphp#24]: https://github.com/crunzphp/crunz/pull/24
[crunzphp#31]: https://github.com/crunzphp/crunz/pull/31
[crunzphp#38]: https://github.com/crunzphp/crunz/pull/38
[crunzphp#39]: https://github.com/crunzphp/crunz/pull/39
[crunzphp#44]: https://github.com/crunzphp/crunz/pull/44
[crunzphp#54]: https://github.com/crunzphp/crunz/pull/54
[crunzphp#62]: https://github.com/crunzphp/crunz/pull/62
[crunzphp#63]: https://github.com/crunzphp/crunz/pull/63
[crunzphp#64]: https://github.com/crunzphp/crunz/pull/64
[crunzphp#65]: https://github.com/crunzphp/crunz/pull/65
[crunzphp#66]: https://github.com/crunzphp/crunz/pull/66
[crunzphp#68]: https://github.com/crunzphp/crunz/pull/68
[crunzphp#77]: https://github.com/crunzphp/crunz/pull/77
[crunzphp#78]: https://github.com/crunzphp/crunz/pull/78
[crunzphp#79]: https://github.com/crunzphp/crunz/pull/79
[v1.5.1]: https://github.com/crunzphp/crunz/compare/v1.5.0...v1.5.1
[v1.6.0]: https://github.com/crunzphp/crunz/compare/v1.5.1...v1.6.0
[v1.6.1]: https://github.com/crunzphp/crunz/compare/v1.6.0...v1.6.1
[v1.7.0]: https://github.com/crunzphp/crunz/compare/v1.6.1...v1.7.0
[v1.7.1]: https://github.com/crunzphp/crunz/compare/v1.7.0...v1.7.1
[v1.7.2]: https://github.com/crunzphp/crunz/compare/v1.7.1...v1.7.2
[v1.7.3]: https://github.com/crunzphp/crunz/compare/v1.7.2...v1.7.3
[v1.8.0]: https://github.com/crunzphp/crunz/compare/v1.7.3...v1.8.0
[v1.9.0]: https://github.com/crunzphp/crunz/compare/v1.8.0...v1.9.0
[v1.10.0]: https://github.com/crunzphp/crunz/compare/v1.9.0...v1.10.0
[v1.10.1]: https://github.com/crunzphp/crunz/compare/v1.10.0...v1.10.1
[v1.11.0-beta.1]: https://github.com/crunzphp/crunz/compare/v1.10.1...v1.11.0-beta.1
[v1.11.0-beta.2]: https://github.com/crunzphp/crunz/compare/v1.11.0-beta.1...v1.11.0-beta.2
[v1.11.0-rc.1]: https://github.com/crunzphp/crunz/compare/v1.11.0-beta.2...v1.11.0-rc.1
[v1.11.0]: https://github.com/crunzphp/crunz/compare/v1.11.0-rc.1...v1.11.0
[v1.11.1]: https://github.com/crunzphp/crunz/compare/v1.11.0...v1.11.1
[v1.11.2]: https://github.com/crunzphp/crunz/compare/v1.11.1...v1.11.2
[v1.12.0]: https://github.com/crunzphp/crunz/compare/v1.11.2...v1.12.0
[v1.12.1]: https://github.com/crunzphp/crunz/compare/v1.12.0...v1.12.1
[v1.12.2]: https://github.com/crunzphp/crunz/compare/v1.12.1...v1.12.2
[v1.12.3]: https://github.com/crunzphp/crunz/compare/v1.12.2...v1.12.3
[v1.12.4]: https://github.com/crunzphp/crunz/compare/v1.12.3...v1.12.4
[v2.0.0]: https://github.com/crunzphp/crunz/compare/v1.12.0...v2.0.0
[v2.0.1]: https://github.com/crunzphp/crunz/compare/v2.0.0...v2.0.1
[v2.0.2]: https://github.com/crunzphp/crunz/compare/v2.0.1...v2.0.2
[v2.0.3]: https://github.com/crunzphp/crunz/compare/v2.0.2...v2.0.3
[v2.0.4]: https://github.com/crunzphp/crunz/compare/v2.0.3...v2.0.4
[v2.1.0]: https://github.com/crunzphp/crunz/compare/v2.0.4...v2.1.0
[v2.2.0]: https://github.com/crunzphp/crunz/compare/v2.1.0...v2.2.0
[v2.2.1]: https://github.com/crunzphp/crunz/compare/v2.2.0...v2.2.1
[v2.2.2]: https://github.com/crunzphp/crunz/compare/v2.2.1...v2.2.2
[v2.2.3]: https://github.com/crunzphp/crunz/compare/v2.2.2...v2.2.3
[v2.2.4]: https://github.com/crunzphp/crunz/compare/v2.2.3...v2.2.4
[v2.3.0]: https://github.com/crunzphp/crunz/compare/v2.2.4...v2.3.0
[v2.3.1]: https://github.com/crunzphp/crunz/compare/v2.3.0...v2.3.1
[v3.0.0]: https://github.com/crunzphp/crunz/compare/v2.3.1...v3.0.0
[v3.0.1]: https://github.com/crunzphp/crunz/compare/v3.0.0...v3.0.1
[v3.1.0]: https://github.com/crunzphp/crunz/compare/v3.0.1...v3.1.0
[v3.2.0]: https://github.com/crunzphp/crunz/compare/v3.1.0...v3.2.0
[v3.2.1]: https://github.com/crunzphp/crunz/compare/v3.2.0...v3.2.1
[v3.2.2]: https://github.com/crunzphp/crunz/compare/v3.2.1...v3.2.2
[v3.3.0]: https://github.com/crunzphp/crunz/compare/v3.2.2...v3.3.0
[v3.4.0]: https://github.com/crunzphp/crunz/compare/v3.3.0...v3.4.0
[v3.4.1]: https://github.com/crunzphp/crunz/compare/v3.4.0...v3.4.1
[v3.5.0]: https://github.com/crunzphp/crunz/compare/v3.4.1...v3.5.0
[v3.5.1]: https://github.com/crunzphp/crunz/compare/v3.5.0...v3.5.1
[v3.6.0]: https://github.com/crunzphp/crunz/compare/v3.5.1...v3.6.0
[v3.7.0]: https://github.com/crunzphp/crunz/compare/v3.6.0...v3.7.0
[@andrewmy]: https://github.com/andrewmy
[@arthurbarros]: https://github.com/arthurbarros
[@bashgeek]: https://github.com/bashgeek
[@codermarcel]: https://github.com/codermarcel
[@digilist]: https://github.com/digilist
[@drjayvee]: https://github.com/drjayvee
[@erfan723]: https://github.com/erfan723
[@FallDi]: https://github.com/FallDi
[@iluuu1994]: https://github.com/iluuu1994
[@jhoughtelin]: https://github.com/jhoughtelin
[@lucatacconi]: https://github.com/lucatacconi
[@m-hume]: https://github.com/m-hume
[@mareksuscak]: https://github.com/mareksuscak
[@mindcreations]: https://github.com/mindcreations
[@PhilETaylor]: https://github.com/PhilETaylor
[@radarhere]: https://github.com/radarhere
[@rrushton]: https://github.com/rrushton
[@SadeghPM]: https://github.com/SadeghPM
[@timurbakarov]: https://github.com/timurbakarov
[@TomasDuda]: https://github.com/TomasDuda
[@vinkla]: https://github.com/vinkla


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Moe Reza Lavarian

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
sh-php:
	docker compose exec --user=www-data php82 sh


================================================
FILE: README.md
================================================
# Crunz needs your funding 💲

## Support further Crunz development by [GitHub](https://github.com/sponsors/PabloKowalczyk).

Check [more info](https://github.com/crunzphp/crunz/issues/111).

# Crunz

Install a cron job once and for all, manage the rest from the code.

Crunz is a framework-agnostic package to schedule periodic tasks (cron jobs) in PHP using a fluent API.

Crunz is capable of executing any kind of executable command as well as PHP closures.

[![Version](http://img.shields.io/packagist/v/crunzphp/crunz.svg?style=flat-square)](https://packagist.org/packages/crunzphp/crunz)
[![Packagist](https://img.shields.io/packagist/dt/crunzphp/crunz.svg?style=flat-square)](https://packagist.org/packages/crunzphp/crunz/stats)
[![Packagist](https://img.shields.io/packagist/dm/crunzphp/crunz.svg?style=flat-square)](https://packagist.org/packages/crunzphp/crunz/stats)

## Roadmap
| Version | Release date | Active support until | Bug support until | Status         |
|---------|--------------|----------------------|-------------------|----------------|
| v1.x    | April 2016   | April 2019           | April 2020        | End of life    |
| v2.x    | April 2019   | April 2021           | April 2022        | End of life    |
| v3.x    | April 2021   | TBD                  | TBD               | Active support |
| v4.x    | TBD          | TBD                  | TBD               | Development    |

## Installation

To install it:

```bash
composer require crunzphp/crunz
```
If the installation is successful, a command-line utility named **crunz** is symlinked to the `vendor/bin` directory of your project.

## How It Works?

The idea is very simple: instead of installing cron jobs in a crontab file, we define them in one or several PHP files, by using the Crunz interface. 

Here's a basic example:

```php
<?php
// tasks/backupTasks.php

use Crunz\Schedule;

$schedule = new Schedule();
$task = $schedule->run('cp project project-bk');       
$task->daily();

return $schedule;
```

To run the tasks, you only need to install an ordinary cron job (a crontab entry) which runs **every minute**, and delegates the responsibility to Crunz' event runner:

```bash
* * * * * cd /project && vendor/bin/crunz schedule:run
```

The command `schedule:run` is responsible for collecting all the PHP task files and run the tasks which are due.

## Task Files

Task files resemble crontab files. Just like crontab files they can contain one or more tasks.

Normally we create our task files in the `tasks/` directory within the project's root directory. 

> By default, Crunz assumes all the task files reside in the `tasks/` directory within the project's root directory.

There are two ways to specify the source directory: 1) Configuration file  2) As a parameter to the event runner command.
 
We can explicitly set the source path by passing it to the event runner as a parameter:

```bash
* * * * * cd /project && vendor/bin/crunz schedule:run /path/to/tasks/directory
```

### Creating a Simple Task

In the terminal, change the directory to your project's root directory and run the following commands:

```bash
mkdir tasks && cd tasks
nano GeneralTasks.php
```

Then, add a task as below:

```php
<?php
// tasks/FirstTasks.php

use Crunz\Schedule;

$schedule = new Schedule();

$task = $schedule->run('cp project project-bk'); 
$task
    ->daily()
    ->description('Create a backup of the project directory.');

// ...

// IMPORTANT: You must return the schedule object
return $schedule; 
```

There are some conventions for creating a task file, which you need to follow. First of all, the filename should end with `Tasks.php` unless we change this via the configuration settings.  

In addition to that, we **must** return the instance of `Schedule` class at the end of each file, otherwise, all the tasks inside the file will be skipped by the event runner.

Since Crunz scans the tasks directory recursively, we can either put all the tasks in one file or across different files (or directories) based on their usage. This behavior helps us have a well organized tasks directory.


## The Command

We can run **any** command or script by using `run()`. This method accepts two arguments:  **the command to be executed**, and **the command options** (as an associative array) if there's any.

### Normal Command or Script

```php
<?php

use Crunz\Schedule;

$schedule = new Schedule();
$task = $schedule->run(PHP_BINARY . ' backup.php', ['--destination' => 'path/to/destination']);
$task
    ->everyMinute()
    ->description('Copying the project directory');

return $schedule;
```

In the above example, `--destination` is an option supported by `backup.php` script.

### Closures

We can also write to a closure instead of a command:

```php
<?php

use Crunz\Schedule;

$schedule = new Schedule();

$x = 12;
$task = $schedule->run(function() use ($x) { 
   // Do some cool stuff in here 
});

$task
    ->everyMinute()
    ->description('Copying the project directory');

return $schedule;
```

## Frequency of Execution

There are a variety of ways to specify **when** and **how often** a task should run. We can combine these methods together to get our desired frequencies. 

### Units of Time

There are a group of methods which specify a unit of time (bigger than minute) as frequency. They usually end with `ly` suffix, as in `hourly()`, `daily()`, `weekly`, `monthly()`, `quarterly()`, and  `yearly` .

All the events scheduled with this set of methods happen at the **beginning** of that time unit. For example `weekly()` will run the event on Sundays, and `monthly()` will run on the first day of each month.

The task below will run **daily at midnight** (start of the daily time period).

```php
<?php
// ...
$task = $schedule->run(PHP_BINARY . ' backup.php');    
$task->daily();
// ...
```

Here's another one, which runs on the **first day of each month**.

```php
<?php
// ...
$task = $schedule->run(PHP_BINARY . ' email.php');
$task->monthly();
// ...
```

### Running Events at Certain Times

To schedule a one-off tasks, you may use `on()` method like this:

```php
<?php
// ...
$task = $schedule->run(PHP_BINARY . ' email.php'); 
$task->on('13:30 2016-03-01');
// ...
```

The above task will run on the first of march 2016 at 01:30 pm. 

> `On()` accepts any date format parsed by PHP's [strtotime](http://php.net/manual/en/function.strtotime.php) function.

To specify the time of a task we use `at()` method:

```php
<?php
// ...
$task = $schedule->run(PHP_BINARY . ' email.php'); 
$task
    ->daily()
    ->at('13:30');
// ...
```

If we only pass a time to the `on()` method, it will have the same effect as using `at()`

```php
<?php
// ...
$task = $schedule->run(PHP_BINARY . ' email.php');   
$task
    ->daily()
    ->on('13:30');
         
// is the sames as
$task = $schedule->run(PHP_BINARY . ' email.php');       
$task
    ->daily()
    ->at('13:30');
// ...
```

We can combine the "Unit of Time" methods eg. daily(), monthly() with the at() or on() constraint in a single statement if we wish.

The following task will be run every hour at the 15th minute

```php
<?php
// ...
$task = $schedule->run(PHP_BINARY . ' feedmecookie.php'); 
$task
    ->hourlyAt('15');
// ...
```
>hourlyOn('15') could have been used instead of hourlyAt('15') with the same result

The following task will be run Monday at 13:30
```php
<?php
// ...
$task = $schedule->run(PHP_BINARY . ' startofwork.php'); 
$task
    ->weeklyOn(1,'13:30');
// ...
```
>Sunday is considered day 0 of the week. 

If we wished for the task to run on Tuesday (day 2 of the week) at 09:00 we would have used:
```php
<?php
// ...
$task = $schedule->run(PHP_BINARY . ' startofwork.php'); 
$task
    ->weeklyOn(2,'09:00');
// ...
```

## Task Life Time

In a crontab entry, we can not easily specify a task's lifetime (the period of time when the task is active). However, it's been made easy in Crunz:

```php
<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->from('12:30 2016-03-04')
    ->to('04:55 2016-03-10');
 //       
```
Or alternatively we can use the `between()` method to accomplish the same result:

```php
<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->between('12:30 2016-03-04', '04:55 2016-03-10');

 //       
```

If we don't specify the date portion, the task will be active **every** day but only within the specified duration:

```php
<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
     ->everyFiveMinutes()
     ->between('12:30', '04:55');

 //       
```

The above task runs **every five minutes** between **12:30 pm** and **4:55 pm** every day.

An example of restricting a task from running only during a certain range of minutes each hour can be achieved as follows:

```php
<?php
//

$hour = date('H');
$startminute = $hour.':05';
$endminute = $hour.':15';

$task = $schedule->run(PHP_BINARY . ' email.php');
$task
     ->hourly()
     ->between($startminute, $endminute);

 //       
```

The above task runs **every hour** between **minutes 5 to 15**

### Weekdays

Crunz also provides a set of methods which specify a certain day in the week. 
* `mondays()`
* `tuesdays()`
* `wednesdays()`
* `thursdays()`
* `fridays()`
* `saturdays()`
* `sundays()`
* `weekdays()`

These methods have been designed to be used as a constraint and should not be used alone. The reason is that weekday methods just modify the `Day of Week` field of a cron job expression.

Consider the following example:

```php
<?php
// Cron equivalent:  * * * * 1
$task = $schedule->run(PHP_BINARY . ' startofwork.php');
$task->mondays();
```

At first glance, the task seems to run **every Monday**, but since it only modifies the "day of week" field of the cron job expression, the task  runs **every minute on Mondays**.

This is the correct way of using weekday methods:

```php
<?php
// ...
$task = $schedule->run(PHP_BINARY . ' startofwork.php');
$task    
    ->mondays()
    ->at('13:30');

// ...
```
>(An easier to read alternative with a similar result ->weeklyOn(0,'13:30') to that shown in a previously example above)


### The Classic Way

We can also do the scheduling the old way, just like we do in a crontab file:

```php
<?php

$task = $schedule->run(PHP_BINARY . ' email.php');
$task->cron('30 12 * 5-6,9 Mon,Fri');
```

### Setting Individual Fields

Crunz's methods are not limited to the ready-made methods explained. We can also set individual fields to compose custom frequencies similar to how a classic crontab would composed them. These methods either accept an array of values, or list arguments separated by commas:

```php
<?php
// ...
$task = $schedule->run(PHP_BINARY . ' email.php');
$task       
    ->minute(['1-30', 45, 55])
    ->hour('1-5', 7, 8)
    ->dayOfMonth(12, 15)
    ->month(1);
```

Or:

```php
<?php
// ...
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->minute('30')
    ->hour('13')
    ->month([1,2])
    ->dayofWeek('Mon', 'Fri', 'Sat');

// ...
```

Based on our use cases, we can choose and combine the proper set of methods, which are easier to use.

## Running Conditions

Another thing that we cannot do very easily in a traditional crontab file is to make conditions for running the tasks. This has been made easy by `when()` and `skip()` methods.

Consider the following code:

```php
<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->between('12:30 2016-03-04', '04:55 2016-03-10')
    ->when(function() {
        if ((bool) (time() % 2)) {
            return true;
        }
        
        return false;
    });
```

Method `when()` accepts a callback,  which must return `TRUE` for the task to run. This is really useful when we need to check our resources before performing a resource-hungry task.

We can also skip a task under certain conditions, by using `skip()` method. If the passed callback returns `TRUE`, the task will be skipped.

```php
<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->between('12:30 2016-03-04', '04:55 2016-03-10')
    ->skip(function() {
        if ((bool) (time() % 2)) {
            return true;
        }
        
        return false;  
    });

 //       
```

We can use these methods **several** times for a single task. They are evaluated sequentially.

## Changing Directories

You can use the `in()` method to change directory before running a command:

```php
<?php

// ...

$task = $schedule->run('./deploy.sh');
$task
    ->in('/home')
    ->weekly()
    ->sundays()
    ->at('12:30')
    ->appendOutputTo('/var/log/backup.log');

// ...

return $schedule;
```

## Parallelism and the Locking Mechanism

Crunz runs the scheduled events in parallel (in separate processes), so all the events which have the same frequency of execution, will run at the same time asynchronously. To achieve this, Crunz utilizes the [symfony/Process](http://symfony.com/doc/current/components/process.html) library for running the tasks in sub-processes.

If the execution of a task lasts until the next interval or even beyond that, we say that the same instances of a task are overlapping. In some cases, this is a not a problem. But there are times, when these tasks are modifying database data or files, which might cause unexpected behaviors, or even data loss.

To prevent critical tasks from overlapping each other, Crunz provides a locking mechanism through `preventOverlapping()` method, which, ensures no task runs if the previous instance is already running. 

```php
<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->preventOverlapping();
 //       
```

By default, crunz uses file based locking (if no parameters are passed to `preventOverlapping`). For alternative lock mechanisms, crunz uses the [symfony/lock](https://symfony.com/doc/current/components/lock.html) component that provides lock mechanisms with various stores. To use this component, you can pass a store to the `preventOverlapping()` method. In the following example, the file based `FlockStore` is used to provide an alternative lock file path.

```php
<?php

use Symfony\Component\Lock\Store\FlockStore;

$store = new FlockStore(__DIR__ . '/locks');
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->preventOverlapping($store);

```

## Keeping the Output

Cron jobs usually have outputs, which is normally emailed to the owner of the crontab file, or the user(s) set by the `MAILTO` environment variable inside the crontab file.

We can also redirect the standard output to a physical file using `>` or `>>` operators:

```bash
* * * * * /command/to/run >> /var/log/crons/cron.log
```

This kind of output logging has been automated in Crunz. To automatically send each event's output to a log file, we can set `log_output` and `output_log_file` options in the configuration file accordingly:

```yaml
# Configuration settings

## ...
log_output:      true
output_log_file: /var/log/crunz.log
## ...
```

This will send the events' output (if executed successfully) to `/var/log/crunz.log` file. However, we need to make sure we are permitted to write to the respective file.

If we need to log the outputs on an event-basis, we can use `appendOutputTo()` or `sendOutputTo()` methods like this:

```php
<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->appendOutputTo('/var/log/crunz/emails.log');

 //       
```

Method `appendOutputTo()` **appends** the output to the specified file. To override the log file with new data after each run, we use `saveOutputTo()` method.

It is also possible to send the errors as emails to a group of recipients by setting `email_output` and `mailer` settings in the configuration file.

## Error Handling

Crunz makes error handling easy by logging and also allowing you add a set of callbacks in case of an error.

## Error Callbacks

You can set as many callbacks as needed to run in case of an error:

```php
<?php

use Crunz\Schedule;

$schedule = new Schedule();

$task = $schedule->run('command/to/run');
$task->everyFiveMinutes();

$schedule
->onError(function() {
   // Send mail
})
->onError(function() {
   // Do something else
});

return $schedule;
```

If there's an error the two defined callbacks will be executed.

## Error Logging

To log the possible errors during each run, we can set `log_error` and `error_log_file` settings in the configuration file as below:

```yaml
# Configuration settings

# ...
log_errors:      true
errors_log_file: /var/log/error.log
# ...
```

As a result, if the execution of an event is unsuccessful for some reasons, the error message is appended to the specified error log file. Each entry provides useful information including the time it happened, the event description,  the executed command which caused the error, and the error message itself.

It is also possible to send the errors as emails to a group of recipients by setting `email_error` and `mailer` settings in the configuration file.

## Custom logger

To use your own logger create class implementing `\Crunz\Application\Service\LoggerFactoryInterface`, for example:

```php
<?php

namespace Vendor\Package;

use Crunz\Application\Service\ConfigurationInterface;
use Crunz\Application\Service\LoggerFactoryInterface;
use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;

final class MyEchoLoggerFactory implements LoggerFactoryInterface
{
    public function create(ConfigurationInterface $configuration): LoggerInterface
    {
        return new class extends AbstractLogger {
            /** @inheritDoc */
            public function log(
                $level,
                $message,
                array $context = array()
            ) {
                echo "crunz.{$level}: {$message}";   
            }
        };
    }
}
```

then use this class name in config: 

```yaml
# ./crunz.yml file
 
logger_factory: 'Vendor\Package\MyEchoLoggerFactory'
```

Done.

## Pre-Process and Post-Process Hooks

There are times when we want to do some kind of operations before and after an event. This is possible by attaching pre-process and post-process callbacks to the respective event.

To do this, we use `before()` and `after()` on both `Event` and `Schedule` objects, meaning we can have pre and post hooks on an event-basis as well as schedule basis. The hooks bind to schedule will run before all events, and after all the events are finished.

```php
<?php

use Crunz\Schedule;

$schedule = new Schedule();

$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->before(function() { 
        // Do something before the task runs
    })
    ->before(function() { 
        // Do something else
    })
    ->after(function() {
        // After the task is run
    });
 
$schedule
    ->before(function () {
       // Do something before all events
    })
    ->after(function () {
       // Do something after all events are finished
    })
    ->before(function () {
       // Do something before all events
    });
```

> We might need to use these methods as many times we need by chaining them.

Post-execution callbacks are only called if the execution of the event has been successful.

## Other Useful Commands

We've already used a few of `crunz` commands like `schedule:run` and `publish:config`. 

To see all the valid options and arguments of `crunz`, we can run the following command:

```bash
vendor/bin/crunz --help
```

### Listing Tasks

One of these commands is `crunz schedule:list`, which lists the defined tasks (in collected `*.Tasks.php` files) in a tabular format.

```text
vendor/bin/crunz schedule:list

+---+---------------+-------------+--------------------+
| # | Task          | Expression  | Command to Run     |
+---+---------------+-------------+--------------------+
| 1 | Sample Task   | * * * * 1 * | command/to/execute |
+---+---------------+-------------+--------------------+
```

By default, list is in text format, but format can be changed by `--format` option.

List in `json` format, command:
```bash
vendor/bin/crunz schedule:list --format json
```

will output:

```json
[
    {
        "number": 1,
        "task": "Sample Task",
        "expression": "* * * * 1",
        "command": "command/to/execute"
    }
]
```

### Force run

While in development it may be useful to force run all tasks regardless of their actual run time,
which can be achieved by adding `--force` to `schedule:run`:

```bash
vendor/bin/crunz schedule:run --force
```

To force run a single task, use the schedule:list command above to determine the Task number and run as follows:

```bash
vendor/bin/crunz schedule:run --task 1 --force
```

### Generating Tasks

There is also a useful command named `make:task`, which generates a task file skeleton with all the defaults, so we won't have to write them from scratch. We can modify the output file later based on our requirements. 

For example, to create a task, which runs `/var/www/script.php` every hour on Mondays, we run the following command:

```text
vendor/bin/crunz make:task exampleOne --run scripts.php --in /var/www --frequency everyHour --constraint mondays
Where do you want to save the file? (Press enter for the current directory)
```

When we run this command, Crunz will ask about the location we want to save the file. By default, it is our source tasks directory.

As a result, the event is defined in a file named `exampleOneTasks.php` within the specified tasks directory.

To see if the event has been created successfully, we list the events:

```text
crunz schedule:list

+---+------------------+-------------+----------------+
| # | Task             | Expression  | Command to Run |
+---+------------------+-------------+----------------+
| 1 | Task description | 0 * * * 1 * | scripts.php    |
+---+------------------+-------------+----------------+
```

To see all the options of `make:task` command with all the defaults, we run this:

```bash
vendor/bin/crunz make:task --help
```

### Debugging tasks

To show basic information about task run:

```bash
vendor/bin/crunz task:debug 1
```

Above command should output something like this:

```text
+----------------------+-----------------------------------+
| Debug information for task '1'                           |
+----------------------+-----------------------------------+
| Command to run       | php -v                            |
| Description          | Inner task                        |
| Prevent overlapping  | No                                |
+----------------------+-----------------------------------+
| Cron expression      | * * * * *                         |
| Comparisons timezone | Europe/Warsaw (from config)       |
+----------------------+-----------------------------------+
| Example run dates                                        |
| #1                   | 2020-03-08 09:27:00 Europe/Warsaw |
| #2                   | 2020-03-08 09:28:00 Europe/Warsaw |
| #3                   | 2020-03-08 09:29:00 Europe/Warsaw |
| #4                   | 2020-03-08 09:30:00 Europe/Warsaw |
| #5                   | 2020-03-08 09:31:00 Europe/Warsaw |
+----------------------+-----------------------------------+
```

## Configuration

There are a few configuration options provided by Crunz in YAML format. To modify the configuration settings, it is highly recommended to have your own copy of the configuration file, instead of modifying the original one. 

To create a copy of the configuration file, first we need to publish the configuration file:

```bash
/project/vendor/bin/crunz publish:config
The configuration file was generated successfully
```

As a result, a copy of the configuration file will be created within our project's root directory.

 The configuration file looks like this:

```yaml
# Crunz Configuration Settings

# This option defines where the task files and
# directories reside.
# The path is relative to the project's root directory,
# where the Crunz is installed (Trailing slashes will be ignored).
source: tasks

# The suffix is meant to target the task files inside the ":source" directory.
# Please note if you change this value, you need
# to make sure all the existing tasks files are renamed accordingly.
suffix: Tasks.php

# Timezone is used to calculate task run time
# This option is very important and not setting it is deprecated
# and will result in exception in 2.0 version.
timezone: ~

# This option define which timezone should be used for log files
# If false, system default timezone will be used
# If true, the timezone in config file that is used to calculate task run time will be used
timezone_log: false

# By default the errors are not logged by Crunz
# You may set the value to true for logging the errors
log_errors: false

# This is the absolute path to the errors' log file
# You need to make sure you have the required permission to write to this file though.
errors_log_file:

# By default the output is not logged as they are redirected to the
# null output.
# Set this to true if you want to keep the outputs
log_output: false

# This is the absolute path to the global output log file
# The events which have dedicated log files (defined with them), won't be
# logged to this file though.
output_log_file:

# By default line breaks in logs aren't allowed.
# Set the value to true to allow them.
log_allow_line_breaks: false

# By default empty context arrays are shown in the log.
# Set the value to true to remove them.
log_ignore_empty_context: false

# This option determines whether the output should be emailed or not.
email_output: false

# This option determines whether the error messages should be emailed or not.
email_errors: false

# Global Swift Mailer settings
#
mailer:
    # Possible values: smtp, mail, and sendmail
    transport: smtp
    recipients:
    sender_name:
    sender_email:


# SMTP settings
#
smtp:
    host:
    port:
    username:
    password:
    encryption:
```

As you can see there are a few options like `source` which is used to specify the source tasks directory. The other options are used for error/output logging/emailing purposes.

Each time we run Crunz commands, it will look into the project's root directory to see if there's any user-modified configuration file. If the configuration file doesn't exists, it will use the one shipped with the package.

## Setting the base cache directory

You can change the base cache directory of crunz by setting the `CRUNZ_BASE_CACHE_DIR` environment variable.
The default base cache directory is `\sys_get_temp_dir()`. The subdirectory `.crunz` is always added to the base cache directory.

## Development ENV flags

The following environment flags should be used only while in development.
Typical end-users do not need to, and should not, change them.

### `CRUNZ_CONTAINER_DEBUG`

Flag used to enable/disable container debug mode, useful only for development.
Enabled by default in `docker-compose`.

### `CRUNZ_DEPRECATION_HANDLER`

Flag used to enable/disable Crunz deprecation handler, useful only for integration tests.
Disabled by default for tests.

## Sponsors

[![Blakfire.io logo](resources/docs/blackfire-logo.png)](https://www.blackfire.io/?utm_source=crunz&utm_medium=readme&utm_campaign=free-open-source)

## Support

You can support further Crunz development by [GitHub](https://github.com/sponsors/PabloKowalczyk). 

## Contributing

### Which branch should I choose?

Bug fixes and readme changes should target `3.9`, new features should target `3.10`.

## If You Need Help

Please submit all issues and questions using GitHub issues and I will try to help you.


## Credits

* [PabloKowalczyk](https://github.com/PabloKowalczyk)
* [Reza Lavarian](https://github.com/lavary)
* [All Contributors](https://github.com/crunzphp/crunz/graphs/contributors)

## License
Crunz is free software distributed under the terms of the MIT license.


================================================
FILE: UPGRADE.md
================================================
# Upgrading from v3.2 to v3.3

## Pass only string parameters to `\Crunz\Schedule::run`

Convert this:
```php
$schedule = new Schedule();
$schedule->run('php', ['-v' => true, 2]);
```

into this:
```php
$schedule = new Schedule();
$schedule->run('php', ['-v' => '1', '2']);
```

# Upgrading from v1.12 to v2.0

## Stop using `mail` transport for mailer

As of `v6.0` SwiftMailer dropped support for `mail` transport,
so `Crunz` `v2.0` won't support it either,
please use `smtp` or `sendmail` transport.

# Upgrading from v1.11 to v1.12

## Always return `\Crunz\Schedule` from task files

Example of wrong task file:

```php
<?php

return [];
```

Example of correct task file:
```php
<?php

use Crunz\Schedule;

$scheduler = new Schedule();

$scheduler
    ->run('php -v')
    ->description('PHP version')
    ->everyMinute();

// Crunz\Schedule instance returned
return $scheduler;
```

## Stop using `\Crunz\Event::setProcess`

If you, for some reason, use above method you should stop it.
This method was intended to be `private` and will be in `v2.0`,
which will lead to exception if you call it.

Example of wrong usage

```php
<?php

use Crunz\Schedule;

$process = new \Symfony\Component\Process\Process('php -i');
$scheduler = new Schedule();
$task = $scheduler->run('php -v');
$task
    // setProcess is deprecated
    ->setProcess($process)
    ->description('PHP version')
    ->everyMinute()
;

return $scheduler;
``` 

# Upgrading from v1.10 to v1.11

## Run `Crunz` in directory with your `crunz.yml`

Searching for Crunz's config is now related to `cwd`, not to `vendor/bin/crunz`.

For example, if your `crunz.yml` is in `/var/www/project/crunz.yml`, then run Crunz with `cd` first:
```bash
cd /var/www/project && vendor/bin/crunz schedule:list
```

Cron job also should be changed:
```bash
* * * * * cd /var/www/project && vendor/bin/crunz schedule:run
```

# Upgrading from v1.9 to v1.10

### Do not pass more than five parts to `Crunz\Event::cron()`

Example correct call:
```yaml
$event = new Crunz\Event;
$event->cron('0 * * * *');
```

# Upgrading from v1.7 to v1.8

### Add `timezone` to your `crunz.yml`

Example config file:
```yaml
source: tasks
suffix: Tasks.php
timezone: Europe/Warsaw
```


================================================
FILE: bootstrap.php
================================================
<?php

require_once __DIR__ . '/vendor/autoload.php';

if (!\defined('IS_WINDOWS')) {
    \define('IS_WINDOWS', PHP_OS_FAMILY === "Windows");
}

// Disable deprecation helper
$envFlags = new \Crunz\EnvFlags\EnvFlags();
$envFlags->disableDeprecationHandler();

// Make sure current working directory is "tests"
$filesystem = new \Crunz\Filesystem\Filesystem();
if (\str_contains($filesystem->getCwd(), 'tests')) {
    return;
}

if (!\chdir('tests')) {
    throw new RuntimeException("Unable to change current directory to 'tests'.");
}


================================================
FILE: composer.json
================================================
{
    "name": "crunzphp/crunz",
    "description": "Schedule your tasks right from the code.",
    "license": "MIT",
    "type": "library",
    "keywords": [
        "scheduler",
        "cron jobs",
        "cron",
        "Task Scheduler",
        "PHP Task Scheduler",
        "Job Scheduler",
        "Job Manager",
        "Event Runner"
    ],
    "authors": [
        {
            "name": "Reza M. Lavaryan",
            "email": "mrl.8081@gmail.com"
        },
        {
            "name": "PabloKowalczyk",
            "homepage": "https://github.com/PabloKowalczyk",
            "role": "Developer"
        }
    ],
    "homepage": "https://github.com/crunzphp/crunz",
    "support": {
        "issues": "https://github.com/crunzphp/crunz/issues"
    },
    "funding": [
        {
            "type": "github",
            "url": "https://github.com/sponsors/PabloKowalczyk"
        }
    ],
    "require": {
        "php": ">=8.2",
        "composer-runtime-api": "^2.0",
        "dragonmantank/cron-expression": "^3.4.0",
        "laravel/serializable-closure": "^2.0",
        "psr/log": "^2.0 || ^3.0",
        "symfony/config": "^6.4.25 || ^7.4.0 || ^8.0.0",
        "symfony/console": "^6.4.25 || ^7.4.0 || ^8.0.0",
        "symfony/dependency-injection": "^6.4.25 || ^7.4.0 || ^8.0.0",
        "symfony/filesystem": "^6.4.25 || ^7.4.0 || ^8.0.0",
        "symfony/lock": "^6.4.25 || ^7.4.0 || ^8.0.0",
        "symfony/mailer": "^6.4.25 || ^7.4.0 || ^8.0.0",
        "symfony/process": "^6.4.25 || ^7.4.0 || ^8.0.0",
        "symfony/string": "^6.4.25 || ^7.4.0 || ^8.0.0",
        "symfony/yaml": "^6.4.25 || ^7.4.0 || ^8.0.0"
    },
    "require-dev": {
        "ext-json": "*",
        "ext-mbstring": "*",
        "ergebnis/composer-normalize": "2.28.3",
        "friendsofphp/php-cs-fixer": "3.90.0",
        "phpstan/phpstan": "2.0.2",
        "phpstan/phpstan-phpunit": "2.0.1",
        "phpstan/phpstan-strict-rules": "2.0.0",
        "phpunit/phpunit": "10.5.63",
        "symfony/error-handler": "^6.4.25 || ^7.4.0 || ^8.0.0",
        "symfony/phpunit-bridge": "^6.4.25 || ^7.4.0 || ^8.0.0"
    },
    "conflict": {
        "laravel/serializable-closure": ">=2.0.9,<2.0.11"
    },
    "minimum-stability": "beta",
    "prefer-stable": true,
    "autoload": {
        "psr-4": {
            "Crunz\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Crunz\\Tests\\": "tests/"
        }
    },
    "bin": [
        "crunz"
    ],
    "config": {
        "allow-plugins": {
            "ergebnis/composer-normalize": true
        },
        "sort-packages": true
    },
    "scripts": {
        "crunz:analyze": [
            "@php vendor/bin/php-cs-fixer fix --diff --dry-run -v",
            "@phpstan:check"
        ],
        "crunz:cs-fix": "@php vendor/bin/php-cs-fixer fix --diff -v --ansi",
        "phpstan:check": "@php vendor/bin/phpstan analyse -c phpstan.neon src tests crunz config bootstrap.php"
    }
}


================================================
FILE: config/services.php
================================================
<?php

declare(strict_types=1);

use Crunz\Application\Cron\CronExpressionFactoryInterface;
use Crunz\Application\Query\TaskInformation\TaskInformationHandler;
use Crunz\Application\Service\ClosureSerializerInterface;
use Crunz\Application\Service\ConfigurationInterface;
use Crunz\Clock\Clock;
use Crunz\Clock\ClockInterface;
use Crunz\Configuration\Configuration;
use Crunz\Configuration\ConfigurationParser;
use Crunz\Configuration\ConfigurationParserInterface;
use Crunz\Configuration\Definition;
use Crunz\Configuration\FileParser;
use Crunz\Console\Command\ConfigGeneratorCommand;
use Crunz\Console\Command\ScheduleListCommand;
use Crunz\Console\Command\ScheduleRunCommand;
use Crunz\Console\Command\TaskGeneratorCommand;
use Crunz\EventRunner;
use Crunz\Filesystem\Filesystem as CrunzFilesystem;
use Crunz\Filesystem\FilesystemInterface;
use Crunz\Finder\Finder;
use Crunz\Finder\FinderInterface;
use Crunz\HttpClient\CurlHttpClient;
use Crunz\HttpClient\FallbackHttpClient;
use Crunz\HttpClient\HttpClientInterface;
use Crunz\HttpClient\HttpClientLoggerDecorator;
use Crunz\HttpClient\StreamHttpClient;
use Crunz\Infrastructure\Dragonmantank\CronExpression\DragonmantankCronExpressionFactory;
use Crunz\Infrastructure\Laravel\LaravelClosureSerializer;
use Crunz\Invoker;
use Crunz\Logger\ConsoleLogger;
use Crunz\Logger\ConsoleLoggerInterface;
use Crunz\Logger\LoggerFactory;
use Crunz\Mailer;
use Crunz\Output\OutputFactory;
use Crunz\Schedule\ScheduleFactory;
use Crunz\Task\Collection;
use Crunz\Task\CollectionInterface;
use Crunz\Task\Loader;
use Crunz\Task\LoaderInterface;
use Crunz\Task\Timezone;
use Crunz\Timezone\Provider;
use Crunz\Timezone\ProviderInterface;
use Crunz\UserInterface\Cli\ClosureRunCommand;
use Crunz\UserInterface\Cli\DebugTaskCommand;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Yaml\Yaml;

$simpleServices = [
    Definition::class,
    Yaml::class,
    Processor::class,
    Invoker::class,
    ProviderInterface::class => Provider::class,
    Filesystem::class,
    ScheduleFactory::class,
    StreamHttpClient::class,
    CurlHttpClient::class,
    FilesystemInterface::class => CrunzFilesystem::class,
    FinderInterface::class => Finder::class,
    LoaderInterface::class => Loader::class,
    CronExpressionFactoryInterface::class => DragonmantankCronExpressionFactory::class,
    ClosureSerializerInterface::class => LaravelClosureSerializer::class,
    ClockInterface::class => Clock::class,
];

/* @var ContainerBuilder $container */

$container
    ->register(ScheduleRunCommand::class, ScheduleRunCommand::class)
    ->setPublic(true)
    ->setArguments(
        [
            new Reference(CollectionInterface::class),
            new Reference(ConfigurationInterface::class),
            new Reference(EventRunner::class),
            new Reference(Timezone::class),
            new Reference(ScheduleFactory::class),
            new Reference(LoaderInterface::class),
        ]
    )
;
$container
    ->register(ClosureRunCommand::class, ClosureRunCommand::class)
    ->setArguments(
        [
            new Reference(ClosureSerializerInterface::class),
        ]
    )
    ->setPublic(true)
;
$container
    ->register(ConfigGeneratorCommand::class, ConfigGeneratorCommand::class)
    ->setPublic(true)
    ->setArguments(
        [
            new Reference(ProviderInterface::class),
            new Reference(Filesystem::class),
            new Reference(FilesystemInterface::class),
        ]
    )
;
$container
    ->register(ScheduleListCommand::class, ScheduleListCommand::class)
    ->setPublic(true)
    ->setArguments(
        [
            new Reference(ConfigurationInterface::class),
            new Reference(CollectionInterface::class),
            new Reference(LoaderInterface::class),
        ]
    )
;
$container
    ->register(TaskGeneratorCommand::class, TaskGeneratorCommand::class)
    ->setPublic(true)
    ->setArguments(
        [
            new Reference(ConfigurationInterface::class),
            new Reference(FilesystemInterface::class),
        ]
    )
;
$container
    ->register(DebugTaskCommand::class, DebugTaskCommand::class)
    ->setPublic(true)
    ->setArguments(
        [
            new Reference(TaskInformationHandler::class),
        ]
    )
;
$container
    ->register(CollectionInterface::class, Collection::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(ConfigurationInterface::class),
            new Reference(FinderInterface::class),
            new Reference(ConsoleLoggerInterface::class),
        ]
    )
;
$container
    ->register(FileParser::class, FileParser::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(Yaml::class),
        ]
    )
;
$container
    ->register(ConfigurationInterface::class, Configuration::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(ConfigurationParserInterface::class),
            new Reference(FilesystemInterface::class),
        ]
    )
;
$container
    ->register(Mailer::class, Mailer::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(ConfigurationInterface::class),
        ]
    )
;
$container
    ->register(LoggerFactory::class, LoggerFactory::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(ConfigurationInterface::class),
            new Reference(Timezone::class),
            new Reference(ConsoleLoggerInterface::class),
            new Reference(ClockInterface::class),
        ]
    )
;
$container
    ->register(EventRunner::class, EventRunner::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(Invoker::class),
            new Reference(ConfigurationInterface::class),
            new Reference(Mailer::class),
            new Reference(LoggerFactory::class),
            new Reference(HttpClientInterface::class),
            new Reference(ConsoleLoggerInterface::class),
        ]
    )
;
$container
    ->register(Timezone::class, Timezone::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(ConfigurationInterface::class),
            new Reference(ConsoleLoggerInterface::class),
        ]
    )
;
$container
    ->register(OutputInterface::class, ConsoleOutput::class)
    ->setPublic(true)
    ->setFactory([new Reference(OutputFactory::class), 'createOutput'])
;
$container
    ->register(OutputFactory::class, OutputFactory::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(InputInterface::class),
        ]
    )
;
$container
    ->register(InputInterface::class, ArgvInput::class)
    ->setPublic(true)
;
$container
    ->register(SymfonyStyle::class, SymfonyStyle::class)
    ->setPublic(true)
    ->setArguments(
        [
            new Reference(InputInterface::class),
            new Reference(OutputInterface::class),
        ]
    )
;
$container
    ->register(ConsoleLoggerInterface::class, ConsoleLogger::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(SymfonyStyle::class),
        ]
    )
;
$container
    ->register(ConsoleLoggerInterface::class, ConsoleLogger::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(SymfonyStyle::class),
        ]
    )
;
$container
    ->register(FallbackHttpClient::class, FallbackHttpClient::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(StreamHttpClient::class),
            new Reference(CurlHttpClient::class),
            new Reference(ConsoleLoggerInterface::class),
        ]
    )
;
$container
    ->register(HttpClientInterface::class, HttpClientLoggerDecorator::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(FallbackHttpClient::class),
            new Reference(ConsoleLoggerInterface::class),
        ]
    )
;
$container
    ->register(ConfigurationParserInterface::class, ConfigurationParser::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(Definition::class),
            new Reference(Processor::class),
            new Reference(FileParser::class),
            new Reference(ConsoleLoggerInterface::class),
            new Reference(FilesystemInterface::class),
        ]
    )
;

$container
    ->register(TaskInformationHandler::class, TaskInformationHandler::class)
    ->setPublic(false)
    ->setArguments(
        [
            new Reference(Timezone::class),
            new Reference(ConfigurationInterface::class),
            new Reference(CollectionInterface::class),
            new Reference(LoaderInterface::class),
            new Reference(ScheduleFactory::class),
            new Reference(CronExpressionFactoryInterface::class),
        ]
    )
;

foreach ($simpleServices as $id => $simpleService) {
    if (!\is_string($id)) {
        $id = $simpleService;
    }

    $container
        ->register($id, $simpleService)
        ->setPublic(false)
    ;
}


================================================
FILE: crunz
================================================
#!/usr/bin/env php
<?php

/*
|--------------------------------------------------------------------------
| Crunz
|--------------------------------------------------------------------------
|
| This file is part of Crunz library.
| (c) Reza M. Lavaryan <mrl.8081@gmail.com>
| For the full copyright and license information, please view the LICENSE
| file that was distributed with this source code.
|
*/

use Composer\InstalledVersions;
use Crunz\Application;

if (!\defined('CRUNZ_BIN')) {
    \define('CRUNZ_BIN', __FILE__);
}

$generatePath = static fn(string ...$parts): string => \implode(DIRECTORY_SEPARATOR, $parts);
$autoloadPaths = [
    // Dependency
    $generatePath(
        \dirname(__DIR__, 2),
        'autoload.php'
    ),
    // Vendor/Bin
    $generatePath(
        \dirname(__DIR__),
        'autoload.php'
    ),
    // Local dev
    $generatePath(
        __DIR__,
        'vendor',
        'autoload.php'
    ),
];
$loadAutoloader = static function () use($autoloadPaths): void {
    foreach ($autoloadPaths as $autoloadPath) {
        if (\file_exists($autoloadPath) === true) {
            require_once $autoloadPath;
            return;
        }
    }

    throw new RuntimeException(
        \sprintf(
            'Unable to find "vendor/autoload.php" in "%s" paths.',
            \implode('", "', $autoloadPaths)
        )
    );
};
$loadAutoloader();

$application = new Application(
    'Crunz Command Line Interface',
    InstalledVersions::getPrettyVersion('crunzphp/crunz') ?? '1.0.x-dev',
);
$application->run();


================================================
FILE: docker/php82/Dockerfile
================================================
FROM php:8.2.30-cli-alpine

RUN apk add --no-cache \
        shadow \
        su-exec && \
    usermod --non-unique --uid 1000 www-data && \
    apk del \
        shadow && \
    docker-php-ext-install -j$(nproc) \
        opcache \
        sysvsem

RUN mkdir -p \
        /var/log/php \
        /var/www/.composer \
    && touch /var/log/php/error.log \
    && chown www-data:www-data \
        /var/log/php/error.log \
        /var/www/.composer

COPY --from=composer/composer:2.9.3-bin /composer /usr/bin/composer
ENV COMPOSER_HOME /var/www/.composer


================================================
FILE: docker/php82/php.ini
================================================
realpath_cache_size = 8192k
realpath_cache_ttl = 6000

expose_php = On
error_log = /var/log/php/error.log
error_reporting = E_ALL
display_errors = On
display_startup_errors = On
log_errors = On
report_memleaks = On

memory_limit = 80M

date.timezone = "UTC"

zend.assertions = 1

opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=80
opcache.interned_strings_buffer=5
opcache.max_accelerated_files=3000
opcache.validate_timestamps=1
opcache.revalidate_freq=0
opcache.save_comments=1
opcache.fast_shutdown=1
opcache.huge_code_pages=1


================================================
FILE: docker-compose.yml
================================================
services:
    php82:
        build:
            context: ./docker/php82
        working_dir: /var/www/html
        environment:
            CRUNZ_CONTAINER_DEBUG: 1
        command: >
            sh -c "
                chown -R www-data:www-data /var/www/.composer && \
                echo 'Logs from /var/log/php/error.log:' && \
                touch /var/log/php/error.log && \
                tail -f /var/log/php/error.log
            "
        volumes:
            - .:/var/www/html
            - ./docker/php82/php.ini:/usr/local/etc/php/php.ini:ro
        stop_grace_period: 1s


================================================
FILE: phpstan-baseline.neon
================================================
parameters:
	ignoreErrors:
		-
			message: "#^Parameter \\#5 \\$timeZone of class Crunz\\\\Application\\\\Query\\\\TaskInformation\\\\TaskInformationView constructor expects DateTimeZone\\|null, mixed given\\.$#"
			count: 1
			path: src/Application/Query/TaskInformation/TaskInformationHandler.php

		-
			message: "#^Variable property access on \\$this\\(Crunz\\\\Application\\\\Query\\\\TaskInformation\\\\TaskInformationHandler\\)\\.$#"
			count: 1
			path: src/Application/Query/TaskInformation/TaskInformationHandler.php

		-
			message: "#^Parameter \\#1 \\$parts of static method Crunz\\\\Path\\\\Path\\:\\:create\\(\\) expects array\\<string\\>, array\\<int, mixed\\> given\\.$#"
			count: 1
			path: src/Configuration/Configuration.php

		-
			message: "#^Method Crunz\\\\Configuration\\\\ConfigurationParser\\:\\:parseConfig\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: src/Configuration/ConfigurationParser.php

		-
			message: "#^Variable \\$cwd on left side of \\?\\? always exists and is not nullable\\.$#"
			count: 1
			path: src/Configuration/ConfigurationParser.php

		-
			message: "#^Method Crunz\\\\Configuration\\\\ConfigurationParserInterface\\:\\:parseConfig\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: src/Configuration/ConfigurationParserInterface.php

		-
			message: "#^Method Crunz\\\\Configuration\\\\FileParser\\:\\:parse\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: src/Configuration/FileParser.php

		-
			message: "#^Method Crunz\\\\Configuration\\\\FileParser\\:\\:parse\\(\\) should return array\\<array\\> but returns array\\<int, mixed\\>\\.$#"
			count: 1
			path: src/Configuration/FileParser.php

		-
			message: "#^Property Crunz\\\\Console\\\\Command\\\\Command\\:\\:\\$arguments type has no value type specified in iterable type array\\.$#"
			count: 1
			path: src/Console/Command/Command.php

		-
			message: "#^Property Crunz\\\\Console\\\\Command\\\\Command\\:\\:\\$options type has no value type specified in iterable type array\\.$#"
			count: 1
			path: src/Console/Command/Command.php

		-
			message: "#^Method Crunz\\\\Console\\\\Command\\\\ConfigGeneratorCommand\\:\\:askForTimezone\\(\\) should return string but returns mixed\\.$#"
			count: 1
			path: src/Console/Command/ConfigGeneratorCommand.php

		-
			message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#"
			count: 1
			path: src/Console/Command/ConfigGeneratorCommand.php

		-
			message: "#^Only booleans are allowed in a negated boolean, int\\<0, max\\> given\\.$#"
			count: 1
			path: src/Console/Command/ScheduleListCommand.php

		-
			message: "#^Only booleans are allowed in a negated boolean, int\\<0, max\\> given\\.$#"
			count: 2
			path: src/Console/Command/ScheduleRunCommand.php

		-
			message: "#^Binary operation \"\\.\" between array\\|string\\|null and mixed results in an error\\.$#"
			count: 1
			path: src/Console/Command/TaskGeneratorCommand.php

		-
			message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:ask\\(\\)\\.$#"
			count: 1
			path: src/Console/Command/TaskGeneratorCommand.php

		-
			message: "#^Method Crunz\\\\Console\\\\Command\\\\TaskGeneratorCommand\\:\\:type\\(\\) should return string but returns array\\|bool\\|float\\|int\\|string\\|null\\.$#"
			count: 1
			path: src/Console/Command/TaskGeneratorCommand.php

		-
			message: "#^Only booleans are allowed in an if condition, string given\\.$#"
			count: 1
			path: src/Console/Command/TaskGeneratorCommand.php

		-
			message: "#^Parameter \\#1 \\$string of function rtrim expects string, array\\|bool\\|float\\|int\\|string\\|null given\\.$#"
			count: 2
			path: src/Console/Command/TaskGeneratorCommand.php

		-
			message: "#^Parameter \\#2 \\$replace of function str_replace expects array\\|string, array\\|bool\\|float\\|int\\|string\\|null given\\.$#"
			count: 3
			path: src/Console/Command/TaskGeneratorCommand.php

		-
			message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, array\\|bool\\|float\\|int\\|string\\|null given\\.$#"
			count: 1
			path: src/Console/Command/TaskGeneratorCommand.php

		-
			message: "#^Return type \\(int\\|null\\) of method Crunz\\\\Console\\\\Command\\\\TaskGeneratorCommand\\:\\:execute\\(\\) should be covariant with return type \\(int\\) of method Symfony\\\\Component\\\\Console\\\\Command\\\\Command\\:\\:execute\\(\\)$#"
			count: 1
			path: src/Console/Command/TaskGeneratorCommand.php

		-
			message: "#^Call to function is_string\\(\\) with string will always evaluate to true\\.$#"
			count: 1
			path: src/Event.php

		-
			message: "#^Cannot cast mixed to string\\.$#"
			count: 1
			path: src/Event.php

		-
			message: "#^Instanceof between Closure and Closure will always evaluate to true\\.$#"
			count: 1
			path: src/Event.php

		-
			message: "#^Instanceof between DateTimeZone and DateTimeZone will always evaluate to true\\.$#"
			count: 1
			path: src/Event.php

		-
			message: "#^Only booleans are allowed in a negated boolean, mixed given\\.$#"
			count: 1
			path: src/Event.php

		-
			message: "#^Only booleans are allowed in an if condition, DateTimeZone\\|string given\\.$#"
			count: 1
			path: src/Event.php

		-
			message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
			count: 2
			path: src/Event.php

		-
			message: "#^Only booleans are allowed in an if condition, string given\\.$#"
			count: 3
			path: src/Event.php

		-
			message: "#^Property Crunz\\\\Event\\:\\:\\$lock \\(Symfony\\\\Component\\\\Lock\\\\Lock\\) does not accept Symfony\\\\Component\\\\Lock\\\\SharedLockInterface\\.$#"
			count: 1
			path: src/Event.php

		-
			message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#"
			count: 1
			path: src/Event.php

		-
			message: "#^Strict comparison using \\=\\=\\= between false and array\\<string, mixed\\> will always evaluate to false\\.$#"
			count: 1
			path: src/Event.php

		-
			message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
			count: 1
			path: src/EventRunner.php

		-
			message: "#^Only booleans are allowed in &&, mixed given on the left side\\.$#"
			count: 2
			path: src/EventRunner.php

		-
			message: "#^Only booleans are allowed in a negated boolean, int\\<0, max\\> given\\.$#"
			count: 1
			path: src/EventRunner.php

		-
			message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
			count: 2
			path: src/EventRunner.php

		-
			message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#"
			count: 1
			path: src/EventRunner.php

		-
			message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(mixed\\)\\: mixed\\)\\|null, Closure\\(array\\)\\: SplFileInfo given\\.$#"
			count: 1
			path: src/Finder/Finder.php

		-
			message: "#^Method Crunz\\\\Infrastructure\\\\Laravel\\\\LaravelClosureSerializer\\:\\:extractWrapper\\(\\) should return Laravel\\\\SerializableClosure\\\\SerializableClosure but returns mixed\\.$#"
			count: 1
			path: src/Infrastructure/Laravel/LaravelClosureSerializer.php

		-
			message: "#^Method Crunz\\\\Infrastructure\\\\Psr\\\\Logger\\\\EnabledLoggerDecorator\\:\\:log\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
			count: 1
			path: src/Infrastructure/Psr/Logger/EnabledLoggerDecorator.php

		-
			message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
			count: 1
			path: src/Infrastructure/Psr/Logger/PsrStreamLogger.php

		-
			message: "#^Method Crunz\\\\Infrastructure\\\\Psr\\\\Logger\\\\PsrStreamLogger\\:\\:log\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
			count: 1
			path: src/Infrastructure/Psr/Logger/PsrStreamLogger.php

		-
			message: "#^Parameter \\#1 \\$message of method Crunz\\\\Infrastructure\\\\Psr\\\\Logger\\\\PsrStreamLogger\\:\\:replaceNewlines\\(\\) expects string, string\\|Stringable given\\.$#"
			count: 1
			path: src/Infrastructure/Psr/Logger/PsrStreamLogger.php

		-
			message: "#^Parameter \\#1 \\$string of function mb_strtoupper expects string, mixed given\\.$#"
			count: 1
			path: src/Infrastructure/Psr/Logger/PsrStreamLogger.php

		-
			message: "#^Parameter \\#3 \\$outputStreamPath of class Crunz\\\\Infrastructure\\\\Psr\\\\Logger\\\\PsrStreamLogger constructor expects string\\|null, mixed given\\.$#"
			count: 1
			path: src/Infrastructure/Psr/Logger/PsrStreamLoggerFactory.php

		-
			message: "#^Parameter \\#4 \\$errorStreamPath of class Crunz\\\\Infrastructure\\\\Psr\\\\Logger\\\\PsrStreamLogger constructor expects string\\|null, mixed given\\.$#"
			count: 1
			path: src/Infrastructure/Psr/Logger/PsrStreamLoggerFactory.php

		-
			message: "#^Parameter \\#5 \\$ignoreEmptyContext of class Crunz\\\\Infrastructure\\\\Psr\\\\Logger\\\\PsrStreamLogger constructor expects bool, mixed given\\.$#"
			count: 1
			path: src/Infrastructure/Psr/Logger/PsrStreamLoggerFactory.php

		-
			message: "#^Parameter \\#6 \\$timezoneLog of class Crunz\\\\Infrastructure\\\\Psr\\\\Logger\\\\PsrStreamLogger constructor expects bool, mixed given\\.$#"
			count: 1
			path: src/Infrastructure/Psr/Logger/PsrStreamLoggerFactory.php

		-
			message: "#^Parameter \\#7 \\$allowLineBreaks of class Crunz\\\\Infrastructure\\\\Psr\\\\Logger\\\\PsrStreamLogger constructor expects bool, mixed given\\.$#"
			count: 1
			path: src/Infrastructure/Psr/Logger/PsrStreamLoggerFactory.php

		-
			message: "#^Method Crunz\\\\Logger\\\\LoggerFactory\\:\\:createLoggerFactory\\(\\) should return Crunz\\\\Application\\\\Service\\\\LoggerFactoryInterface but returns object\\.$#"
			count: 1
			path: src/Logger/LoggerFactory.php

		-
			message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
			count: 1
			path: src/Logger/LoggerFactory.php

		-
			message: "#^Parameter \\#1 \\$class of function class_exists expects string, mixed given\\.$#"
			count: 1
			path: src/Logger/LoggerFactory.php

		-
			message: "#^Part \\$loggerFactoryClass \\(mixed\\) of encapsed string cannot be cast to string\\.$#"
			count: 2
			path: src/Logger/LoggerFactory.php

		-
			message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
			count: 1
			path: src/Mailer.php

		-
			message: "#^Only booleans are allowed in an if condition, Symfony\\\\Component\\\\Mailer\\\\Mailer\\|null given\\.$#"
			count: 1
			path: src/Mailer.php

		-
			message: "#^Parameter \\#1 \\$address of class Symfony\\\\Component\\\\Mime\\\\Address constructor expects string, mixed given\\.$#"
			count: 1
			path: src/Mailer.php

		-
			message: "#^Parameter \\#1 \\.\\.\\.\\$addresses of method Symfony\\\\Component\\\\Mime\\\\Email\\:\\:addTo\\(\\) expects string\\|Symfony\\\\Component\\\\Mime\\\\Address, mixed given\\.$#"
			count: 1
			path: src/Mailer.php

		-
			message: "#^Parameter \\#2 \\$name of class Symfony\\\\Component\\\\Mime\\\\Address constructor expects string, mixed given\\.$#"
			count: 1
			path: src/Mailer.php

		-
			message: "#^Part \\$host \\(mixed\\) of encapsed string cannot be cast to string\\.$#"
			count: 1
			path: src/Mailer.php

		-
			message: "#^Part \\$password \\(mixed\\) of encapsed string cannot be cast to string\\.$#"
			count: 1
			path: src/Mailer.php

		-
			message: "#^Part \\$port \\(mixed\\) of encapsed string cannot be cast to string\\.$#"
			count: 1
			path: src/Mailer.php

		-
			message: "#^Part \\$user \\(mixed\\) of encapsed string cannot be cast to string\\.$#"
			count: 1
			path: src/Mailer.php

		-
			message: "#^Only booleans are allowed in \\|\\|, mixed given on the right side\\.$#"
			count: 1
			path: src/Output/OutputFactory.php

		-
			message: "#^Call to function is_string\\(\\) with string will always evaluate to true\\.$#"
			count: 1
			path: src/Schedule.php

		-
			message: "#^Only booleans are allowed in &&, int\\<0, max\\> given on the right side\\.$#"
			count: 1
			path: src/Schedule.php

		-
			message: "#^Parameter \\#2 \\$suffix of method Crunz\\\\Finder\\\\FinderInterface\\:\\:find\\(\\) expects string, mixed given\\.$#"
			count: 1
			path: src/Task/Collection.php

		-
			message: "#^Part \\$suffix \\(mixed\\) of encapsed string cannot be cast to string\\.$#"
			count: 1
			path: src/Task/Collection.php

		-
			message: "#^Call to function is_string\\(\\) with string will always evaluate to true\\.$#"
			count: 1
			path: src/Task/TaskNumber.php

		-
			message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
			count: 1
			path: src/Task/Timezone.php

		-
			message: "#^Parameter \\#1 \\$timezone of class DateTimeZone constructor expects string, mixed given\\.$#"
			count: 1
			path: src/Task/Timezone.php

		-
			message: "#^Part \\$newTimezone \\(mixed\\) of encapsed string cannot be cast to string\\.$#"
			count: 2
			path: src/Task/Timezone.php

		-
			message: "#^Return type \\(int\\|null\\) of method Crunz\\\\UserInterface\\\\Cli\\\\ClosureRunCommand\\:\\:execute\\(\\) should be covariant with return type \\(int\\) of method Symfony\\\\Component\\\\Console\\\\Command\\\\Command\\:\\:execute\\(\\)$#"
			count: 1
			path: src/UserInterface/Cli/ClosureRunCommand.php

		-
			message: "#^Method Crunz\\\\Tests\\\\EndToEnd\\\\WrongTaskTest\\:\\:scheduleInstanceProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/EndToEnd/WrongTaskTest.php

		-
			message: "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:setInputStream\\(\\)\\.$#"
			count: 1
			path: tests/Functional/TaskGeneratorTest.php

		-
			message: "#^Call to function method_exists\\(\\) with Symfony\\\\Component\\\\Console\\\\Tester\\\\CommandTester and 'setInputs' will always evaluate to true\\.$#"
			count: 1
			path: tests/Functional/TaskGeneratorTest.php

		-
			message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
			count: 2
			path: tests/TestCase/EndToEnd/Environment/Environment.php

		-
			message: "#^Casting to string something that's already string\\.$#"
			count: 1
			path: tests/TestCase/EndToEndTestCase.php

		-
			message: "#^Cannot cast mixed to string\\.$#"
			count: 1
			path: tests/TestCase/FakeConfiguration.php

		-
			message: "#^Method Crunz\\\\Tests\\\\TestCase\\\\FakeConfiguration\\:\\:__construct\\(\\) has parameter \\$config with no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/TestCase/FakeConfiguration.php

		-
			message: "#^Property Crunz\\\\Tests\\\\TestCase\\\\FakeConfiguration\\:\\:\\$config \\(array\\<int\\|string, array\\|bool\\|string\\|null\\>\\) does not accept array\\<int\\|string, mixed\\>\\.$#"
			count: 1
			path: tests/TestCase/FakeConfiguration.php

		-
			message: "#^Property Crunz\\\\Tests\\\\TestCase\\\\FakeConfiguration\\:\\:\\$config type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/TestCase/FakeConfiguration.php

		-
			message: "#^Parameter \\#1 \\$timezone of class DateTimeZone constructor expects string, mixed given\\.$#"
			count: 1
			path: tests/TestCase/Faker.php

		-
			message: "#^Method Crunz\\\\Tests\\\\TestCase\\\\Logger\\\\SpyPsrLogger\\:\\:getLogs\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/TestCase/Logger/SpyPsrLogger.php

		-
			message: "#^Method Crunz\\\\Tests\\\\TestCase\\\\Logger\\\\SpyPsrLogger\\:\\:log\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/TestCase/Logger/SpyPsrLogger.php

		-
			message: "#^Property Crunz\\\\Tests\\\\TestCase\\\\Logger\\\\SpyPsrLogger\\:\\:\\$logs type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/TestCase/Logger/SpyPsrLogger.php

		-
			message: "#^Offset 'uri' on array\\{timed_out\\: bool, blocked\\: bool, eof\\: bool, unread_bytes\\: int, stream_type\\: string, wrapper_type\\: string, wrapper_data\\: mixed, mode\\: string, \\.\\.\\.\\} on left side of \\?\\? always exists and is not nullable\\.$#"
			count: 1
			path: tests/TestCase/TemporaryFile.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\Application\\\\Cron\\\\AbstractCronExpressionTest\\:\\:multipleRunDatesProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/Application/Cron/AbstractCronExpressionTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\Application\\\\Query\\\\TaskInformation\\\\TaskInformationHandlerTest\\:\\:taskInformationProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/Application/Query/TaskInformation/TaskInformationHandlerTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\Configuration\\\\ConfigurationTest\\:\\:createConfiguration\\(\\) has parameter \\$config with no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/Configuration/ConfigurationTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\EnvFlags\\\\EnvFlagsTest\\:\\:containerDebugProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/EnvFlags/EnvFlagsTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\EnvFlags\\\\EnvFlagsTest\\:\\:statusProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/EnvFlags/EnvFlagsTest.php

		-
			message: "#^Call to method expects\\(\\) on an unknown class Symfony\\\\Component\\\\Lock\\\\StoreInterface\\.$#"
			count: 1
			path: tests/Unit/EventRunnerTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\EventTest\\:\\:deprecatedEveryProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/EventTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\EventTest\\:\\:everyMethodProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/EventTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\EventTest\\:\\:hourlyAtInvalidProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/EventTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\Filesystem\\\\FilesystemTest\\:\\:fileExistsProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/Filesystem/FilesystemTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\Finder\\\\FinderTest\\:\\:tasksProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/Finder/FinderTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\Infrastructure\\\\Psr\\\\Logger\\\\EnabledLoggerDecoratorTest\\:\\:disabledChannelProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/Infrastructure/Psr/Logger/EnabledLoggerDecoratorTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\Infrastructure\\\\Psr\\\\Logger\\\\EnabledLoggerDecoratorTest\\:\\:enabledChannelProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/Infrastructure/Psr/Logger/EnabledLoggerDecoratorTest.php

		-
			message: "#^Parameter \\#1 \\$config of class Crunz\\\\Tests\\\\TestCase\\\\FakeConfiguration constructor expects array\\<int\\|string, array\\|bool\\|string\\|null\\>, array\\<string, mixed\\> given\\.$#"
			count: 1
			path: tests/Unit/Logger/LoggerFactoryTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\Output\\\\OutputFactoryTest\\:\\:inputProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/Output/OutputFactoryTest.php

		-
			message: "#^Call to function is_string\\(\\) with string will always evaluate to true\\.$#"
			count: 1
			path: tests/Unit/Pingable.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\Pinger\\\\PingableTest\\:\\:nonStringProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/Pinger/PingableTest.php

		-
			message: "#^Parameter \\#1 \\$url of method Crunz\\\\Tests\\\\Unit\\\\Pingable\\:\\:pingBefore\\(\\) expects string, mixed given\\.$#"
			count: 1
			path: tests/Unit/Pinger/PingableTest.php

		-
			message: "#^Parameter \\#1 \\$url of method Crunz\\\\Tests\\\\Unit\\\\Pingable\\:\\:thenPing\\(\\) expects string, mixed given\\.$#"
			count: 1
			path: tests/Unit/Pinger/PingableTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\Task\\\\TaskNumberTest\\:\\:nonNumericProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/Task/TaskNumberTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\Task\\\\TaskNumberTest\\:\\:nonStringValueProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/Task/TaskNumberTest.php

		-
			message: "#^Method Crunz\\\\Tests\\\\Unit\\\\Task\\\\TaskNumberTest\\:\\:numericValueProvider\\(\\) return type has no value type specified in iterable type array\\.$#"
			count: 1
			path: tests/Unit/Task/TaskNumberTest.php

		-
			message: "#^Parameter \\#1 \\$value of static method Crunz\\\\Task\\\\TaskNumber\\:\\:fromString\\(\\) expects string, mixed given\\.$#"
			count: 1
			path: tests/Unit/Task/TaskNumberTest.php


================================================
FILE: phpstan.neon
================================================
parameters:
    level: 9
    reportUnmatchedIgnoredErrors: false
    inferPrivatePropertyTypeFromConstructor: true
    ignoreErrors:
        -
            message: '#Variable \$container might not be defined#'
            path: config/services.php
        -
            message: '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::children\(\)#'
            path: src/Configuration/Definition.php
        -
            message: '#Variable \$configFile might not be defined#'
            path: src/Configuration/ConfigurationParser.php
        -
            message: '#Call to an undefined method Crunz\\Event::DummyFrequency\(\)#'
            path: src/Stubs/BasicTask.php
        -
            message: '#Parameter \#1 \$command of static method Symfony\\Component\\Process\\Process::fromShellCommandline\(\) expects string#'
            path: src/Process/Process.php
        -
            message: '#Result of#'
            path: src/Event.php
        -
            message: '#Parameter \#1 \$store of class#'
            path: src/Event.php
        -
            message: '#CrunzContainer#'
            path: src/Application.php
        -
            message: '#Parameter \#2 \$currentTime#'
            path: src/Infrastructure/Dragonmantank/CronExpression/DragonmantankCronExpression.php

includes:
    - phpstan-baseline.neon
    - vendor/phpstan/phpstan-phpunit/extension.neon
    - vendor/phpstan/phpstan-phpunit/rules.neon
    - vendor/phpstan/phpstan-strict-rules/rules.neon


================================================
FILE: phpunit.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
    bootstrap="bootstrap.php"
    backupGlobals="false"
    beStrictAboutCoversAnnotation="true"
    beStrictAboutOutputDuringTests="true"
    beStrictAboutTestsThatDoNotTestAnything="true"
    beStrictAboutTodoAnnotatedTests="true"
    verbose="true"
    colors="true"
>
    <coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">src</directory>
        </include>
    </coverage>

    <testsuites>
        <testsuite name="EndToEnd">
            <directory suffix="Test.php">tests/EndToEnd</directory>
        </testsuite>
        <testsuite name="Integration">
            <directory suffix="Test.php">tests/Functional</directory>
        </testsuite>
        <testsuite name="Unit">
            <directory suffix="Test.php">tests/Unit</directory>
        </testsuite>
    </testsuites>

    <listeners>
        <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener"/>
    </listeners>
</phpunit>


================================================
FILE: resources/config/crunz.yml
================================================
# Crunz Configuration Settings

# This option defines where the task files and
# directories reside.
# The path is relative to this config file.
# Trailing slashes will be ignored.
source: tasks

# The suffix is meant to target the task files inside the ":source" directory.
# Please note if you change this value, you need
# to make sure all the existing tasks files are renamed accordingly.
suffix: Tasks.php

# Timezone is used to calculate task run time
# This option is very important and not setting it is deprecated
# and will result in exception in 2.0 version.
timezone: ~

# This option define which timezone should be used for log files
# If false, system default timezone will be used
# If true, the timezone in config file that is used to calculate task run time will be used
timezone_log: false

# By default the errors are not logged by Crunz
# You may set the value to true for logging the errors
log_errors: false

# This is the absolute path to the errors' log file
# You need to make sure you have the required permission to write to this file though.
errors_log_file: ~

# By default the output is not logged as they are redirected to the
# null output.
# Set this to true if you want to keep the outputs
log_output: false

# This is the absolute path to the global output log file
# The events which have dedicated log files (defined with them), won't be
# logged to this file though.
output_log_file: ~

# By default line breaks in logs aren't allowed.
# Set the value to true to allow them.
log_allow_line_breaks: false

# By default empty context arrays are shown in the log.
# Set the value to true to remove them.
log_ignore_empty_context: false

# This option determines whether the output should be emailed or not.
email_output: false

# This option determines whether the error messages should be emailed or not.
email_errors: false

# Global Swift Mailer settings
mailer:
    # Possible values: smtp, mail, and sendmail
    transport: smtp
    recipients:
    sender_name:
    sender_email:


# SMTP settings
smtp:
    host: ~
    port: ~
    username: ~
    password: ~
    encryption: ~


================================================
FILE: src/Application/Cron/CronExpressionFactoryInterface.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Application\Cron;

interface CronExpressionFactoryInterface
{
    public function createFromString(string $cronExpression): CronExpressionInterface;
}


================================================
FILE: src/Application/Cron/CronExpressionInterface.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Application\Cron;

interface CronExpressionInterface
{
    /** @return \DateTimeImmutable[] */
    public function multipleRunDates(
        int $total,
        \DateTimeImmutable $now,
        ?\DateTimeZone $timeZone = null,
    ): array;
}


================================================
FILE: src/Application/Query/TaskInformation/TaskInformation.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Application\Query\TaskInformation;

use Crunz\Task\TaskNumber;

final class TaskInformation
{
    public function __construct(private readonly TaskNumber $taskNumber)
    {
    }

    public function taskNumber(): TaskNumber
    {
        return $this->taskNumber;
    }
}


================================================
FILE: src/Application/Query/TaskInformation/TaskInformationHandler.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Application\Query\TaskInformation;

use Crunz\Application\Cron\CronExpressionFactoryInterface;
use Crunz\Application\Service\ConfigurationInterface;
use Crunz\Event;
use Crunz\Schedule\ScheduleFactory;
use Crunz\Task\CollectionInterface;
use Crunz\Task\LoaderInterface;
use Crunz\Task\Timezone;

final class TaskInformationHandler
{
    public function __construct(
        private readonly Timezone $timezone,
        private readonly ConfigurationInterface $configuration,
        private readonly CollectionInterface $taskCollection,
        private readonly LoaderInterface $taskLoader,
        private readonly ScheduleFactory $scheduleFactory,
        private readonly CronExpressionFactoryInterface $cronExpressionFactory,
    ) {
    }

    public function handle(TaskInformation $taskInformation): TaskInformationView
    {
        $source = $this->configuration
            ->getSourcePath()
        ;
        /** @var \SplFileInfo[] $files */
        $files = $this->taskCollection
            ->all($source)
        ;

        // List of schedules
        $schedules = $this->taskLoader
            ->load(...\array_values($files))
        ;

        $timezoneForComparisons = $this->timezone
            ->timezoneForComparisons()
        ;
        $event = $this->scheduleFactory
            ->singleTask($taskInformation->taskNumber(), ...$schedules)
        ;

        $cronExpression = $this->cronExpressionFactory
            ->createFromString($event->getExpression())
        ;
        $nextRunTimezone = $timezoneForComparisons;
        $eventProperties = $this->getEventProperties($event, ['timezone', 'preventOverlapping']);
        $eventTimezone = $eventProperties['timezone'];
        if (\is_string($eventTimezone)) {
            $eventTimezone = new \DateTimeZone($eventTimezone);
            $nextRunTimezone = $eventTimezone;
        }

        $nextRuns = $cronExpression->multipleRunDates(
            5,
            new \DateTimeImmutable(),
            $nextRunTimezone
        );

        return new TaskInformationView(
            $event->getCommand(),
            $event->description ?? '',
            $event->getExpression(),
            \filter_var($eventProperties['preventOverlapping'] ?? false, FILTER_VALIDATE_BOOLEAN),
            $eventTimezone,
            $timezoneForComparisons,
            ...$nextRuns
        );
    }

    /**
     * @param string[] $properties
     *
     * @return array<string,mixed>
     */
    private function getEventProperties(Event $event, array $properties): array
    {
        $propertiesExtractor = function () use ($properties, $event): array {
            $values = [];
            foreach ($properties as $property) {
                if (!\property_exists($event, $property)) {
                    $class = $event::class;

                    throw new \RuntimeException("Property '{$property}' doesn't exists in '{$class}' class.");
                }

                $values[$property] = $this->{$property};
            }

            return $values;
        };

        return $propertiesExtractor->bindTo($event, Event::class)();
    }
}


================================================
FILE: src/Application/Query/TaskInformation/TaskInformationView.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Application\Query\TaskInformation;

final class TaskInformationView
{
    /** @var \DateTimeImmutable[] */
    private readonly array $nextRuns;

    public function __construct(
        private readonly string|object $command,
        private readonly string $description,
        private readonly string $cronExpression,
        private readonly bool $preventOverlapping,
        private readonly ?\DateTimeZone $timeZone,
        private readonly \DateTimeZone $configTimeZone,
        \DateTimeImmutable ...$nextRuns,
    ) {
        $this->nextRuns = $nextRuns;
    }

    public function command(): string|object
    {
        return $this->command;
    }

    public function description(): string
    {
        return $this->description;
    }

    public function cronExpression(): string
    {
        return $this->cronExpression;
    }

    public function timeZone(): ?\DateTimeZone
    {
        return $this->timeZone;
    }

    public function configTimeZone(): \DateTimeZone
    {
        return $this->configTimeZone;
    }

    /** @return \DateTimeImmutable[] */
    public function nextRuns(): array
    {
        return $this->nextRuns;
    }

    public function preventOverlapping(): bool
    {
        return $this->preventOverlapping;
    }
}


================================================
FILE: src/Application/Service/ClosureSerializerInterface.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Application\Service;

interface ClosureSerializerInterface
{
    public function serialize(\Closure $closure): string;

    public function unserialize(string $serializedClosure): \Closure;

    public function closureCode(\Closure $closure): string;
}


================================================
FILE: src/Application/Service/ConfigurationInterface.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Application\Service;

interface ConfigurationInterface
{
    /**
     * Return a parameter based on a key.
     */
    public function get(string $key, mixed $default = null): mixed;

    /**
     * Set a parameter based on a key.
     */
    public function withNewEntry(string $key, mixed $value): ConfigurationInterface;

    public function getSourcePath(): string;
}


================================================
FILE: src/Application/Service/LoggerFactoryInterface.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Application\Service;

use Psr\Log\LoggerInterface;

/**
 * @experimental
 */
interface LoggerFactoryInterface
{
    public function create(ConfigurationInterface $configuration): LoggerInterface;
}


================================================
FILE: src/Application.php
================================================
<?php

declare(strict_types=1);

namespace Crunz;

use Crunz\CacheDirectoryFactory\CacheDirectoryFactory;
use Crunz\Console\Command\ConfigGeneratorCommand;
use Crunz\Console\Command\ScheduleListCommand;
use Crunz\Console\Command\ScheduleRunCommand;
use Crunz\Console\Command\TaskGeneratorCommand;
use Crunz\EnvFlags\EnvFlags;
use Crunz\Path\Path;
use Crunz\UserInterface\Cli\DebugTaskCommand;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;

class Application extends SymfonyApplication
{
    /**
     * List of commands to register.
     *
     * @var class-string[]
     */
    private const COMMANDS = [
        // This command starts the event runner (vendor/bin/crunz schedule:run)
        // It takes an optional argument which is the source directory for tasks
        // If the argument is not provided, the default in the configuratrion file
        // will be considered as the source path
        ScheduleRunCommand::class,

        // This command (vendor/bin/schedule:list) lists the scheduled events in different task files
        // Just like schedule:run it gets the :source argument
        ScheduleListCommand::class,

        // This command generates a task from the command-line
        // This is often useful when you want to create a task file and start
        // adding tasks to it.
        TaskGeneratorCommand::class,

        // The modify the configuration, the user's own copy should be modified
        // This command creates a configuration file in Crunz installation directory
        ConfigGeneratorCommand::class,

        // This command is used by Crunz itself for running serialized closures
        // It accepts an argument which is the serialized form of the closure to run.
        UserInterface\Cli\ClosureRunCommand::class,

        // Debug task command
        DebugTaskCommand::class,
    ];

    private Container $container;
    private readonly EnvFlags $envFlags;
    private readonly CacheDirectoryFactory $cacheDirectoryFactory;

    public function __construct(string $appName, string $appVersion)
    {
        parent::__construct($appName, $appVersion);

        $this->cacheDirectoryFactory = new CacheDirectoryFactory();
        $this->envFlags = new EnvFlags();

        $this->initializeContainer();
        $this->registerDeprecationHandler();

        foreach (self::COMMANDS as $commandClass) {
            /** @var Command $command */
            $command = $this->container
                ->get($commandClass)
            ;

            // @phpstan-ignore function.alreadyNarrowedType (backward compatibility with Symfony < 7.4)
            if (\method_exists($this, 'addCommand')) {
                $this->addCommand($command);
            } else {
                $this->add($command);
            }
        }
    }

    public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
    {
        if (null === $output) {
            /** @var OutputInterface $outputObject */
            $outputObject = $this->container
                ->get(OutputInterface::class);

            $output = $outputObject;
        }

        if (null === $input) {
            /** @var InputInterface $inputObject */
            $inputObject = $this->container
                ->get(InputInterface::class);

            $input = $inputObject;
        }

        return parent::run($input, $output);
    }

    private function initializeContainer(): void
    {
        $containerCacheDirWritable = $this->createBaseCacheDirectory();
        $isContainerDebugEnabled = $this->envFlags
            ->isContainerDebugEnabled();

        if ($containerCacheDirWritable) {
            $class = 'CrunzContainer';
            $baseClass = 'Container';
            $cachePath = Path::create(
                [
                    $this->getContainerCacheDir(),
                    "{$class}.php",
                ]
            );
            $cache = new ConfigCache($cachePath->toString(), $isContainerDebugEnabled);

            if (!$cache->isFresh()) {
                $containerBuilder = $this->buildContainer();
                $containerBuilder->compile();

                $this->dumpContainer(
                    $cache,
                    $containerBuilder,
                    $class,
                    $baseClass
                );
            }

            require_once $cache->getPath();

            $this->container = new $class();

            return;
        }

        $containerBuilder = $this->buildContainer();
        $containerBuilder->compile();

        $this->container = $containerBuilder;
    }

    /**
     * @return ContainerBuilder
     *
     * @throws \Exception
     */
    private function buildContainer()
    {
        $containerBuilder = new ContainerBuilder();
        $configDir = Path::create(
            [
                __DIR__,
                '..',
                'config',
            ]
        );

        $phpLoader = new PhpFileLoader($containerBuilder, new FileLocator($configDir->toString()));
        $phpLoader->load('services.php');

        return $containerBuilder;
    }

    private function dumpContainer(
        ConfigCache $cache,
        ContainerBuilder $container,
        string $class,
        string $baseClass,
    ): void {
        $dumper = new PhpDumper($container);

        /** @var string $content */
        $content = $dumper->dump(
            [
                'class' => $class,
                'base_class' => $baseClass,
                'file' => $cache->getPath(),
            ]
        );

        $cache->write($content, $container->getResources());
    }

    /**
     * @return bool
     */
    private function createBaseCacheDirectory()
    {
        $baseCacheDir = $this->getBaseCacheDir();

        if (!\is_dir($baseCacheDir)) {
            $makeDirResult = \mkdir(
                $this->getBaseCacheDir(),
                0777,
                true
            );

            return $makeDirResult
                && \is_dir($baseCacheDir)
                && \is_writable($baseCacheDir)
            ;
        }

        return \is_writable($baseCacheDir);
    }

    private function getBaseCacheDir(): string
    {
        return $this->cacheDirectoryFactory->generate()->toString();
    }

    /**
     * @return string
     */
    private function getContainerCacheDir()
    {
        $containerCacheDir = Path::create(
            [
                $this->getBaseCacheDir(),
                \get_current_user(),
                $this->getVersion(),
            ]
        );

        return $containerCacheDir->toString();
    }

    private function registerDeprecationHandler(): void
    {
        $isDeprecationHandlerEnabled = $this->envFlags
            ->isDeprecationHandlerEnabled();

        if (!$isDeprecationHandlerEnabled) {
            return;
        }

        /** @var SymfonyStyle $io */
        $io = $this->container
            ->get(SymfonyStyle::class);

        \set_error_handler(
            static function (
                int $errorNumber,
                string $errorString,
                string $file,
                int $line,
            ) use ($io): bool {
                $io->block(
                    "{$errorString} File {$file}, line {$line}",
                    'Deprecation',
                    'bg=yellow;fg=black',
                    ' ',
                    true
                );

                return true;
            },
            E_USER_DEPRECATED
        );
    }
}


================================================
FILE: src/CacheDirectoryFactory/CacheDirectoryFactory.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\CacheDirectoryFactory;

use Crunz\Exception\CrunzException;
use Crunz\Path\Path;

final class CacheDirectoryFactory
{
    public const CRUNZ_BASE_CACHE_DIR = 'CRUNZ_BASE_CACHE_DIR';

    /**
     * @throws CrunzException
     */
    public function generate(): Path
    {
        $cacheDirectory = '.crunz';
        /** @var false|string $basePath */
        $basePath = \getenv(self::CRUNZ_BASE_CACHE_DIR, true);
        if (false === $basePath) {
            return Path::fromStrings(\sys_get_temp_dir(), $cacheDirectory);
        }

        $basePath = \ltrim($basePath);
        if ('' === $basePath) {
            throw new CrunzException('Crunz base cache directory path is empty.');
        }

        return Path::fromStrings($basePath, $cacheDirectory);
    }
}


================================================
FILE: src/Clock/Clock.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Clock;

final class Clock implements ClockInterface
{
    public function now(): \DateTimeImmutable
    {
        return new \DateTimeImmutable();
    }
}


================================================
FILE: src/Clock/ClockInterface.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Clock;

interface ClockInterface
{
    public function now(): \DateTimeImmutable;
}


================================================
FILE: src/Configuration/ConfigFileNotExistsException.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Configuration;

use Crunz\Exception\CrunzException;

final class ConfigFileNotExistsException extends CrunzException
{
    public static function fromFilePath(string $filePath): self
    {
        return new self("Configuration file '{$filePath}' not exists.");
    }
}


================================================
FILE: src/Configuration/ConfigFileNotReadableException.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Configuration;

use Crunz\Exception\CrunzException;

final class ConfigFileNotReadableException extends CrunzException
{
    public static function fromFilePath(string $filePath): self
    {
        return new self("Config file '{$filePath}' is not readable.");
    }
}


================================================
FILE: src/Configuration/Configuration.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Configuration;

use Crunz\Application\Service\ConfigurationInterface;
use Crunz\Filesystem\FilesystemInterface;
use Crunz\Path\Path;

final class Configuration implements ConfigurationInterface
{
    /** @var array<string,mixed> */
    private $config;

    public function __construct(
        private readonly ConfigurationParserInterface $configurationParser,
        private readonly FilesystemInterface $filesystem,
    ) {
    }

    /**
     * Return a parameter based on a key.
     */
    public function get(string $key, mixed $default = null): mixed
    {
        if (null === $this->config) {
            $this->config = $this->configurationParser
                ->parseConfig();
        }

        if (\array_key_exists($key, $this->config)) {
            return $this->config[$key];
        }

        $parts = \explode('.', $key);

        $value = $this->config;
        foreach ($parts as $part) {
            if (!\is_array($value) || !\array_key_exists($part, $value)) {
                return $default;
            }

            $value = $value[$part];
        }

        return $value;
    }

    /**
     * Set a parameter based on key/value.
     */
    public function withNewEntry(string $key, mixed $value): ConfigurationInterface
    {
        $newConfiguration = clone $this;

        if (null === $newConfiguration->config) {
            $newConfiguration->config = $newConfiguration->configurationParser
                ->parseConfig();
        }

        $parts = \explode('.', $key);

        if (\count($parts) > 1) {
            if (\array_key_exists($parts[0], $newConfiguration->config) && \is_array($newConfiguration->config[$parts[0]])) {
                $newConfiguration->config[$parts[0]][$parts[1]] = $value;
            } else {
                $newConfiguration->config[$parts[0]] = [$parts[1] => $value];
            }
        } else {
            $newConfiguration->config[$key] = $value;
        }

        return $newConfiguration;
    }

    public function getSourcePath(): string
    {
        $sourcePath = Path::create(
            [
                $this->filesystem
                    ->getCwd(),
                $this->get('source', 'tasks'),
            ]
        );

        return $sourcePath->toString();
    }
}


================================================
FILE: src/Configuration/ConfigurationParser.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Configuration;

use Crunz\Console\Command\ConfigGeneratorCommand;
use Crunz\Filesystem\FilesystemInterface;
use Crunz\Logger\ConsoleLoggerInterface;
use Crunz\Path\Path;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Processor;

final class ConfigurationParser implements ConfigurationParserInterface
{
    public function __construct(
        private readonly ConfigurationInterface $configurationDefinition,
        private readonly Processor $definitionProcessor,
        private readonly FileParser $fileParser,
        private readonly ConsoleLoggerInterface $consoleLogger,
        private readonly FilesystemInterface $filesystem,
    ) {
    }

    public function parseConfig(): array
    {
        $configFile = null;
        $parsedConfig = [];
        $configFileParsed = false;

        try {
            $configFile = $this->configFilePath();
            $parsedConfig = $this->fileParser
                ->parse($configFile);

            $configFileParsed = true;
        } catch (ConfigFileNotExistsException $exception) {
            $this->consoleLogger
                ->debug("Config file not found, exception message: '<error>{$exception->getMessage()}</error>'.");
        } catch (ConfigFileNotReadableException $exception) {
            $this->consoleLogger
                ->debug("Config file is not readable, exception message: '<error>{$exception->getMessage()}</error>'.");
        }

        if (false === $configFileParsed) {
            $this->consoleLogger
                ->verbose('Unable to find/parse config file, fallback to default values.');
        } else {
            $this->consoleLogger
                ->verbose("Using config file <info>{$configFile}</info>.");
        }

        return $this->definitionProcessor
            ->processConfiguration(
                $this->configurationDefinition,
                $parsedConfig
            );
    }

    /** @throws ConfigFileNotExistsException */
    private function configFilePath(): string
    {
        $cwd = $this->filesystem
            ->getCwd();
        $configPath = Path::fromStrings($cwd ?? '', ConfigGeneratorCommand::CONFIG_FILE_NAME)->toString();
        $configExists = $this->filesystem
            ->fileExists($configPath);

        if ($configExists) {
            return $configPath;
        }

        throw new ConfigFileNotExistsException(
            \sprintf(
                'Unable to find config file "%s".',
                $configPath
            )
        );
    }
}


================================================
FILE: src/Configuration/ConfigurationParserInterface.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Configuration;

interface ConfigurationParserInterface
{
    /** @return array<string,int|string|array> */
    public function parseConfig(): array;
}


================================================
FILE: src/Configuration/Definition.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Configuration;

use Crunz\Infrastructure\Psr\Logger\PsrStreamLoggerFactory;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Definition implements ConfigurationInterface
{
    public function getConfigTreeBuilder(): TreeBuilder
    {
        $treeBuilder = new TreeBuilder('crunz');

        $rootNode = $treeBuilder->getRootNode();
        $rootNode

            ->children()

                ->scalarNode('source')
                    ->cannotBeEmpty()
                    ->info('path to the tasks directory' . PHP_EOL)
                ->end()

                ->scalarNode('suffix')
                    ->defaultValue('Tasks.php')
                    ->info('The suffix for filenames' . PHP_EOL)
                ->end()

                ->scalarNode('timezone')
                    ->info('Timezone used to calculate task run date')
                ->end()

                ->booleanNode('timezone_log')
                    ->defaultFalse()
                    ->info('Whether configured "timezone" will be used for logs')
                ->end()

                ->scalarNode('logger_factory')
                    ->defaultValue(PsrStreamLoggerFactory::class)
                    ->cannotBeEmpty()
                    ->info("Class name implementing 'LoggerFactoryInterface'. Use it to provider your own logger.")
                ->end()

                ->booleanNode('log_errors')
                    ->defaultFalse()
                    ->info('Flag for logging errors' . PHP_EOL)
                ->end()

                ->scalarNode('errors_log_file')
                    ->defaultValue('/dev/null')
                    ->info('Path to errors log' . PHP_EOL)
                ->end()

                ->booleanNode('log_output')
                    ->defaultFalse()
                    ->info('Flag for logging output' . PHP_EOL)
                ->end()

                ->scalarNode('output_log_file')
                    ->defaultValue('/dev/null')
                    ->info('Path to output logs' . PHP_EOL)
                ->end()

                ->scalarNode('log_allow_line_breaks')
                    ->defaultFalse()
                    ->info('Flag for line breaks in logs' . PHP_EOL)
                ->end()

                ->scalarNode('log_ignore_empty_context')
                    ->defaultFalse()
                    ->info('Flag for empty context in logs' . PHP_EOL)
                ->end()

                ->scalarNode('email_output')
                    ->defaultFalse()
                    ->info('Email the event\'s output' . PHP_EOL)
                ->end()

                ->scalarNode('email_errors')
                    ->defaultFalse()
                    ->info('Notify by email in case of an error' . PHP_EOL)
                ->end()

                ->arrayNode('mailer')

                    ->children()

                        ->scalarNode('transport')
                        ->info('The type the Swift transporter' . PHP_EOL)
                        ->end()

                        ->arrayNode('recipients')
                        ->prototype('scalar')->end()
                        ->info('List of the email recipients' . PHP_EOL)
                        ->end()

                        ->scalarNode('sender_name')
                        ->info('The sender name' . PHP_EOL)
                        ->end()

                        ->scalarNode('sender_email')
                        ->info('The sender email' . PHP_EOL)
                        ->end()

                    ->end()

                ->end()

                ->arrayNode('smtp')

                    ->children()

                        ->scalarNode('host')
                        ->info('SMTP host' . PHP_EOL)
                        ->end()

                        ->scalarNode('port')
                        ->info('SMTP port' . PHP_EOL)
                        ->end()

                        ->scalarNode('username')
                        ->info('SMTP username' . PHP_EOL)
                        ->end()

                        ->scalarNode('password')
                        ->info('SMTP password' . PHP_EOL)
                        ->end()

                        ->scalarNode('encryption')
                        ->info('SMTP encryption' . PHP_EOL)
                        ->end()

                    ->end()

                ->end()

            ->end()
        ;

        return $treeBuilder;
    }
}


================================================
FILE: src/Configuration/FileParser.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Configuration;

use Symfony\Component\Yaml\Yaml;

class FileParser
{
    public function __construct(private readonly Yaml $yamlParser)
    {
    }

    /**
     * @return array<array>
     *
     * @throws ConfigFileNotExistsException
     * @throws ConfigFileNotReadableException
     */
    public function parse(string $configPath): array
    {
        if (!\file_exists($configPath)) {
            throw ConfigFileNotExistsException::fromFilePath($configPath);
        }

        if (!\is_readable($configPath)) {
            throw ConfigFileNotReadableException::fromFilePath($configPath);
        }

        $yamlParser = $this->yamlParser;
        $configContent = \file_get_contents($configPath);

        if (false === $configContent) {
            throw ConfigFileNotReadableException::fromFilePath($configPath);
        }

        return [$yamlParser::parse($configContent)];
    }
}


================================================
FILE: src/Console/Command/Command.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Console\Command;

use Symfony\Component\Console\Command\Command as BaseCommand;

class Command extends BaseCommand
{
    /** @var array<string|bool|int|float|array|null> */
    protected $arguments;

    /** @var array<string|bool|int|float|array|null> */
    protected $options;

    /**
     * Input object.
     *
     * @var \Symfony\Component\Console\Input\InputInterface
     */
    protected $input;

    /**
     * output object.
     *
     * @var \Symfony\Component\Console\Output\OutputInterface
     */
    protected $output;
}


================================================
FILE: src/Console/Command/ConfigGeneratorCommand.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Console\Command;

use Crunz\Filesystem\FilesystemInterface;
use Crunz\Path\Path;
use Crunz\Timezone\ProviderInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Filesystem\Filesystem;

final class ConfigGeneratorCommand extends Command
{
    public const CONFIG_FILE_NAME = 'crunz.yml';

    public function __construct(
        private readonly ProviderInterface $timezoneProvider,
        private readonly Filesystem $symfonyFilesystem,
        private readonly FilesystemInterface $filesystem,
    ) {
        parent::__construct();
    }

    /**
     * Configures the current command.
     */
    protected function configure(): void
    {
        $this
            ->setName('publish:config')
            ->setDescription("Generates a config file within the project's root directory.")
            ->setHelp("This generates a config file in YML format within the project's root directory.")
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $symfonyStyleIo = new SymfonyStyle($input, $output);
        $cwd = $this->filesystem
            ->getCwd();
        $path = Path::create([$cwd, self::CONFIG_FILE_NAME])->toString();
        $destination = \realpath($path) ?: $path;
        $configExists = $this->filesystem
            ->fileExists($destination)
        ;

        $output->writeln(
            "<info>Destination config file: '{$destination}'.</info>",
            OutputInterface::VERBOSITY_VERBOSE
        );

        if ($configExists) {
            $output->writeln(
                "<comment>The configuration file already exists at '{$destination}'.</comment>"
            );

            return 0;
        }

        $projectRoot = $this->filesystem
            ->projectRootDirectory();
        $srcPath = Path::fromStrings(
            $projectRoot,
            'resources',
            'config',
            self::CONFIG_FILE_NAME
        );
        $src = $srcPath->toString();
        $output->writeln(
            "<info>Source config file: '{$src}'.</info>",
            OutputInterface::VERBOSITY_VERBOSE
        );
        $defaultTimezone = $this->askForTimezone($symfonyStyleIo);
        $output->writeln(
            "<info>Provided timezone: '{$defaultTimezone}'.</info>",
            OutputInterface::VERBOSITY_VERBOSE
        );

        $this->updateTimezone(
            $destination,
            $src,
            $defaultTimezone
        );

        $output->writeln('<info>The configuration file was generated successfully.</info>');

        return 0;
    }

    /**
     * @return string
     */
    protected function askForTimezone(SymfonyStyle $symfonyStyleIo)
    {
        $defaultTimezone = $this->timezoneProvider
            ->defaultTimezone()
            ->getName()
        ;
        $question = new Question(
            '<question>Please provide default timezone for task run date calculations</question>',
            $defaultTimezone
        );
        $question->setAutocompleterValues(\DateTimeZone::listIdentifiers());
        $question->setValidator(
            static function ($answer) {
                try {
                    new \DateTimeZone($answer);
                } catch (\Exception) {
                    throw new \Exception("Timezone '{$answer}' is not valid. Please provide valid timezone.");
                }

                return $answer;
            }
        );

        return $symfonyStyleIo->askQuestion($question);
    }

    private function updateTimezone(
        string $destination,
        string $src,
        string $timezone,
    ): void {
        $this->symfonyFilesystem
            ->dumpFile(
                $destination,
                \str_replace(
                    'timezone: ~',
                    "timezone: {$timezone}",
                    $this->filesystem
                        ->readContent($src)
                )
            )
        ;
    }
}


================================================
FILE: src/Console/Command/ScheduleListCommand.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Console\Command;

use Crunz\Application\Service\ConfigurationInterface;
use Crunz\Exception\CrunzException;
use Crunz\Task\CollectionInterface;
use Crunz\Task\LoaderInterface;
use Crunz\Task\WrongTaskInstanceException;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class ScheduleListCommand extends \Symfony\Component\Console\Command\Command
{
    private const FORMAT_TEXT = 'text';
    private const FORMAT_JSON = 'json';
    private const FORMATS = [
        self::FORMAT_TEXT,
        self::FORMAT_JSON,
    ];

    public function __construct(
        private readonly ConfigurationInterface $configuration,
        private readonly CollectionInterface $taskCollection,
        private readonly LoaderInterface $taskLoader,
    ) {
        parent::__construct();
    }

    /**
     * Configures the current command.
     */
    protected function configure(): void
    {
        $possibleFormats = \implode('", "', self::FORMATS);
        $this
            ->setName('schedule:list')
            ->setDescription('Displays the list of scheduled tasks.')
            ->setDefinition(
                [
                    new InputArgument(
                        'source',
                        InputArgument::OPTIONAL,
                        'The source directory for collecting the tasks.',
                        $this->configuration
                            ->getSourcePath()
                    ),
                ]
            )
            ->addOption(
                'format',
                'f',
                InputOption::VALUE_REQUIRED,
                "Tasks list format, possible formats: \"{$possibleFormats}\".",
                self::FORMAT_TEXT,
            )
        ;
    }

    /**
     * @throws WrongTaskInstanceException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        /** @var string $source */
        $source = $input->getArgument('source');
        $format = $this->resolveFormat($input);
        $tasks = $this->tasks($source);
        if (!\count($tasks)) {
            $output->writeln('<comment>No task found!</comment>');

            return 0;
        }

        $this->printList(
            $output,
            $tasks,
            $format,
        );

        return 0;
    }

    /**
     * @return array<
     *     int,
     *     array{
     *         number: int,
     *         task: string,
     *         expression: string,
     *         command: string,
     *     },
     * >
     */
    private function tasks(string $source): array
    {
        /** @var \SplFileInfo[] $tasks */
        $tasks = $this->taskCollection
            ->all($source)
        ;
        $schedules = $this->taskLoader
            ->load(...\array_values($tasks))
        ;

        $tasksList = [];
        $number = 0;

        foreach ($schedules as $schedule) {
            $events = $schedule->events();
            foreach ($events as $event) {
                $tasksList[] = [
                    'number' => ++$number,
                    'task' => $event->description ?? '',
                    'expression' => $event->getExpression(),
                    'command' => $event->getCommandForDisplay(),
                ];
            }
        }

        return $tasksList;
    }

    private function resolveFormat(InputInterface $input): string
    {
        /** @var string $format */
        $format = $input->getOption('format');
        $isValidFormat = \in_array(
            $format,
            self::FORMATS,
            true,
        );

        if (false === $isValidFormat) {
            throw new CrunzException("Format '{$format}' is not supported.");
        }

        return $format;
    }

    /**
     * @param array<
     *     int,
     *     array{
     *         number: int,
     *         task: string,
     *         expression: string,
     *         command: string,
     *     },
     * > $tasks
     */
    private function printList(
        OutputInterface $output,
        array $tasks,
        string $format,
    ): void {
        switch ($format) {
            case self::FORMAT_TEXT:
                $table = new Table($output);
                $table->setHeaders(
                    [
                        '#',
                        'Task',
                        'Expression',
                        'Command to Run',
                    ]
                );

                foreach ($tasks as $task) {
                    $table->addRow($task);
                }

                $table->render();

                break;

            case self::FORMAT_JSON:
                $output->writeln(
                    \json_encode(
                        $tasks,
                        JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT,
                    ),
                );

                break;

            default:
                throw new CrunzException("Unable to print list in format '{$format}'.");
        }
    }
}


================================================
FILE: src/Console/Command/ScheduleRunCommand.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Console\Command;

use Crunz\Application\Service\ConfigurationInterface;
use Crunz\EventRunner;
use Crunz\Schedule;
use Crunz\Task\CollectionInterface;
use Crunz\Task\LoaderInterface;
use Crunz\Task\TaskNumber;
use Crunz\Task\Timezone;
use Crunz\Task\WrongTaskInstanceException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class ScheduleRunCommand extends Command
{
    public function __construct(
        private readonly CollectionInterface $taskCollection,
        private readonly ConfigurationInterface $configuration,
        private readonly EventRunner $eventRunner,
        private readonly Timezone $taskTimezone,
        private readonly Schedule\ScheduleFactory $scheduleFactory,
        private readonly LoaderInterface $taskLoader,
    ) {
        parent::__construct();
    }

    /**
     * Configures the current command.
     */
    protected function configure(): void
    {
        $this->setName('schedule:run')
            ->setDescription('Starts the event runner.')
            ->setDefinition(
                [
                    new InputArgument(
                        'source',
                        InputArgument::OPTIONAL,
                        'The source directory for collecting the task files.',
                        $this->configuration
                            ->getSourcePath()
                    ),
                ]
            )
            ->addOption(
                'force',
                'f',
                InputOption::VALUE_NONE,
                'Run all tasks regardless of configured run time.'
            )
            ->addOption(
                'task',
                't',
                InputOption::VALUE_REQUIRED,
                'Which task to run. Provide task number from <info>schedule:list</info> command.',
                null
            )
           ->setHelp('This command starts the Crunz event runner.');
    }

    /**
     * @throws WrongTaskInstanceException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->arguments = $input->getArguments();
        $this->options = $input->getOptions();
        $task = $this->options['task'];
        /** @var string $source */
        $source = $input->getArgument('source') ?? '';
        /** @var \SplFileInfo[] $files */
        $files = $this->taskCollection
            ->all($source)
        ;

        if (!\count($files)) {
            $output->writeln('<comment>No task found! Please check your source path.</comment>');

            return 0;
        }

        // List of schedules
        $schedules = $this->taskLoader
            ->load(...\array_values($files))
        ;
        $tasksTimezone = $this->taskTimezone
            ->timezoneForComparisons()
        ;

        // Is specified task should be invoked?
        if (\is_string($task)) {
            $schedules = $this->scheduleFactory
                ->singleTaskSchedule(TaskNumber::fromString($task), ...$schedules);
        }

        $forceRun = \filter_var($this->options['force'] ?? false, FILTER_VALIDATE_BOOLEAN);
        $schedules = \array_map(
            static function (Schedule $schedule) use ($tasksTimezone, $forceRun) {
                if (false === $forceRun) {
                    // We keep the events which are due and dismiss the rest.
                    $schedule->events(
                        $schedule->dueEvents(
                            $tasksTimezone
                        )
                    );
                }

                return $schedule;
            },
            $schedules
        );
        $schedules = \array_filter(
            $schedules,
            static fn (Schedule $schedule): bool => \count($schedule->events()) > 0
        );

        if (!\count($schedules)) {
            $output->writeln('<comment>No event is due!</comment>');

            return 0;
        }

        // Running the events
        $this->eventRunner
            ->handle($output, $schedules)
        ;

        return 0;
    }
}


================================================
FILE: src/Console/Command/TaskGeneratorCommand.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Console\Command;

use Crunz\Application\Service\ConfigurationInterface;
use Crunz\Filesystem\FilesystemInterface;
use Crunz\Path\Path;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;

class TaskGeneratorCommand extends Command
{
    /**
     * Default option values.
     *
     * @var array<string,string>
     */
    final public const DEFAULTS = [
        'frequency' => 'everyThirtyMinutes',
        'constraint' => 'weekdays',
        'in' => 'path/to/your/command',
        'run' => 'command/to/execute',
        'description' => 'Task description',
        'type' => 'basic',
    ];
    /**
     * Stub content.
     *
     * @var string
     */
    protected $stub;

    public function __construct(
        private readonly ConfigurationInterface $config,
        private readonly FilesystemInterface $filesystem,
    ) {
        parent::__construct();
    }

    /**
     * Configures the current command.
     */
    protected function configure(): void
    {
        $this
            ->setName('make:task')
            ->setDescription('Generates a task file with one task.')
            ->setDefinition(
                [
                    new InputArgument(
                        'taskfile',
                        InputArgument::REQUIRED,
                        'The task file name'
                    ),
                    new InputOption(
                        'frequency',
                        'f',
                        InputOption::VALUE_OPTIONAL,
                        "The task's frequency",
                        self::DEFAULTS['frequency']
                    ),
                    new InputOption(
                        'constraint',
                        'c',
                        InputOption::VALUE_OPTIONAL,
                        "The task's constraint",
                        self::DEFAULTS['constraint']
                    ),
                    new InputOption(
                        'in',
                        'i',
                        InputOption::VALUE_OPTIONAL,
                        "The command's path",
                        self::DEFAULTS['in']
                    ),
                    new InputOption(
                        'run',
                        'r',
                        InputOption::VALUE_OPTIONAL,
                        "The task's command",
                        self::DEFAULTS['run']
                    ),
                    new InputOption(
                        'description',
                        'd',
                        InputOption::VALUE_OPTIONAL,
                        "The task's description",
                        self::DEFAULTS['description']
                    ),
                    new InputOption(
                        'type',
                        't',
                        InputOption::VALUE_OPTIONAL,
                        'The task type',
                        self::DEFAULTS['type']
                    ),
                ]
            )
            ->setHelp('This command makes a task file skeleton.');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->input = $input;
        $this->output = $output;

        $this->arguments = $input->getArguments();
        $this->options = $input->getOptions();
        $this->stub = $this->getStub();

        if ($this->stub) {
            $this
                ->replaceFrequency()
                ->replaceConstraint()
                ->replaceCommand()
                ->replacePath()
                ->replaceDescription()
            ;
        }

        if ($this->save()) {
            $output->writeln('<info>The task file generated successfully</info>');
        } else {
            $output->writeln('<comment>There was a problem when generating the file. Please check your command.</comment>');
        }

        return 0;
    }

    /**
     * Save the generate task skeleton into a file.
     *
     * @return bool
     */
    protected function save()
    {
        $filename = Path::create([$this->outputPath(), $this->outputFile()]);

        return (bool) \file_put_contents($filename->toString(), $this->stub);
    }

    /**
     * Ask a question.
     *
     * @param string $question
     *
     * @return ?string
     */
    protected function ask($question)
    {
        $helper = $this->getHelper('question');
        $question = new Question("<question>{$question}</question>");

        return $helper->ask($this->input, $this->output, $question);
    }

    /**
     * Return the output path.
     *
     * @return string
     */
    protected function outputPath()
    {
        $source = $this->config
            ->getSourcePath()
        ;
        $destination = $this->ask('Where do you want to save the file? (Press enter for the current directory)');
        $outputPath = $destination ?? $source;

        if (!\file_exists($outputPath)) {
            \mkdir($outputPath, 0744, true);
        }

        return $outputPath;
    }

    /**
     * Populate the output filename.
     *
     * @return string
     */
    protected function outputFile()
    {
        /** @var string $suffix */
        $suffix = $this->config
            ->get('suffix')
        ;
        /** @var string $taskFile */
        $taskFile = $this->arguments['taskfile'];

        return \preg_replace('/Tasks|\.php$/', '', $taskFile) . $suffix;
    }

    /**
     * Get the task stub.
     *
     * @return string
     */
    protected function getStub()
    {
        $projectRootDirectory = $this->filesystem
            ->projectRootDirectory();
        $path = Path::fromStrings(
            $projectRootDirectory,
            'src',
            'Stubs',
            \ucfirst($this->type() . 'Task.php')
        );

        return $this->filesystem
            ->readContent($path->toString());
    }

    /**
     * Get the task type.
     *
     * @return string
     */
    protected function type()
    {
        return $this->options['type'];
    }

    /**
     * Replace frequency.
     */
    protected function replaceFrequency(): self
    {
        $this->stub = \str_replace('DummyFrequency', \rtrim($this->options['frequency'], '()'), $this->stub);

        return $this;
    }

    /**
     * Replace constraint.
     */
    protected function replaceConstraint(): self
    {
        $this->stub = \str_replace('DummyConstraint', \rtrim($this->options['constraint'], '()'), $this->stub);

        return $this;
    }

    protected function replaceCommand(): self
    {
        $run = $this->optionString('run');
        $this->stub = \str_replace('DummyCommand', $run, $this->stub);

        return $this;
    }

    protected function replacePath(): self
    {
        $in = $this->optionString('in');
        $this->stub = \str_replace('DummyPath', $in, $this->stub);

        return $this;
    }

    protected function replaceDescription(): self
    {
        $description = $this->optionString('description');
        $this->stub = \str_replace('DummyDescription', $description, $this->stub);

        return $this;
    }

    private function optionString(string $name): string
    {
        $option = $this->options[$name] ?? throw new \RuntimeException("Missing option '{$name}'.");
        if (false === \is_string($option)) {
            throw new \RuntimeException("Option must be of type 'string'.");
        }

        return $option;
    }
}


================================================
FILE: src/EnvFlags/EnvFlags.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\EnvFlags;

use Crunz\Exception\CrunzException;

final class EnvFlags
{
    public const DEPRECATION_HANDLER_FLAG = 'CRUNZ_DEPRECATION_HANDLER';
    public const CONTAINER_DEBUG_FLAG = 'CRUNZ_CONTAINER_DEBUG';

    /** @return bool */
    public function isDeprecationHandlerEnabled()
    {
        $registerHandlerEnv = \getenv(self::DEPRECATION_HANDLER_FLAG, true);
        $registerHandler = true;

        if (false !== $registerHandlerEnv) {
            $registerHandler = \filter_var($registerHandlerEnv, FILTER_VALIDATE_BOOLEAN);
        }

        return $registerHandler;
    }

    /** @return bool */
    public function isContainerDebugEnabled()
    {
        $containerDebugEnv = \getenv(self::CONTAINER_DEBUG_FLAG, true);
        $containerDebug = false;

        if (false !== $containerDebugEnv) {
            $containerDebug = \filter_var($containerDebugEnv, FILTER_VALIDATE_BOOLEAN);
        }

        return $containerDebug;
    }

    /** @throws CrunzException When disabling deprecation handler fails */
    public function disableContainerDebug(): void
    {
        if (false === \putenv(self::CONTAINER_DEBUG_FLAG . '=0')) {
            throw new CrunzException('Disabling container debug failed.');
        }
    }

    /** @throws CrunzException When enabling deprecation handler fails */
    public function enableContainerDebug(): void
    {
        if (false === \putenv(self::CONTAINER_DEBUG_FLAG . '=1')) {
            throw new CrunzException('Enabling container debug failed.');
        }
    }

    /** @throws CrunzException When enabling deprecation handler fails */
    public function enableDeprecationHandler(): void
    {
        if (false === \putenv(self::DEPRECATION_HANDLER_FLAG . '=1')) {
            throw new CrunzException('Enabling deprecation handler failed.');
        }
    }

    /** @throws CrunzException When disabling deprecation handler fails */
    public function disableDeprecationHandler(): void
    {
        if (false === \putenv(self::DEPRECATION_HANDLER_FLAG . '=0')) {
            throw new CrunzException('Disabling deprecation handler failed.');
        }
    }
}


================================================
FILE: src/Event.php
================================================
<?php

declare(strict_types=1);

namespace Crunz;

use Closure;
use Cron\CronExpression;
use Crunz\Application\Service\ClosureSerializerInterface;
use Crunz\Clock\Clock;
use Crunz\Clock\ClockInterface;
use Crunz\Exception\CrunzException;
use Crunz\Exception\NotImplementedException;
use Crunz\Infrastructure\Laravel\LaravelClosureSerializer;
use Crunz\Logger\Logger;
use Crunz\Path\Path;
use Crunz\Pinger\PingableInterface;
use Crunz\Pinger\PingableTrait;
use Crunz\Process\Process;
use Crunz\Task\TaskException;
use Symfony\Component\Lock\Exception\InvalidArgumentException;
use Symfony\Component\Lock\Factory;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\PersistingStoreInterface;
use Symfony\Component\Lock\Store\FlockStore;

class Event implements PingableInterface
{
    use PingableTrait;

    private const LOCK_TTL = 120; // seconds
    private const LOCK_REFRESH_THRESHOLD = 30; // seconds

    /**
     * The location that output should be sent to.
     *
     * @var string
     */
    public $output = '/dev/null';

    /**
     * Indicates whether output should be appended.
     *
     * @var bool
     */
    public $shouldAppendOutput = false;

    /**
     * The human readable description of the event.
     *
     * @var string|null
     */
    public $description;

    /**
     * Event generated output.
     *
     * @var string|null
     */
    public $outputStream;

    /**
     * Event personal logger instance.
     *
     * @var Logger
     */
    public $logger;

    /** @var string|\Closure */
    protected $command;

    /**
     * Process that runs the event.
     *
     * @var Process
     */
    protected $process;

    /**
     * The cron expression representing the event's frequency.
     *
     * @var string
     */
    protected $expression = '* * * * *';

    /**
     * The timezone the date should be evaluated on.
     *
     * @var \DateTimeZone|string
     */
    protected $timezone;

    /**
     * Datetime or time since the task is evaluated and possibly executed only for display purposes.
     */
    protected \DateTime|string|null $from = null;

    /**
     * Datetime or time until the task is evaluated and possibly executed only for display purposes.
     */
    protected \DateTime|string|null $to = null;

    /**
     * The user the command should run as.
     *
     * @var string
     */
    protected $user;

    /**
     * The array of filter callbacks.
     *
     * @var \Closure[]
     */
    protected $filters = [];

    /**
     * The array of reject callbacks.
     *
     * @var \Closure[]
     */
    protected $rejects = [];

    /**
     * The array of callbacks to be run before the event is started.
     *
     * @var \Closure[]
     */
    protected $beforeCallbacks = [];

    /**
     * The array of callbacks to be run after the event is finished.
     *
     * @var \Closure[]
     */
    protected $afterCallbacks = [];

    /**
     * Current working directory.
     *
     * @var string
     */
    protected $cwd;

    /**
     * Position of cron fields.
     *
     * @var array<string,int>
     */
    protected $fieldsPosition = [
        'minute' => 1,
        'hour' => 2,
        'day' => 3,
        'month' => 4,
        'week' => 5,
    ];

    /**
     * Indicates if the command should not overlap itself.
     */
    private bool $preventOverlapping = false;
    /** @var ClockInterface */
    private static $clock;
    private static ?ClosureSerializerInterface $closureSerializer = null;

    /**
     * The symfony lock factory that is used to acquire locks. If the value is null, but preventOverlapping = true
     * crunz falls back to filesystem locks.
     */
    private ?LockFactory $lockFactory = null;
    /** @var string[] */
    private array $wholeOutput = [];
    /** @var Lock */
    private $lock;
    /** @var \Closure[] */
    private array $errorCallbacks = [];

    /**
     * Create a new event instance.
     *
     * @param string|\Closure $command
     * @param string|int      $id
     */
    public function __construct(protected $id, $command)
    {
        $this->command = $command;
        $this->output = $this->getDefaultOutput();
    }

    /**
     * Change the current working directory.
     *
     * @param string $directory
     *
     * @return self
     */
    public function in($directory)
    {
        $this->cwd = $directory;

        return $this;
    }

    /**
     * Determine if the event's output is sent to null.
     *
     * @return bool
     */
    public function nullOutput()
    {
        return 'NUL' === $this->output || '/dev/null' === $this->output;
    }

    /**
     * Build the command string.
     *
     * @return string
     */
    public function buildCommand()
    {
        $command = '';

        if ($this->cwd) {
            if ($this->user) {
                $command .= $this->sudo($this->user);
            }

            // Support changing drives in Windows
            $cdParameter = $this->isWindows() ? '/d ' : '';
            $andSign = $this->isWindows() ? ' &' : ';';

            $command .= "cd {$cdParameter}{$this->cwd}{$andSign} ";
        }

        if ($this->user) {
            $command .= $this->sudo($this->user);
        }

        $command .= \is_string($this->command)
            ? $this->command
            : $this->serializeClosure($this->command)
        ;

        return \trim($command, '& ');
    }

    /**
     * Determine whether the passed value is a closure or not.
     *
     * @return bool
     */
    public function isClosure()
    {
        return \is_object($this->command) && ($this->command instanceof \Closure);
    }

    /**
     * Determine if the given event should run based on the Cron expression.
     *
     * @return bool
     */
    public function isDue(\DateTimeZone $timeZone)
    {
        return $this->expressionPasses($timeZone) && $this->filtersPass($timeZone);
    }

    /**
     * Determine if the filters pass for the event.
     *
     * @return bool
     */
    public function filtersPass(\DateTimeZone $timeZone)
    {
        $invoker = new Invoker();

        foreach ($this->filters as $callback) {
            if (!$invoker->call($callback)) {
                return false;
            }
        }

        foreach ($this->rejects as $callback) {
            if ($invoker->call($callback, [$timeZone])) {
                return false;
            }
        }

        return true;
    }

    /** @return string */
    public function wholeOutput()
    {
        return \implode('', $this->wholeOutput);
    }

    /**
     * Start the event execution.
     *
     * @return int
     */
    public function start()
    {
        $command = $this->buildCommand();
        $process = Process::fromStringCommand($command);

        $this->setProcess($process);
        $this->getProcess()->start(
            function ($type, $content): void {
                $this->wholeOutput[] = $content;
            }
        );

        if ($this->preventOverlapping) {
            $this->lock();
        }

        /** @var int $pid */
        $pid = $this->getProcess()
            ->getPid();

        return $pid;
    }

    /**
     * The Cron expression representing the event's frequency.
     *
     * @throws TaskException
     */
    public function cron(string $expression): self
    {
        $parts = \preg_split(
            '/\s/',
            $expression,
            -1,
            PREG_SPLIT_NO_EMPTY
        );
        $parts = false === $parts
            ? []
            : $parts
        ;

        if (\count($parts) > 5) {
            throw new TaskException("Expression '{$expression}' has more than five parts and this is not allowed.");
        }

        $this->expression = $expression;

        return $this;
    }

    /**
     * Schedule the event to run hourly.
     */
    public function hourly(): self
    {
        return $this->hourlyAt(0);
    }

    public function hourlyAt(int $minute): self
    {
        if ($minute < 0) {
            throw new CrunzException("Minute cannot be lower than '0'.");
        }

        if ($minute > 59) {
            throw new CrunzException("Minute cannot be greater than '59'.");
        }

        return $this->cron("{$minute} * * * *");
    }

    /**
     * Schedule the event to run daily.
     */
    public function daily(): self
    {
        return $this->cron('0 0 * * *');
    }

    /**
     * Schedule the event to run on a certain date.
     *
     * @param string $date
     *
     * @return $this
     */
    public function on($date)
    {
        $parsedDate = \date_parse($date);
        if (false === $parsedDate) {
            $parsedDate = [];
        }

        $segments = \array_intersect_key($parsedDate, $this->fieldsPosition);

        if ($parsedDate['year']) {
            $this->skip(static fn () => (int) \date('Y') !== $parsedDate['year']);
        }

        foreach ($segments as $key => $value) {
            if (false !== $value) {
                $this->spliceIntoPosition($this->fieldsPosition[$key], (string) $value);
            }
        }

        return $this;
    }

    /**
     * Schedule the command at a given time.
     *
     * @param string $time
     */
    public function at($time): self
    {
        return $this->dailyAt($time);
    }

    /**
     * Schedule the event to run daily at a given time (10:00, 19:30, etc).
     *
     * @param string $time
     */
    public function dailyAt($time): self
    {
        $segments = \explode(':', $time);
        $firstSegment = (int) $segments[0];
        $secondSegment = \count($segments) > 1
            ? (int) $segments[1]
            : '0'
        ;

        return $this
            ->spliceIntoPosition(2, (string) $firstSegment)
            ->spliceIntoPosition(1, (string) $secondSegment)
        ;
    }

    /**
     * Set Working period.
     *
     * @param string $from
     * @param string $to
     *
     * @return self
     */
    public function between($from, $to)
    {
        return $this->from($from)
                    ->to($to);
    }

    /**
     * Check if event should be on.
     *
     * @param string $datetime
     *
     * @return self
     */
    public function from($datetime)
    {
        $this->from = $datetime;

        return $this->skip(
            fn (\DateTimeZone $timeZone) => $this->notYet($datetime, $timeZone)
        );
    }

    /**
     * Check if event should be off.
     *
     * @param string $datetime
     *
     * @return self
     */
    public function to($datetime)
    {
        $this->to = $datetime;

        return $this->skip(
            fn (\DateTimeZone $timeZone) => $this->past($datetime, $timeZone),
        );
    }

    /**
     * Schedule the event to run twice daily.
     *
     * @param int $first
     * @param int $second
     */
    public function twiceDaily($first = 1, $second = 13): self
    {
        $hours = $first . ',' . $second;

        return $this
            ->spliceIntoPosition(1, '0')
            ->spliceIntoPosition(2, $hours)
        ;
    }

    /**
     * Schedule the event to run only on weekdays.
     */
    public function weekdays(): self
    {
        return $this->spliceIntoPosition(5, '1-5');
    }

    /**
     * Schedule the event to run only on Mondays.
     */
    public function mondays(): self
    {
        return $this->days(1);
    }

    /**
     * Schedule the event to run only on Tuesdays.
     */
    public function tuesdays(): self
    {
        return $this->days(2);
    }

    /**
     * Schedule the event to run only on Wednesdays.
     */
    public function wednesdays(): self
    {
        return $this->days(3);
    }

    /**
     * Schedule the event to run only on Thursdays.
     */
    public function thursdays(): self
    {
        return $this->days(4);
    }

    /**
     * Schedule the event to run only on Fridays.
     */
    public function fridays(): self
    {
        return $this->days(5);
    }

    /**
     * Schedule the event to run only on Saturdays.
     */
    public function saturdays(): self
    {
        return $this->days(6);
    }

    /**
     * Schedule the event to run only on Sundays.
     */
    public function sundays(): self
    {
        return $this->days(0);
    }

    /**
     * Schedule the event to run weekly.
     */
    public function weekly(): self
    {
        return $this->cron('0 0 * * 0');
    }

    /**
     * Schedule the event to run weekly on a given day and time.
     *
     * @param string $time
     */
    public function weeklyOn(int|string $day, $time = '0:0'): self
    {
        $this->dailyAt($time);

        return $this->spliceIntoPosition(5, (string) $day);
    }

    /**
     * Schedule the event to run monthly.
     */
    public function monthly(): self
    {
        return $this->cron('0 0 1 * *');
    }

    /**
     * Schedule the event to run quarterly.
     */
    public function quarterly(): self
    {
        return $this->cron('0 0 1 */3 *');
    }

    /**
     * Schedule the event to run yearly.
     */
    public function yearly(): self
    {
        return $this->cron('0 0 1 1 *');
    }

    /**
     * Set the days of the week the command should run on.
     */
    public function days(mixed $days): self
    {
        $days = \is_array($days) ? $days : \func_get_args();

        return $this->spliceIntoPosition(5, \implode(',', $days));
    }

    /**
     * Set hour for the cron job.
     */
    public function hour(mixed $value): self
    {
        $value = \is_array($value) ? $value : \func_get_args();

        return $this->spliceIntoPosition(2, \implode(',', $value));
    }

    /**
     * Set minute for the cron job.
     */
    public function minute(mixed $value): self
    {
        $value = \is_array($value) ? $value : \func_get_args();

        return $this->spliceIntoPosition(1, \implode(',', $value));
    }

    /**
     * Set hour for the cron job.
     */
    public function dayOfMonth(mixed $value): self
    {
        $value = \is_array($value) ? $value : \func_get_args();

        return $this->spliceIntoPosition(3, \implode(',', $value));
    }

    /**
     * Set hour for the cron job.
     */
    public function month(mixed $value): self
    {
        $value = \is_array($value) ? $value : \func_get_args();

        return $this->spliceIntoPosition(4, \implode(',', $value));
    }

    /**
     * Set hour for the cron job.
     */
    public function dayOfWeek(mixed $value): self
    {
        $value = \is_array($value) ? $value : \func_get_args();

        return $this->spliceIntoPosition(5, \implode(',', $value));
    }

    /**
     * Set the timezone the date should be evaluated on.
     *
     * @return $this
     */
    public function timezone(\DateTimeZone|string $timezone)
    {
        $this->timezone = $timezone;

        return $this;
    }

    /**
     * Set which user the command should run as.
     *
     * @param string $user
     *
     * @return $this
     */
    public function user($user)
    {
        if ($this->isWindows()) {
            throw new NotImplementedException('Changing user on Windows is not implemented.');
        }

        $this->user = $user;

        return $this;
    }

    /**
     * Do not allow the event to overlap each other.
     *
     * By default, the lock is acquired through file system locks. Alternatively, you can pass a symfony lock store
     * that will be responsible for the locking.
     *
     * @param PersistingStoreInterface|object $store
     *
     * @return $this
     */
    public function preventOverlapping(?object $store = null)
    {
        if (null !== $store && !($store instanceof PersistingStoreInterface)) {
            $expectedClass = PersistingStoreInterface::class;
            $actualClass = $store::class;

            throw new \RuntimeException(
                "Instance of '{$expectedClass}' is expected, '{$actualClass}' provided"
            );
        }

        $lockStore = $store ?: $this->createDefaultLockStore();
        $this->preventOverlapping = true;
        $this->lockFactory = new LockFactory($lockStore);

        // Skip the event if it's locked (processing)
        $this->skip(function () {
            $lock = $this->createLockObject();
            $lock->acquire();

            return !$lock->isAcquired();
        });

        $releaseCallback = function (): void {
            $this->releaseLock();
        };

        // Delete the lock file when the event is completed
        $this->after($releaseCallback);
        // Or on error
        $this->addErrorCallback($releaseCallback);

        return $this;
    }

    /**
     * Register a callback to further filter the schedule.
     *
     * @return $this
     */
    public function when(\Closure $callback)
    {
        $this->filters[] = $callback;

        return $this;
    }

    /**
     * Register a callback to further filter the schedule.
     *
     * @return $this
     */
    public function skip(\Closure $callback)
    {
        $this->rejects[] = $callback;

        return $this;
    }

    /**
     * Send the output of the command to a given location.
     *
     * @param string $location
     * @param bool   $append
     *
     * @return $this
     */
    public function sendOutputTo($location, $append = false)
    {
        $this->output = $location;

        $this->shouldAppendOutput = $append;

        return $this;
    }

    /**
     * Append the output of the command to a given location.
     *
     * @param string $location
     *
     * @return $this
     */
    public function appendOutputTo($location)
    {
        return $this->sendOutputTo($location, true);
    }

    /**
     * Register a callback to be called before the operation.
     *
     * @return $this
     */
    public function before(\Closure $callback)
    {
        $this->beforeCallbacks[] = $callback;

        return $this;
    }

    /**
     * Register a callback to be called after the operation.
     *
     * @return $this
     */
    public function after(\Closure $callback)
    {
        return $this->then($callback);
    }

    /**
     * Register a callback to be called after the operation.
     *
     * @return $this
     */
    public function then(\Closure $callback)
    {
        $this->afterCallbacks[] = $callback;

        return $this;
    }

    /**
     * Set the human-friendly description of the event.
     *
     * @param string $description
     *
     * @return $this
     */
    public function name($description)
    {
        return $this->description($description);
    }

    /**
     * Return the event's process.
     *
     * @return Process $process
     */
    public function getProcess()
    {
        return $this->process;
    }

    /**
     * Set the human-friendly description of the event.
     *
     * @param string $description
     *
     * @return $this
     */
    public function description($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Another way to the frequency of the cron job.
     *
     * @param string         $unit
     * @param float|int|null $value
     */
    public function every($unit = null, $value = null): self
    {
        if (null === $unit || !isset($this->fieldsPosition[$unit])) {
            return $this;
        }

        $value = (1 === (int) $value) ? '*' : '*/' . $value;

        return $this->spliceIntoPosition($this->fieldsPosition[$unit], $value)
                    ->applyMask($unit);
    }

    /**
     * Return the event's command.
     */
    public function getId(): string|int
    {
        return $this->id;
    }

    /**
     * Get the summary of the event for display.
     *
     * @return string
     */
    public function getSummaryForDisplay()
    {
        if (\is_string($this->description)) {
            return $this->description;
        }

        return $this->buildCommand();
    }

    /**
     * Get the command for display.
     *
     * @return string
     */
    public function getCommandForDisplay()
    {
        return $this->isClosure() ? 'object(Closure)' : $this->buildCommand();
    }

    /**
     * Get the Cron expression for the event.
     *
     * @return string
     */
    public function getExpression()
    {
        return $this->expression;
    }

    /**
     * Get the 'from' configuration for the event if present.
     */
    public function getFrom(): \DateTime|string|null
    {
        return $this->from;
    }

    /**
     * Get the 'to' configuration for the event if present.
     */
    public function getTo(): \DateTime|string|null
    {
        return $this->to;
    }

    /**
     * Set the event's command.
     *
     * @param string $command
     *
     * @return $this
     */
    public function setCommand($command)
    {
        $this->command = $command;

        return $this;
    }

    /**
     * Return the event's command.
     */
    public function getCommand(): string|\Closure
    {
        return $this->command;
    }

    /**
     * Return the current working directory.
     *
     * @return string
     */
    public function getWorkingDirectory()
    {
        return $this->cwd;
    }

    /**
     * Return event's full output.
     *
     * @return string|null
     */
    public function getOutputStream()
    {
        return $this->outputStream;
    }

    /**
     * Return all registered before callbacks.
     *
     * @return \Closure[]
     */
    public function beforeCallbacks()
    {
        return $this->beforeCallbacks;
    }

    /**
     * Return all registered after callbacks.
     *
     * @return \Closure[]
     */
    public function afterCallbacks()
    {
        return $this->afterCallbacks;
    }

    /** @return \Closure[] */
    public function errorCallbacks()
    {
        return $this->errorCallbacks;
    }

    /**
     * If this event is prevented from overlapping, this method should be called regularly to refresh the lock.
     */
    public function refreshLock(): void
    {
        if (!$this->preventOverlapping) {
            return;
        }

        $lock = $this->createLockObject();
        $remainingLifetime = $lock->getRemainingLifetime();

        // Lock will never expire
        if (null === $remainingLifetime) {
            return;
        }

        $lockRefreshNeeded = $remainingLifetime < self::LOCK_REFRESH_THRESHOLD;
        if ($lockRefreshNeeded) {
            $lock->refresh();
        }
    }

    public function everyMinute(): self
    {
        return $this->cron('* * * * *');
    }

    public function everyTwoMinutes(): self
    {
        return $this->cron('*/2 * * * *');
    }

    public function everyThreeMinutes(): self
    {
        return $this->cron('*/3 * * * *');
    }

    public function everyFourMinutes(): self
    {
        return $this->cron('*/4 * * * *');
    }

    public function everyFiveMinutes(): self
    {
        return $this->cron('*/5 * * * *');
    }

    public function everyTenMinutes(): self
    {
        return $this->cron('*/10 * * * *');
    }

    public function everyFifteenMinutes(): self
    {
        return $this->cron('*/15 * * * *');
    }

    public function everyThirtyMinutes(): self
    {
        return $this->cron('*/30 * * * *');
    }

    public function everyTwoHours(): self
    {
        return $this->cron('0 */2 * * *');
    }

    public function everyThreeHours(): self
    {
        return $this->cron('0 */3 * * *');
    }

    public function everyFourHours(): self
    {
        return $this->cron('0 */4 * * *');
    }

    public function everySixHours(): self
    {
        return $this->cron('0 */6 * * *');
    }

    /**
     * Get the symfony lock object for the task.
     *
     * @return Lock
     */
    protected function createLockObject()
    {
        $this->checkLockFactory();

        if (null === $this->lock && null !== $this->lockFactory) {
            $this->lock = $this->lockFactory
                ->createLock($this->lockKey(), self::LOCK_TTL);
        }

        return $this->lock;
    }

    /**
     * Release the lock after the command completed.
     */
    protected function releaseLock(): void
    {
        $this->checkLockFactory();

        $lock = $this->createLockObject();
        $lock->release();
    }

    /**
     * Get the default output depending on the OS.
     *
     * @return string
     */
    protected function getDefaultOutput()
    {
        return (DIRECTORY_SEPARATOR === '\\') ? 'NUL' : '/dev/null';
    }

    /**
     * Add sudo to the command.
     *
     * @param string $user
     *
     * @return string
     */
    protected function sudo($user)
    {
        return "sudo -u {$user} ";
    }

    /**
     * Convert closure to an executable command.
     *
     * @return string
     */
    protected function serializeClosure(\Closure $closure)
    {
        $closure = $this->closureSerializer()
            ->serialize($closure)
        ;
        $serializedClosure = \http_build_query([$closure]);
        $crunzRoot = CRUNZ_BIN;

        return \escapeshellarg(PHP_BINARY) . ' ' . \escapeshellarg($crunzRoot) . " closure:run {$serializedClosure}";
    }

    /**
     * Determine if the Cron expression passes.
     *
     * @return bool
     */
    protected function expressionPasses(\DateTimeZone $timeZone)
    {
        $now = $this->getClock()
            ->now();
        $now = $now->setTimezone($timeZone);

        if ($this->timezone) {
            $taskTimeZone = \is_object($this->timezone) && $this->timezone instanceof \DateTimeZone
                ? $this->timezone
                    ->getName()
                : $this->timezone
            ;

            $now = $now->setTimezone(
                new \DateTimeZone(
                    $taskTimeZone
                )
            );
        }

        return CronExpression::factory($this->expression)->isDue($now->format('Y-m-d H:i:s'));
    }

    /**
     * Check if time hasn't arrived.
     *
     * @param string $datetime
     */
    protected function notYet($datetime, \DateTimeZone $timeZone): bool
    {
        $timeZonedNow = $this->timeZonedNow($timeZone);
        $testedDateTime = new \DateTimeImmutable($datetime, $timeZone);

        return $timeZonedNow < $testedDateTime;
    }

    /**
     * Check if the time has passed.
     *
     * @param string $datetime
     */
    protected function past($datetime, \DateTimeZone $timeZone): bool
    {
        $timeZonedNow = $this->timeZonedNow($timeZone);
        $testedDateTime = new \DateTimeImmutable($datetime, $timeZone);

        return $timeZonedNow > $testedDateTime;
    }

    /**
     * Splice the given value into the given position of the expression.
     *
     * @param int    $position
     * @param string $value
     */
    protected function spliceIntoPosition($position, $value): self
    {
        $segments = \explode(' ', $this->expression);

        $segments[$position - 1] = $value;

        return $this->cron(\implode(' ', $segments));
    }

    /**
     * Mask a cron expression.
     *
     * @param string $unit
     *
     * @return self
     */
    protected function applyMask($unit)
    {
        $cron = \explode(' ', $this->expression);
        $mask = ['0', '0', '1', '1', '*', '*'];
        $fpos = $this->fieldsPosition[$unit] - 1;

        \array_splice($cron, 0, $fpos, \array_slice($mask, 0, $fpos));

        return $this->cron(\implode(' ', $cron));
    }

    /**
     * Lock the event.
     */
    protected function lock(): void
    {
        $lock = $this->createLockObject();
        $lock->acquire();
    }

    private function addErrorCallback(\Closure $closure): void
    {
        $this->errorCallbacks[] = $closure;
    }

    /**
     * Set the event's process.
     */
    private function setProcess(Process $process): void
    {
        $this->process = $process;
    }

    /**
     * @return FlockStore
     *
     * @throws CrunzException
     */
    private function createDefaultLockStore()
    {
        try {
            $lockPath = Path::create(
                [
                    \sys_get_temp_dir(),
                    '.crunz',
                ]
            );

            $store = new FlockStore($lockPath->toString());
        } catch (InvalidArgumentException) {
            // Fallback to system temp dir
            $lockPath = Path::create([\sys_get_temp_dir()]);
            $store = new FlockStore($lockPath->toString());
        }

        return $store;
    }

    private function lockKey(): string
    {
        if ($this->isClosure()) {
            /** @var \Closure $closure */
            $closure = $this->command;
            $command = $this->closureSerializer()
                ->closureCode($closure)
            ;
        } else {
            $command = $this->buildCommand();
        }

        return 'crunz-' . \md5($command);
    }

    private function checkLockFactory(): void
    {
        if (null === $this->lockFactory) {
            throw new \BadMethodCallException(
                'No lock factory. Please call preventOverlapping() first.'
            );
        }
    }

    private function getClock(): ClockInterface
    {
        if (null === self::$clock) {
            self::$clock = new Clock();
        }

        return self::$clock;
    }

    private function closureSerializer(): ClosureSerializerInterface
    {
        if (null === self::$closureSerializer) {
            self::$closureSerializer = new LaravelClosureSerializer();
        }

        return self::$closureSerializer;
    }

    private function isWindows(): bool
    {
        $osCode = \mb_substr(
            PHP_OS,
            0,
            3
        );

        return 'WIN' === $osCode;
    }

    private function timeZonedNow(\DateTimeZone $timeZone): \DateTimeImmutable
    {
        $clock = $this->getClock();
        $now = $clock->now();

        return $now->setTimezone($timeZone);
    }
}


================================================
FILE: src/EventRunner.php
================================================
<?php

declare(strict_types=1);

namespace Crunz;

use Crunz\Application\Service\ConfigurationInterface;
use Crunz\HttpClient\HttpClientInterface;
use Crunz\Logger\ConsoleLoggerInterface;
use Crunz\Logger\Logger;
use Crunz\Logger\LoggerFactory;
use Crunz\Pinger\PingableInterface;
use Symfony\Component\Console\Output\OutputInterface;

class EventRunner
{
    /** @var Schedule[] */
    protected array $schedules = [];
    /** @var Logger|null */
    protected $logger;
    private ?OutputInterface $output = null;

    public function __construct(
        protected Invoker $invoker,
        private readonly ConfigurationInterface $configuration,
        protected Mailer $mailer,
        private readonly LoggerFactory $loggerFactory,
        private readonly HttpClientInterface $httpClient,
        private readonly ConsoleLoggerInterface $consoleLogger,
    ) {
    }

    /** @param Schedule[] $schedules */
    public function handle(OutputInterface $output, array $schedules = []): void
    {
        $this->schedules = $schedules;
        $this->output = $output;

        foreach ($this->schedules as $schedule) {
            $this->consoleLogger
                ->debug("Invoke Schedule's ping before");

            $this->pingBefore($schedule);

            // Running the before-callbacks of the current schedule
            $this->invoke($schedule->beforeCallbacks());

            $events = $schedule->events();
            foreach ($events as $event) {
                $this->start($event);
            }
        }

        // Watch events until they are finished
        $this->manageStartedEvents();
    }

    protected function start(Event $event): void
    {
        $this->logger = $this->loggerFactory
            ->create()
        ;

        // if sendOutputTo or appendOutputTo have been specified
        if (!$event->nullOutput()) {
            // if sendOutputTo then truncate the log file if it exists
            if (!$event->shouldAppendOutput) {
                $f = @\fopen($event->output, 'r+');
                if (false !== $f) {
                    \ftruncate($f, 0);
                    \fclose($f);
                }
            }
            // Create an instance of the Logger specific to the event
            $event->logger = $this->loggerFactory->createEvent($event->output);
        }

        $this->consoleLogger
            ->debug("Invoke Event's ping before.");

        $this->pingBefore($event);

        // Running the before-callbacks
        $event->outputStream = $this->invoke($event->beforeCallbacks());
        $event->start();
    }

    protected function manageStartedEvents(): void
    {
        while ($this->schedules) {
            foreach ($this->schedules as $scheduleKey => $schedule) {
                $events = $schedule->events();
                // 10% chance that refresh will be called
                $refreshLocks = (\random_int(1, 100) <= 10);

                /** @var Event $event */
                foreach ($events as $eventKey => $event) {
                    if ($refreshLocks) {
                        $event->refreshLock();
                    }

                    $proc = $event->getProcess();
                    if ($proc->isRunning()) {
                        continue;
                    }

                    $runStatus = '';

                    if ($proc->isSuccessful()) {
                        $this->consoleLogger
                            ->debug("Invoke Event's ping after.");
                        $this->pingAfter($event);

                        $runStatus = '<info>success</info>';

                        $event->outputStream .= $event->wholeOutput();
                        $event->outputStream .= $this->invoke($event->afterCallbacks());

                        $this->handleOutput($event);
                    } else {
                        $runStatus = '<error>fail</error>';

                        // Invoke error callbacks
                        $this->invoke($event->errorCallbacks());
                        // Calling registered error callbacks with an instance of $event as argument
                        $this->invoke($schedule->errorCallbacks(), [$event]);
                        $this->handleError($event);
                    }

                    $id = $event->description ?: $event->getId();

                    $this->consoleLogger
                        ->debug("Task <info>{$id}</info> status: {$runStatus}.");

                    // Dismiss the event if it's finished
                    $schedule->dismissEvent($eventKey);
                }

                // If there's no event left for the Schedule instance,
                // run the schedule's after-callbacks and remove
                // the Schedule from list of active schedules.                                                                                                                           zzzwwscxqqqAAAQ11
                if (!\count($schedule->events())) {
                    $this->consoleLogger
                        ->debug("Invoke Schedule's ping after.");

                    $this->pingAfter($schedule);
                    $this->invoke($schedule->afterCallbacks());
                    unset($this->schedules[$scheduleKey]);
                }
            }

            \usleep(250000);
        }
    }

    /**
     * @param \Closure[]         $callbacks
     * @param array<mixed,mixed> $parameters
     *
     * @return string
     */
    protected function invoke(array $callbacks = [], array $parameters = [])
    {
        $output = '';
        foreach ($callbacks as $callback) {
            /** @var string $callResult */
            $callResult = $this->invoker->call($callback, $parameters, true);
            // Invoke the callback with buffering enabled
            $output .= $callResult;
        }

        return $output;
    }

    protected function handleOutput(Event $event): void
    {
        $logged = false;
        $logOutput = $this->configuration
            ->get('log_output')
        ;

        if (!$event->nullOutput()) {
            $event->logger->info($this->formatEventOutput($event));
            $logged = true;
        }

        if ($logOutput && !$logged) {
            $this->logger()
                ->info($this->formatEventOutput($event))
            ;
            $logged = true;
        }

        if (!$logged) {
            $this->display($event->getOutputStream());
        }

        $emailOutput = $this->configuration
            ->get('email_output')
        ;
        if ($emailOutput && !empty($event->getOutputStream())) {
            $this->mailer->send(
                'Crunz: output for event: ' . ($event->description ?? $event->getId()),
                $this->formatEventOutput($event)
            );
        }
    }

    protected function handleError(Event $event): void
    {
        $logErrors = $this->configuration
            ->get('log_errors')
        ;
        $emailErrors = $this->configuration
            ->get('email_errors')
        ;

        if ($logErrors) {
            $this->logger()
                ->error($this->formatEventError($event))
            ;
        } else {
            $output = $event->wholeOutput();

            $this->output
                ?->write("<error>{$output}</error>")
            ;
        }

        // Send error as email as configured
        if ($emailErrors) {
            $this->mailer->send(
                'Crunz: reporting error for event:' . ($event->description ?? $event->getId()),
                $this->formatEventError($event)
            );
        }
    }

    /** @return string */
    protected function formatEventOutput(Event $event)
    {
        return $event->description
            . '('
            . $event->getCommandForDisplay()
            . ') '
            . PHP_EOL
            . PHP_EOL
            . $event->outputStream
            . PHP_EOL;
    }

    /** @return string */
    protected function formatEventError(Event $event)
    {
        return $event->description
            . '('
            . $event->getCommandForDisplay()
            . ') '
            . PHP_EOL
            . $event->wholeOutput()
            . PHP_EOL;
    }

    /** @param string|null $output */
    protected function display($output): void
    {
        $this->output
            ?->write(\is_string($output) ? $output : '')
        ;
    }

    private function pingBefore(PingableInterface $schedule): void
    {
        if (!$schedule->hasPingBefore()) {
            $this->consoleLogger
                ->debug('There is no ping before url.');

            return;
        }

        /** @var non-empty-string $pingBeforeUrl */
        $pingBeforeUrl = $schedule->getPingBeforeUrl();
        $this->httpClient
            ->ping($pingBeforeUrl)
        ;
    }

    private function pingAfter(PingableInterface $schedule): void
    {
        if (!$schedule->hasPingAfter()) {
            $this->consoleLogger
                ->debug('There is no ping after url.');

            return;
        }

        /** @var non-empty-string $pingAfterUrl */
        $pingAfterUrl = $schedule->getPingAfterUrl();
        $this->httpClient
            ->ping($pingAfterUrl)
        ;
    }

    private function logger(): Logger
    {
        if (null === $this->logger) {
            $this->logger = $this->loggerFactory
                ->create()
            ;
        }

        return $this->logger;
    }
}


================================================
FILE: src/Exception/CrunzException.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Exception;

class CrunzException extends \Exception
{
}


================================================
FILE: src/Exception/EmptyTimezoneException.php
================================================
<?php

declare(strict_types=1);

namespace Crunz\Ex
Download .txt
gitextract_lvj5ppvr/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── 1_Bug_report.md
│   │   └── 2_Feature_request.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       └── php.yaml
├── .gitignore
├── .php-cs-fixer.dist.php
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── UPGRADE.md
├── bootstrap.php
├── composer.json
├── config/
│   └── services.php
├── crunz
├── docker/
│   └── php82/
│       ├── Dockerfile
│       └── php.ini
├── docker-compose.yml
├── phpstan-baseline.neon
├── phpstan.neon
├── phpunit.xml
├── resources/
│   └── config/
│       └── crunz.yml
├── src/
│   ├── Application/
│   │   ├── Cron/
│   │   │   ├── CronExpressionFactoryInterface.php
│   │   │   └── CronExpressionInterface.php
│   │   ├── Query/
│   │   │   └── TaskInformation/
│   │   │       ├── TaskInformation.php
│   │   │       ├── TaskInformationHandler.php
│   │   │       └── TaskInformationView.php
│   │   └── Service/
│   │       ├── ClosureSerializerInterface.php
│   │       ├── ConfigurationInterface.php
│   │       └── LoggerFactoryInterface.php
│   ├── Application.php
│   ├── CacheDirectoryFactory/
│   │   └── CacheDirectoryFactory.php
│   ├── Clock/
│   │   ├── Clock.php
│   │   └── ClockInterface.php
│   ├── Configuration/
│   │   ├── ConfigFileNotExistsException.php
│   │   ├── ConfigFileNotReadableException.php
│   │   ├── Configuration.php
│   │   ├── ConfigurationParser.php
│   │   ├── ConfigurationParserInterface.php
│   │   ├── Definition.php
│   │   └── FileParser.php
│   ├── Console/
│   │   └── Command/
│   │       ├── Command.php
│   │       ├── ConfigGeneratorCommand.php
│   │       ├── ScheduleListCommand.php
│   │       ├── ScheduleRunCommand.php
│   │       └── TaskGeneratorCommand.php
│   ├── EnvFlags/
│   │   └── EnvFlags.php
│   ├── Event.php
│   ├── EventRunner.php
│   ├── Exception/
│   │   ├── CrunzException.php
│   │   ├── EmptyTimezoneException.php
│   │   ├── MailerException.php
│   │   ├── NotImplementedException.php
│   │   ├── TaskNotExistException.php
│   │   └── WrongTaskNumberException.php
│   ├── Filesystem/
│   │   ├── Filesystem.php
│   │   └── FilesystemInterface.php
│   ├── Finder/
│   │   ├── Finder.php
│   │   └── FinderInterface.php
│   ├── HttpClient/
│   │   ├── CurlHttpClient.php
│   │   ├── FallbackHttpClient.php
│   │   ├── HttpClientException.php
│   │   ├── HttpClientInterface.php
│   │   ├── HttpClientLoggerDecorator.php
│   │   └── StreamHttpClient.php
│   ├── Infrastructure/
│   │   ├── Dragonmantank/
│   │   │   └── CronExpression/
│   │   │       ├── DragonmantankCronExpression.php
│   │   │       └── DragonmantankCronExpressionFactory.php
│   │   ├── Laravel/
│   │   │   └── LaravelClosureSerializer.php
│   │   └── Psr/
│   │       └── Logger/
│   │           ├── EnabledLoggerDecorator.php
│   │           ├── PsrStreamLogger.php
│   │           └── PsrStreamLoggerFactory.php
│   ├── Invoker.php
│   ├── Logger/
│   │   ├── ConsoleLogger.php
│   │   ├── ConsoleLoggerInterface.php
│   │   ├── Logger.php
│   │   └── LoggerFactory.php
│   ├── Mailer.php
│   ├── Output/
│   │   └── OutputFactory.php
│   ├── Path/
│   │   └── Path.php
│   ├── Pinger/
│   │   ├── PingableException.php
│   │   ├── PingableInterface.php
│   │   └── PingableTrait.php
│   ├── Process/
│   │   └── Process.php
│   ├── Schedule/
│   │   └── ScheduleFactory.php
│   ├── Schedule.php
│   ├── Stubs/
│   │   └── BasicTask.php
│   ├── Task/
│   │   ├── Collection.php
│   │   ├── CollectionInterface.php
│   │   ├── Loader.php
│   │   ├── LoaderInterface.php
│   │   ├── TaskException.php
│   │   ├── TaskNumber.php
│   │   ├── Timezone.php
│   │   └── WrongTaskInstanceException.php
│   ├── Timezone/
│   │   ├── Provider.php
│   │   └── ProviderInterface.php
│   └── UserInterface/
│       └── Cli/
│           ├── ClosureRunCommand.php
│           └── DebugTaskCommand.php
└── tests/
    ├── EndToEnd/
    │   ├── ClosureRunTest.php
    │   ├── ConfigProviderTest.php
    │   ├── ConfigRecognitionTest.php
    │   ├── DebugTaskTest.php
    │   ├── LoggerTest.php
    │   ├── TasksSourceRecognitionTest.php
    │   ├── VersionTest.php
    │   └── WrongTaskTest.php
    ├── Functional/
    │   ├── ConfigProviderTest.php
    │   ├── DifferentBaseCacheDirTest.php
    │   ├── ScheduleListTest.php
    │   ├── ScheduleRunTest.php
    │   └── TaskGeneratorTest.php
    ├── TestCase/
    │   ├── EndToEnd/
    │   │   └── Environment/
    │   │       ├── Environment.php
    │   │       └── EnvironmentBuilder.php
    │   ├── EndToEndTestCase.php
    │   ├── FakeConfiguration.php
    │   ├── FakeLoader.php
    │   ├── FakeTaskCollection.php
    │   ├── Faker.php
    │   ├── Logger/
    │   │   ├── NullLogger.php
    │   │   └── SpyPsrLogger.php
    │   ├── SerializableTaskRunnerStub.php
    │   ├── TaskRunnerStub.php
    │   ├── TemporaryFile.php
    │   ├── TestClock.php
    │   └── UnitTestCase.php
    ├── Unit/
    │   ├── Application/
    │   │   ├── Cron/
    │   │   │   └── AbstractCronExpressionTestCase.php
    │   │   └── Query/
    │   │       └── TaskInformation/
    │   │           └── TaskInformationHandlerTest.php
    │   ├── CacheDirectoryFactory/
    │   │   └── CacheDirectoryFactoryTest.php
    │   ├── Configuration/
    │   │   ├── ConfigurationParserTest.php
    │   │   ├── ConfigurationTest.php
    │   │   └── FileParserTest.php
    │   ├── Console/
    │   │   └── Command/
    │   │       ├── ScheduleListCommandTest.php
    │   │       └── ScheduleRunCommandTest.php
    │   ├── EnvFlags/
    │   │   └── EnvFlagsTest.php
    │   ├── EventRunnerTest.php
    │   ├── EventTest.php
    │   ├── Filesystem/
    │   │   └── FilesystemTest.php
    │   ├── Finder/
    │   │   └── FinderTest.php
    │   ├── HttpClient/
    │   │   └── StreamHttpClientTest.php
    │   ├── Infrastructure/
    │   │   ├── Dragonmantank/
    │   │   │   └── CronExpression/
    │   │   │       └── DragonmantankCronExpressionTestCase.php
    │   │   └── Psr/
    │   │       └── Logger/
    │   │           ├── EnabledLoggerDecoratorTest.php
    │   │           ├── PsrStreamLoggerFactoryTest.php
    │   │           └── PsrStreamLoggerTest.php
    │   ├── InvokerTest.php
    │   ├── Logger/
    │   │   ├── ConsoleLoggerTest.php
    │   │   └── LoggerFactoryTest.php
    │   ├── MailerTest.php
    │   ├── Output/
    │   │   └── OutputFactoryTest.php
    │   ├── Path/
    │   │   └── PathTest.php
    │   ├── Pingable.php
    │   ├── Pinger/
    │   │   └── PingableTest.php
    │   ├── Process/
    │   │   └── ProcessTest.php
    │   ├── Schedule/
    │   │   └── ScheduleFactoryTest.php
    │   ├── ScheduleTest.php
    │   ├── Service/
    │   │   ├── AbstractClosureSerializerTestCase.php
    │   │   ├── LaravelClosureSerializerTest.php
    │   │   └── LaravelClosureSerializerTestCase.php
    │   ├── Task/
    │   │   ├── TaskNumberTest.php
    │   │   └── TimezoneTest.php
    │   ├── Timezone/
    │   │   └── ProviderTest.php
    │   └── UserInterface/
    │       └── Cli/
    │           └── ClosureRunCommandTest.php
    ├── crunz.yml
    ├── resources/
    │   ├── fixtures/
    │   │   └── finder/
    │   │       ├── direct/
    │   │       │   └── directHere.php
    │   │       └── symlink/
    │   │           └── symlinkHere.php
    │   └── tasks/
    │       ├── ClosureTasks.php
    │       ├── CustomOutputTasks.php
    │       ├── FailTasks.php
    │       ├── NoOverlappingClosureTasks.php
    │       ├── PhpVersionTasks.php
    │       └── WrongTasks.php
    └── tasks/
        └── TestTasks.php
Download .txt
SYMBOL INDEX (758 symbols across 138 files)

FILE: src/Application.php
  class Application (line 27) | class Application extends SymfonyApplication
    method __construct (line 66) | public function __construct(string $appName, string $appVersion)
    method run (line 91) | public function run(?InputInterface $input = null, ?OutputInterface $o...
    method initializeContainer (line 112) | private function initializeContainer(): void
    method buildContainer (line 159) | private function buildContainer()
    method dumpContainer (line 176) | private function dumpContainer(
    method createBaseCacheDirectory (line 199) | private function createBaseCacheDirectory()
    method getBaseCacheDir (line 219) | private function getBaseCacheDir(): string
    method getContainerCacheDir (line 227) | private function getContainerCacheDir()
    method registerDeprecationHandler (line 240) | private function registerDeprecationHandler(): void

FILE: src/Application/Cron/CronExpressionFactoryInterface.php
  type CronExpressionFactoryInterface (line 7) | interface CronExpressionFactoryInterface
    method createFromString (line 9) | public function createFromString(string $cronExpression): CronExpressi...

FILE: src/Application/Cron/CronExpressionInterface.php
  type CronExpressionInterface (line 7) | interface CronExpressionInterface
    method multipleRunDates (line 10) | public function multipleRunDates(

FILE: src/Application/Query/TaskInformation/TaskInformation.php
  class TaskInformation (line 9) | final class TaskInformation
    method __construct (line 11) | public function __construct(private readonly TaskNumber $taskNumber)
    method taskNumber (line 15) | public function taskNumber(): TaskNumber

FILE: src/Application/Query/TaskInformation/TaskInformationHandler.php
  class TaskInformationHandler (line 15) | final class TaskInformationHandler
    method __construct (line 17) | public function __construct(
    method handle (line 27) | public function handle(TaskInformation $taskInformation): TaskInformat...
    method getEventProperties (line 82) | private function getEventProperties(Event $event, array $properties): ...

FILE: src/Application/Query/TaskInformation/TaskInformationView.php
  class TaskInformationView (line 7) | final class TaskInformationView
    method __construct (line 12) | public function __construct(
    method command (line 24) | public function command(): string|object
    method description (line 29) | public function description(): string
    method cronExpression (line 34) | public function cronExpression(): string
    method timeZone (line 39) | public function timeZone(): ?\DateTimeZone
    method configTimeZone (line 44) | public function configTimeZone(): \DateTimeZone
    method nextRuns (line 50) | public function nextRuns(): array
    method preventOverlapping (line 55) | public function preventOverlapping(): bool

FILE: src/Application/Service/ClosureSerializerInterface.php
  type ClosureSerializerInterface (line 7) | interface ClosureSerializerInterface
    method serialize (line 9) | public function serialize(\Closure $closure): string;
    method unserialize (line 11) | public function unserialize(string $serializedClosure): \Closure;
    method closureCode (line 13) | public function closureCode(\Closure $closure): string;

FILE: src/Application/Service/ConfigurationInterface.php
  type ConfigurationInterface (line 7) | interface ConfigurationInterface
    method get (line 12) | public function get(string $key, mixed $default = null): mixed;
    method withNewEntry (line 17) | public function withNewEntry(string $key, mixed $value): Configuration...
    method getSourcePath (line 19) | public function getSourcePath(): string;

FILE: src/Application/Service/LoggerFactoryInterface.php
  type LoggerFactoryInterface (line 12) | interface LoggerFactoryInterface
    method create (line 14) | public function create(ConfigurationInterface $configuration): LoggerI...

FILE: src/CacheDirectoryFactory/CacheDirectoryFactory.php
  class CacheDirectoryFactory (line 10) | final class CacheDirectoryFactory
    method generate (line 17) | public function generate(): Path

FILE: src/Clock/Clock.php
  class Clock (line 7) | final class Clock implements ClockInterface
    method now (line 9) | public function now(): \DateTimeImmutable

FILE: src/Clock/ClockInterface.php
  type ClockInterface (line 7) | interface ClockInterface
    method now (line 9) | public function now(): \DateTimeImmutable;

FILE: src/Configuration/ConfigFileNotExistsException.php
  class ConfigFileNotExistsException (line 9) | final class ConfigFileNotExistsException extends CrunzException
    method fromFilePath (line 11) | public static function fromFilePath(string $filePath): self

FILE: src/Configuration/ConfigFileNotReadableException.php
  class ConfigFileNotReadableException (line 9) | final class ConfigFileNotReadableException extends CrunzException
    method fromFilePath (line 11) | public static function fromFilePath(string $filePath): self

FILE: src/Configuration/Configuration.php
  class Configuration (line 11) | final class Configuration implements ConfigurationInterface
    method __construct (line 16) | public function __construct(
    method get (line 25) | public function get(string $key, mixed $default = null): mixed
    method withNewEntry (line 53) | public function withNewEntry(string $key, mixed $value): Configuration...
    method getSourcePath (line 77) | public function getSourcePath(): string

FILE: src/Configuration/ConfigurationParser.php
  class ConfigurationParser (line 14) | final class ConfigurationParser implements ConfigurationParserInterface
    method __construct (line 16) | public function __construct(
    method parseConfig (line 25) | public function parseConfig(): array
    method configFilePath (line 61) | private function configFilePath(): string

FILE: src/Configuration/ConfigurationParserInterface.php
  type ConfigurationParserInterface (line 7) | interface ConfigurationParserInterface
    method parseConfig (line 10) | public function parseConfig(): array;

FILE: src/Configuration/Definition.php
  class Definition (line 11) | class Definition implements ConfigurationInterface
    method getConfigTreeBuilder (line 13) | public function getConfigTreeBuilder(): TreeBuilder

FILE: src/Configuration/FileParser.php
  class FileParser (line 9) | class FileParser
    method __construct (line 11) | public function __construct(private readonly Yaml $yamlParser)
    method parse (line 21) | public function parse(string $configPath): array

FILE: src/Console/Command/Command.php
  class Command (line 9) | class Command extends BaseCommand

FILE: src/Console/Command/ConfigGeneratorCommand.php
  class ConfigGeneratorCommand (line 16) | final class ConfigGeneratorCommand extends Command
    method __construct (line 20) | public function __construct(
    method configure (line 31) | protected function configure(): void
    method execute (line 40) | protected function execute(InputInterface $input, OutputInterface $out...
    method askForTimezone (line 97) | protected function askForTimezone(SymfonyStyle $symfonyStyleIo)
    method updateTimezone (line 123) | private function updateTimezone(

FILE: src/Console/Command/ScheduleListCommand.php
  class ScheduleListCommand (line 18) | class ScheduleListCommand extends \Symfony\Component\Console\Command\Com...
    method __construct (line 27) | public function __construct(
    method configure (line 38) | protected function configure(): void
    method execute (line 68) | protected function execute(InputInterface $input, OutputInterface $out...
    method tasks (line 100) | private function tasks(string $source): array
    method resolveFormat (line 128) | private function resolveFormat(InputInterface $input): string
    method printList (line 156) | private function printList(

FILE: src/Console/Command/ScheduleRunCommand.php
  class ScheduleRunCommand (line 20) | class ScheduleRunCommand extends Command
    method __construct (line 22) | public function __construct(
    method configure (line 36) | protected function configure(): void
    method execute (line 70) | protected function execute(InputInterface $input, OutputInterface $out...

FILE: src/Console/Command/TaskGeneratorCommand.php
  class TaskGeneratorCommand (line 16) | class TaskGeneratorCommand extends Command
    method __construct (line 38) | public function __construct(
    method configure (line 48) | protected function configure(): void
    method execute (line 107) | protected function execute(InputInterface $input, OutputInterface $out...
    method save (line 140) | protected function save()
    method ask (line 154) | protected function ask($question)
    method outputPath (line 167) | protected function outputPath()
    method outputFile (line 187) | protected function outputFile()
    method getStub (line 204) | protected function getStub()
    method type (line 224) | protected function type()
    method replaceFrequency (line 232) | protected function replaceFrequency(): self
    method replaceConstraint (line 242) | protected function replaceConstraint(): self
    method replaceCommand (line 249) | protected function replaceCommand(): self
    method replacePath (line 257) | protected function replacePath(): self
    method replaceDescription (line 265) | protected function replaceDescription(): self
    method optionString (line 273) | private function optionString(string $name): string

FILE: src/EnvFlags/EnvFlags.php
  class EnvFlags (line 9) | final class EnvFlags
    method isDeprecationHandlerEnabled (line 15) | public function isDeprecationHandlerEnabled()
    method isContainerDebugEnabled (line 28) | public function isContainerDebugEnabled()
    method disableContainerDebug (line 41) | public function disableContainerDebug(): void
    method enableContainerDebug (line 49) | public function enableContainerDebug(): void
    method enableDeprecationHandler (line 57) | public function enableDeprecationHandler(): void
    method disableDeprecationHandler (line 65) | public function disableDeprecationHandler(): void

FILE: src/Event.php
  class Event (line 28) | class Event implements PingableInterface
    method __construct (line 185) | public function __construct(protected $id, $command)
    method in (line 198) | public function in($directory)
    method nullOutput (line 210) | public function nullOutput()
    method buildCommand (line 220) | public function buildCommand()
    method isClosure (line 253) | public function isClosure()
    method isDue (line 263) | public function isDue(\DateTimeZone $timeZone)
    method filtersPass (line 273) | public function filtersPass(\DateTimeZone $timeZone)
    method wholeOutput (line 293) | public function wholeOutput()
    method start (line 303) | public function start()
    method cron (line 331) | public function cron(string $expression): self
    method hourly (line 356) | public function hourly(): self
    method hourlyAt (line 361) | public function hourlyAt(int $minute): self
    method daily (line 377) | public function daily(): self
    method on (line 389) | public function on($date)
    method at (line 416) | public function at($time): self
    method dailyAt (line 426) | public function dailyAt($time): self
    method between (line 449) | public function between($from, $to)
    method from (line 462) | public function from($datetime)
    method to (line 478) | public function to($datetime)
    method twiceDaily (line 493) | public function twiceDaily($first = 1, $second = 13): self
    method weekdays (line 506) | public function weekdays(): self
    method mondays (line 514) | public function mondays(): self
    method tuesdays (line 522) | public function tuesdays(): self
    method wednesdays (line 530) | public function wednesdays(): self
    method thursdays (line 538) | public function thursdays(): self
    method fridays (line 546) | public function fridays(): self
    method saturdays (line 554) | public function saturdays(): self
    method sundays (line 562) | public function sundays(): self
    method weekly (line 570) | public function weekly(): self
    method weeklyOn (line 580) | public function weeklyOn(int|string $day, $time = '0:0'): self
    method monthly (line 590) | public function monthly(): self
    method quarterly (line 598) | public function quarterly(): self
    method yearly (line 606) | public function yearly(): self
    method days (line 614) | public function days(mixed $days): self
    method hour (line 624) | public function hour(mixed $value): self
    method minute (line 634) | public function minute(mixed $value): self
    method dayOfMonth (line 644) | public function dayOfMonth(mixed $value): self
    method month (line 654) | public function month(mixed $value): self
    method dayOfWeek (line 664) | public function dayOfWeek(mixed $value): self
    method timezone (line 676) | public function timezone(\DateTimeZone|string $timezone)
    method user (line 690) | public function user($user)
    method preventOverlapping (line 711) | public function preventOverlapping(?object $store = null)
    method when (line 751) | public function when(\Closure $callback)
    method skip (line 763) | public function skip(\Closure $callback)
    method sendOutputTo (line 778) | public function sendOutputTo($location, $append = false)
    method appendOutputTo (line 794) | public function appendOutputTo($location)
    method before (line 804) | public function before(\Closure $callback)
    method after (line 816) | public function after(\Closure $callback)
    method then (line 826) | public function then(\Closure $callback)
    method name (line 840) | public function name($description)
    method getProcess (line 850) | public function getProcess()
    method description (line 862) | public function description($description)
    method every (line 875) | public function every($unit = null, $value = null): self
    method getId (line 890) | public function getId(): string|int
    method getSummaryForDisplay (line 900) | public function getSummaryForDisplay()
    method getCommandForDisplay (line 914) | public function getCommandForDisplay()
    method getExpression (line 924) | public function getExpression()
    method getFrom (line 932) | public function getFrom(): \DateTime|string|null
    method getTo (line 940) | public function getTo(): \DateTime|string|null
    method setCommand (line 952) | public function setCommand($command)
    method getCommand (line 962) | public function getCommand(): string|\Closure
    method getWorkingDirectory (line 972) | public function getWorkingDirectory()
    method getOutputStream (line 982) | public function getOutputStream()
    method beforeCallbacks (line 992) | public function beforeCallbacks()
    method afterCallbacks (line 1002) | public function afterCallbacks()
    method errorCallbacks (line 1008) | public function errorCallbacks()
    method refreshLock (line 1016) | public function refreshLock(): void
    method everyMinute (line 1036) | public function everyMinute(): self
    method everyTwoMinutes (line 1041) | public function everyTwoMinutes(): self
    method everyThreeMinutes (line 1046) | public function everyThreeMinutes(): self
    method everyFourMinutes (line 1051) | public function everyFourMinutes(): self
    method everyFiveMinutes (line 1056) | public function everyFiveMinutes(): self
    method everyTenMinutes (line 1061) | public function everyTenMinutes(): self
    method everyFifteenMinutes (line 1066) | public function everyFifteenMinutes(): self
    method everyThirtyMinutes (line 1071) | public function everyThirtyMinutes(): self
    method everyTwoHours (line 1076) | public function everyTwoHours(): self
    method everyThreeHours (line 1081) | public function everyThreeHours(): self
    method everyFourHours (line 1086) | public function everyFourHours(): self
    method everySixHours (line 1091) | public function everySixHours(): self
    method createLockObject (line 1101) | protected function createLockObject()
    method releaseLock (line 1116) | protected function releaseLock(): void
    method getDefaultOutput (line 1129) | protected function getDefaultOutput()
    method sudo (line 1141) | protected function sudo($user)
    method serializeClosure (line 1151) | protected function serializeClosure(\Closure $closure)
    method expressionPasses (line 1167) | protected function expressionPasses(\DateTimeZone $timeZone)
    method notYet (line 1195) | protected function notYet($datetime, \DateTimeZone $timeZone): bool
    method past (line 1208) | protected function past($datetime, \DateTimeZone $timeZone): bool
    method spliceIntoPosition (line 1222) | protected function spliceIntoPosition($position, $value): self
    method applyMask (line 1238) | protected function applyMask($unit)
    method lock (line 1252) | protected function lock(): void
    method addErrorCallback (line 1258) | private function addErrorCallback(\Closure $closure): void
    method setProcess (line 1266) | private function setProcess(Process $process): void
    method createDefaultLockStore (line 1276) | private function createDefaultLockStore()
    method lockKey (line 1296) | private function lockKey(): string
    method checkLockFactory (line 1311) | private function checkLockFactory(): void
    method getClock (line 1320) | private function getClock(): ClockInterface
    method closureSerializer (line 1329) | private function closureSerializer(): ClosureSerializerInterface
    method isWindows (line 1338) | private function isWindows(): bool
    method timeZonedNow (line 1349) | private function timeZonedNow(\DateTimeZone $timeZone): \DateTimeImmut...

FILE: src/EventRunner.php
  class EventRunner (line 15) | class EventRunner
    method __construct (line 23) | public function __construct(
    method handle (line 34) | public function handle(OutputInterface $output, array $schedules = [])...
    method start (line 58) | protected function start(Event $event): void
    method manageStartedEvents (line 88) | protected function manageStartedEvents(): void
    method invoke (line 162) | protected function invoke(array $callbacks = [], array $parameters = [])
    method handleOutput (line 175) | protected function handleOutput(Event $event): void
    method handleError (line 209) | protected function handleError(Event $event): void
    method formatEventOutput (line 240) | protected function formatEventOutput(Event $event)
    method formatEventError (line 253) | protected function formatEventError(Event $event)
    method display (line 265) | protected function display($output): void
    method pingBefore (line 272) | private function pingBefore(PingableInterface $schedule): void
    method pingAfter (line 288) | private function pingAfter(PingableInterface $schedule): void
    method logger (line 304) | private function logger(): Logger

FILE: src/Exception/CrunzException.php
  class CrunzException (line 7) | class CrunzException extends \Exception

FILE: src/Exception/EmptyTimezoneException.php
  class EmptyTimezoneException (line 7) | class EmptyTimezoneException extends CrunzException

FILE: src/Exception/MailerException.php
  class MailerException (line 7) | final class MailerException extends CrunzException

FILE: src/Exception/NotImplementedException.php
  class NotImplementedException (line 7) | class NotImplementedException extends CrunzException

FILE: src/Exception/TaskNotExistException.php
  class TaskNotExistException (line 7) | class TaskNotExistException extends CrunzException

FILE: src/Exception/WrongTaskNumberException.php
  class WrongTaskNumberException (line 7) | class WrongTaskNumberException extends CrunzException

FILE: src/Filesystem/Filesystem.php
  class Filesystem (line 9) | final class Filesystem implements FilesystemInterface
    method getCwd (line 13) | public function getCwd()
    method fileExists (line 24) | public function fileExists($filePath)
    method tempDir (line 29) | public function tempDir()
    method removeDirectory (line 34) | public function removeDirectory($directoryPath, $ignoredPaths = []): void
    method dumpFile (line 70) | public function dumpFile($filePath, $content): void
    method createDirectory (line 78) | public function createDirectory($directoryPath): void
    method copy (line 99) | public function copy($sourceFile, $targetFile): void
    method projectRootDirectory (line 104) | public function projectRootDirectory()
    method readContent (line 129) | public function readContent($filePath)

FILE: src/Filesystem/FilesystemInterface.php
  type FilesystemInterface (line 9) | interface FilesystemInterface
    method getCwd (line 12) | public function getCwd();
    method fileExists (line 19) | public function fileExists($filePath);
    method tempDir (line 22) | public function tempDir();
    method removeDirectory (line 28) | public function removeDirectory($directoryPath, $ignoredPaths = []): v...
    method dumpFile (line 34) | public function dumpFile($filePath, $content): void;
    method createDirectory (line 37) | public function createDirectory($directoryPath): void;
    method copy (line 43) | public function copy($sourceFile, $targetFile): void;
    method projectRootDirectory (line 46) | public function projectRootDirectory();
    method readContent (line 53) | public function readContent($filePath);

FILE: src/Finder/Finder.php
  class Finder (line 9) | final class Finder implements FinderInterface
    method find (line 11) | public function find(Path $directory, $suffix)

FILE: src/Finder/FinderInterface.php
  type FinderInterface (line 9) | interface FinderInterface
    method find (line 16) | public function find(Path $directory, $suffix);

FILE: src/HttpClient/CurlHttpClient.php
  class CurlHttpClient (line 7) | final class CurlHttpClient implements HttpClientInterface
    method ping (line 9) | public function ping($url): void

FILE: src/HttpClient/FallbackHttpClient.php
  class FallbackHttpClient (line 9) | final class FallbackHttpClient implements HttpClientInterface
    method __construct (line 14) | public function __construct(
    method ping (line 21) | public function ping($url): void
    method chooseHttpClient (line 28) | private function chooseHttpClient(): HttpClientInterface

FILE: src/HttpClient/HttpClientException.php
  class HttpClientException (line 9) | class HttpClientException extends CrunzException

FILE: src/HttpClient/HttpClientInterface.php
  type HttpClientInterface (line 7) | interface HttpClientInterface
    method ping (line 14) | public function ping($url): void;

FILE: src/HttpClient/HttpClientLoggerDecorator.php
  class HttpClientLoggerDecorator (line 9) | final class HttpClientLoggerDecorator implements HttpClientInterface
    method __construct (line 11) | public function __construct(private readonly HttpClientInterface $http...
    method ping (line 15) | public function ping($url): void

FILE: src/HttpClient/StreamHttpClient.php
  class StreamHttpClient (line 7) | final class StreamHttpClient implements HttpClientInterface
    method ping (line 14) | public function ping($url): void

FILE: src/Infrastructure/Dragonmantank/CronExpression/DragonmantankCronExpression.php
  class DragonmantankCronExpression (line 10) | final class DragonmantankCronExpression implements CronExpressionInterface
    method __construct (line 12) | public function __construct(private readonly CronExpression $innerCron...
    method multipleRunDates (line 16) | public function multipleRunDates(int $total, \DateTimeImmutable $now, ...

FILE: src/Infrastructure/Dragonmantank/CronExpression/DragonmantankCronExpressionFactory.php
  class DragonmantankCronExpressionFactory (line 11) | final class DragonmantankCronExpressionFactory implements CronExpression...
    method createFromString (line 13) | public function createFromString(string $cronExpression): CronExpressi...

FILE: src/Infrastructure/Laravel/LaravelClosureSerializer.php
  class LaravelClosureSerializer (line 11) | final class LaravelClosureSerializer implements ClosureSerializerInterface
    method serialize (line 13) | public function serialize(\Closure $closure): string
    method unserialize (line 22) | public function unserialize(string $serializedClosure): \Closure
    method closureCode (line 29) | public function closureCode(\Closure $closure): string
    method extractWrapper (line 36) | private function extractWrapper(string $serializedClosure): Serializab...

FILE: src/Infrastructure/Psr/Logger/EnabledLoggerDecorator.php
  class EnabledLoggerDecorator (line 12) | final class EnabledLoggerDecorator extends AbstractLogger
    method __construct (line 14) | public function __construct(
    method log (line 20) | public function log($level, string|\Stringable $message, array $contex...

FILE: src/Infrastructure/Psr/Logger/PsrStreamLogger.php
  class PsrStreamLogger (line 12) | final class PsrStreamLogger extends AbstractLogger
    method __construct (line 23) | public function __construct(
    method __destruct (line 36) | public function __destruct()
    method log (line 42) | public function log(
    method createInfoHandler (line 69) | private function createInfoHandler()
    method createErrorHandler (line 79) | private function createErrorHandler()
    method initializeHandler (line 89) | private function initializeHandler(string $path)
    method closeStream (line 125) | private function closeStream($stream): void
    method dirFromStream (line 134) | private function dirFromStream(string $stream): ?string
    method formatContext (line 154) | private function formatContext(array $data): string
    method formatDate (line 163) | private function formatDate(): string
    method replaceNewlines (line 176) | private function replaceNewlines(string $message): string

FILE: src/Infrastructure/Psr/Logger/PsrStreamLoggerFactory.php
  class PsrStreamLoggerFactory (line 13) | final class PsrStreamLoggerFactory implements LoggerFactoryInterface
    method __construct (line 15) | public function __construct(private readonly Timezone $timezoneProvide...
    method create (line 19) | public function create(ConfigurationInterface $configuration): LoggerI...

FILE: src/Invoker.php
  class Invoker (line 7) | class Invoker
    method call (line 16) | public function call($closure, array $parameters = [], $buffer = false...

FILE: src/Logger/ConsoleLogger.php
  class ConsoleLogger (line 9) | final class ConsoleLogger implements ConsoleLoggerInterface
    method __construct (line 11) | public function __construct(private readonly SymfonyStyle $symfonyStyle)
    method normal (line 18) | public function normal($message): void
    method verbose (line 26) | public function verbose($message): void
    method veryVerbose (line 34) | public function veryVerbose($message): void
    method debug (line 44) | public function debug($message): void
    method write (line 53) | private function write($message, $verbosity): void

FILE: src/Logger/ConsoleLoggerInterface.php
  type ConsoleLoggerInterface (line 9) | interface ConsoleLoggerInterface
    method normal (line 20) | public function normal($message): void;
    method verbose (line 25) | public function verbose($message): void;
    method veryVerbose (line 30) | public function veryVerbose($message): void;
    method debug (line 37) | public function debug($message): void;

FILE: src/Logger/Logger.php
  class Logger (line 9) | class Logger
    method __construct (line 11) | public function __construct(private readonly LoggerInterface $psrLogger)
    method info (line 18) | public function info(string $message): void
    method error (line 26) | public function error(string $message): void
    method log (line 31) | private function log(string $content, string $level): void

FILE: src/Logger/LoggerFactory.php
  class LoggerFactory (line 14) | class LoggerFactory
    method __construct (line 18) | public function __construct(
    method create (line 26) | public function create(): Logger
    method createEvent (line 35) | public function createEvent(string $output): Logger
    method loggerFactory (line 44) | private function loggerFactory(): LoggerFactoryInterface
    method initializeLoggerFactory (line 49) | private function initializeLoggerFactory(): LoggerFactoryInterface
    method createLoggerFactory (line 74) | private function createLoggerFactory(

FILE: src/Mailer.php
  class Mailer (line 14) | class Mailer
    method __construct (line 19) | public function __construct(private readonly ConfigurationInterface $c...
    method send (line 28) | public function send(string $subject, string $message): void
    method getMailer (line 42) | private function getMailer(): SymfonyMailer
    method getSmtpTransport (line 70) | private function getSmtpTransport(): Transport\TransportInterface
    method getSendMailTransport (line 91) | private function getSendMailTransport(): Transport\TransportInterface
    method getMessage (line 98) | private function getMessage(string $subject, string $message): Email
    method config (line 114) | private function config(string $key): mixed

FILE: src/Output/OutputFactory.php
  class OutputFactory (line 11) | final class OutputFactory
    method __construct (line 13) | public function __construct(private readonly InputInterface $input)
    method createOutput (line 17) | public function createOutput(): OutputInterface

FILE: src/Path/Path.php
  class Path (line 9) | final class Path
    method __construct (line 11) | private function __construct(private readonly string $path)
    method create (line 20) | public static function create(array $parts): self
    method fromStrings (line 38) | public static function fromStrings(string ...$parts): self
    method toString (line 43) | public function toString(): string

FILE: src/Pinger/PingableException.php
  class PingableException (line 9) | class PingableException extends CrunzException

FILE: src/Pinger/PingableInterface.php
  type PingableInterface (line 7) | interface PingableInterface
    method pingBefore (line 14) | public function pingBefore($url);
    method hasPingBefore (line 21) | public function hasPingBefore();
    method thenPing (line 28) | public function thenPing($url);
    method hasPingAfter (line 35) | public function hasPingAfter();
    method getPingBeforeUrl (line 42) | public function getPingBeforeUrl();
    method getPingAfterUrl (line 49) | public function getPingAfterUrl();

FILE: src/Pinger/PingableTrait.php
  type PingableTrait (line 7) | trait PingableTrait
    method pingBefore (line 14) | public function pingBefore($url)
    method hasPingBefore (line 23) | public function hasPingBefore()
    method thenPing (line 28) | public function thenPing($url)
    method hasPingAfter (line 37) | public function hasPingAfter()
    method getPingBeforeUrl (line 42) | public function getPingBeforeUrl()
    method getPingAfterUrl (line 51) | public function getPingAfterUrl()
    method checkUrl (line 65) | private function checkUrl($url): void

FILE: src/Process/Process.php
  class Process (line 10) | final class Process
    method __construct (line 13) | private function __construct(private readonly SymfonyProcess $process)
    method fromStringCommand (line 17) | public static function fromStringCommand(string $command, ?string $cwd...
    method fromArrayCommand (line 25) | public static function fromArrayCommand(array $command): self
    method start (line 33) | public function start($callback = null): void
    method wait (line 39) | public function wait(): void
    method startAndWait (line 45) | public function startAndWait(): void
    method setEnv (line 54) | public function setEnv(array $env): void
    method getPid (line 60) | public function getPid(): ?int
    method isRunning (line 66) | public function isRunning(): bool
    method isSuccessful (line 72) | public function isSuccessful(): bool
    method getOutput (line 78) | public function getOutput(): string
    method errorOutput (line 84) | public function errorOutput(): string
    method commandLine (line 90) | public function commandLine(): string

FILE: src/Schedule.php
  class Schedule (line 11) | class Schedule implements PingableInterface
    method run (line 51) | public function run($command, array $parameters = [])
    method before (line 67) | public function before(\Closure $callback)
    method after (line 79) | public function after(\Closure $callback)
    method then (line 89) | public function then(\Closure $callback)
    method onError (line 101) | public function onError(\Closure $callback)
    method beforeCallbacks (line 113) | public function beforeCallbacks()
    method afterCallbacks (line 123) | public function afterCallbacks()
    method errorCallbacks (line 133) | public function errorCallbacks()
    method events (line 145) | public function events(?array $events = null)
    method dueEvents (line 159) | public function dueEvents(\DateTimeZone $timeZone)
    method dismissEvent (line 174) | public function dismissEvent($key)
    method id (line 186) | protected function id()
    method compileParameters (line 197) | protected function compileParameters(array $parameters): string

FILE: src/Schedule/ScheduleFactory.php
  class ScheduleFactory (line 12) | class ScheduleFactory
    method singleTaskSchedule (line 19) | public function singleTaskSchedule(TaskNumber $taskNumber, Schedule .....
    method singleTask (line 30) | public function singleTask(TaskNumber $taskNumber, Schedule ...$schedu...

FILE: src/Task/Collection.php
  class Collection (line 12) | class Collection implements CollectionInterface
    method __construct (line 14) | public function __construct(
    method all (line 21) | public function all(string $source): iterable

FILE: src/Task/CollectionInterface.php
  type CollectionInterface (line 7) | interface CollectionInterface
    method all (line 10) | public function all(string $source): iterable;

FILE: src/Task/Loader.php
  class Loader (line 9) | final class Loader implements LoaderInterface
    method load (line 12) | public function load(\SplFileInfo ...$files): array
    method loadSchedule (line 33) | private function loadSchedule(\SplFileInfo $file)

FILE: src/Task/LoaderInterface.php
  type LoaderInterface (line 9) | interface LoaderInterface
    method load (line 12) | public function load(\SplFileInfo ...$files): array;

FILE: src/Task/TaskException.php
  class TaskException (line 9) | class TaskException extends CrunzException

FILE: src/Task/TaskNumber.php
  class TaskNumber (line 9) | class TaskNumber
    method __construct (line 15) | private function __construct(int $number)
    method fromString (line 31) | public static function fromString($value)
    method asInt (line 46) | public function asInt(): int
    method asArrayIndex (line 51) | public function asArrayIndex(): int

FILE: src/Task/Timezone.php
  class Timezone (line 11) | class Timezone
    method __construct (line 15) | public function __construct(
    method timezoneForComparisons (line 22) | public function timezoneForComparisons(): \DateTimeZone

FILE: src/Task/WrongTaskInstanceException.php
  class WrongTaskInstanceException (line 9) | final class WrongTaskInstanceException extends TaskException
    method fromFilePath (line 11) | public static function fromFilePath(\SplFileInfo $filePath, mixed $sch...

FILE: src/Timezone/Provider.php
  class Provider (line 7) | final class Provider implements ProviderInterface
    method defaultTimezone (line 9) | public function defaultTimezone(): \DateTimeZone

FILE: src/Timezone/ProviderInterface.php
  type ProviderInterface (line 7) | interface ProviderInterface
    method defaultTimezone (line 12) | public function defaultTimezone();

FILE: src/UserInterface/Cli/ClosureRunCommand.php
  class ClosureRunCommand (line 13) | class ClosureRunCommand extends SymfonyCommand
    method __construct (line 15) | public function __construct(private readonly ClosureSerializerInterfac...
    method configure (line 23) | protected function configure(): void
    method execute (line 42) | protected function execute(InputInterface $input, OutputInterface $out...

FILE: src/UserInterface/Cli/DebugTaskCommand.php
  class DebugTaskCommand (line 19) | final class DebugTaskCommand extends Command
    method __construct (line 21) | public function __construct(private readonly TaskInformationHandler $t...
    method configure (line 26) | protected function configure(): void
    method execute (line 38) | protected function execute(InputInterface $input, OutputInterface $out...
    method createTable (line 53) | private function createTable(

FILE: tests/EndToEnd/ClosureRunTest.php
  class ClosureRunTest (line 9) | final class ClosureRunTest extends EndToEndTestCase
    method closure_tasks (line 12) | public function closure_tasks(): void
    method test_prevent_overlapping_works_on_closures (line 34) | public function test_prevent_overlapping_works_on_closures(): void

FILE: tests/EndToEnd/ConfigProviderTest.php
  class ConfigProviderTest (line 13) | final class ConfigProviderTest extends EndToEndTestCase
    method test_config_can_be_published (line 15) | public function test_config_can_be_published(): void

FILE: tests/EndToEnd/ConfigRecognitionTest.php
  class ConfigRecognitionTest (line 10) | final class ConfigRecognitionTest extends EndToEndTestCase
    method search_config_in_cwd (line 13) | public function search_config_in_cwd(): void
    method assertHasTask (line 44) | private function assertHasTask(string $output): void

FILE: tests/EndToEnd/DebugTaskTest.php
  class DebugTaskTest (line 9) | final class DebugTaskTest extends EndToEndTestCase
    method test_task_debug (line 11) | public function test_task_debug(): void
    method extractContentLines (line 52) | private function extractContentLines(string $output): array
    method assertHeader (line 91) | private function assertHeader(string $header, array $lines): void

FILE: tests/EndToEnd/LoggerTest.php
  class LoggerTest (line 9) | final class LoggerTest extends EndToEndTestCase
    method test_outputs_are_logged (line 11) | public function test_outputs_are_logged(): void
    method test_event_logging_override (line 42) | public function test_event_logging_override(): void
    method assertLogRecord (line 69) | private function assertLogRecord(

FILE: tests/EndToEnd/TasksSourceRecognitionTest.php
  class TasksSourceRecognitionTest (line 10) | final class TasksSourceRecognitionTest extends EndToEndTestCase
    method search_tasks_in_cwd (line 13) | public function search_tasks_in_cwd(): void
    method search_tasks_in_cwd_with_config (line 30) | public function search_tasks_in_cwd_with_config(): void
    method assertHasTask (line 51) | private function assertHasTask(string $output): void

FILE: tests/EndToEnd/VersionTest.php
  class VersionTest (line 10) | final class VersionTest extends EndToEndTestCase
    method test_version (line 12) | public function test_version(): void

FILE: tests/EndToEnd/WrongTaskTest.php
  class WrongTaskTest (line 9) | final class WrongTaskTest extends EndToEndTestCase
    method every_task_must_return_crunz_schedule_instance (line 16) | public function every_task_must_return_crunz_schedule_instance(string ...
    method scheduleInstanceProvider (line 37) | public static function scheduleInstanceProvider(): iterable

FILE: tests/Functional/ConfigProviderTest.php
  class ConfigProviderTest (line 11) | class ConfigProviderTest extends TestCase
    method config_already_exists (line 14) | public function config_already_exists(): void

FILE: tests/Functional/DifferentBaseCacheDirTest.php
  class DifferentBaseCacheDirTest (line 11) | final class DifferentBaseCacheDirTest extends TestCase
    method different_base_cache_dir_is_used (line 18) | public function different_base_cache_dir_is_used(): void

FILE: tests/Functional/ScheduleListTest.php
  class ScheduleListTest (line 11) | class ScheduleListTest extends TestCase
    method show_list (line 14) | public function show_list(): void

FILE: tests/Functional/ScheduleRunTest.php
  class ScheduleRunTest (line 11) | class ScheduleRunTest extends TestCase
    method show_list (line 14) | public function show_list(): void

FILE: tests/Functional/TaskGeneratorTest.php
  class TaskGeneratorTest (line 13) | class TaskGeneratorTest extends TestCase
    method setUp (line 19) | public function setUp(): void
    method tearDown (line 33) | public function tearDown(): void
    method generate_task_file (line 39) | public function generate_task_file(): void
    method getInputStream (line 61) | private function getInputStream(string $input)
    method clearTask (line 75) | private function clearTask(): void
    method provideAnswer (line 82) | private function provideAnswer(

FILE: tests/TestCase/EndToEnd/Environment/Environment.php
  class Environment (line 14) | final class Environment
    method __construct (line 31) | public function __construct(
    method __destruct (line 43) | public function __destruct()
    method setUp (line 60) | private function setUp(): void
    method runCrunzCommand (line 69) | public function runCrunzCommand(
    method rootDirectory (line 117) | public function rootDirectory(): string
    method dumpConfig (line 130) | private function dumpConfig(): void
    method copyTasks (line 147) | private function copyTasks(): void
    method dumpComposerJson (line 182) | private function dumpComposerJson(): void
    method composerInstall (line 212) | private function composerInstall(): void
    method createRootDirectory (line 223) | private function createRootDirectory(): void
    method createProcess (line 236) | private function createProcess(string $command, ?string $cwd = null): ...

FILE: tests/TestCase/EndToEnd/Environment/EnvironmentBuilder.php
  class EnvironmentBuilder (line 10) | final class EnvironmentBuilder
    method __construct (line 18) | public function __construct(private readonly FilesystemInterface $file...
    method addTask (line 23) | public function addTask(string $taskName): self
    method changeTaskDirectory (line 30) | public function changeTaskDirectory(Path $path): self
    method withConfig (line 38) | public function withConfig(array $config): self
    method createEnvironment (line 45) | public function createEnvironment(): Environment

FILE: tests/TestCase/EndToEndTestCase.php
  class EndToEndTestCase (line 13) | abstract class EndToEndTestCase extends TestCase
    method createEnvironmentBuilder (line 18) | public function createEnvironmentBuilder(): EnvironmentBuilder
    method normalizeOutput (line 27) | protected function normalizeOutput(string $output): string
    method normalizeProcessOutput (line 43) | protected function normalizeProcessOutput(Process $process): string
    method normalizeProcessErrorOutput (line 48) | protected function normalizeProcessErrorOutput(Process $process): string

FILE: tests/TestCase/FakeConfiguration.php
  class FakeConfiguration (line 10) | final class FakeConfiguration implements ConfigurationInterface
    method __construct (line 32) | public function __construct(array $config = [])
    method get (line 37) | public function get(string $key, mixed $default = null): mixed
    method withNewEntry (line 56) | public function withNewEntry(string $key, mixed $value): Configuration...
    method getSourcePath (line 75) | public function getSourcePath(): string

FILE: tests/TestCase/FakeLoader.php
  class FakeLoader (line 10) | final class FakeLoader implements LoaderInterface
    method __construct (line 13) | public function __construct(private readonly array $schedules = [])
    method load (line 17) | public function load(\SplFileInfo ...$files): array

FILE: tests/TestCase/FakeTaskCollection.php
  class FakeTaskCollection (line 9) | final class FakeTaskCollection implements CollectionInterface
    method __construct (line 12) | public function __construct(private readonly iterable $tasks = [])
    method all (line 16) | public function all(string $source): iterable

FILE: tests/TestCase/Faker.php
  class Faker (line 9) | final class Faker
    method timeZone (line 26) | public static function timeZone(): \DateTimeZone
    method elementFromArray (line 38) | public static function elementFromArray(array $elements): mixed
    method int (line 51) | public static function int(int $min = PHP_INT_MIN, int $max = PHP_INT_...
    method dateTime (line 56) | public static function dateTime(string $start = '-20 years', string $e...
    method words (line 70) | public static function words(int $count = 3): string
    method word (line 82) | public static function word(): string

FILE: tests/TestCase/Logger/NullLogger.php
  class NullLogger (line 9) | final class NullLogger implements ConsoleLoggerInterface
    method normal (line 11) | public function normal($message): void
    method verbose (line 16) | public function verbose($message): void
    method veryVerbose (line 21) | public function veryVerbose($message): void
    method debug (line 26) | public function debug($message): void

FILE: tests/TestCase/Logger/SpyPsrLogger.php
  class SpyPsrLogger (line 9) | final class SpyPsrLogger extends AbstractLogger
    method log (line 14) | public function log($level, string|\Stringable $message, array $contex...
    method getLogs (line 24) | public function getLogs(): array

FILE: tests/TestCase/SerializableTaskRunnerStub.php
  class SerializableTaskRunnerStub (line 7) | final class SerializableTaskRunnerStub
    method __serialize (line 15) | public function __serialize(): array
    method __unserialize (line 24) | public function __unserialize(array $data): void
    method createTask (line 30) | public function createTask(): \Closure

FILE: tests/TestCase/TaskRunnerStub.php
  class TaskRunnerStub (line 7) | final class TaskRunnerStub
    method createTask (line 14) | public function createTask(): \Closure

FILE: tests/TestCase/TemporaryFile.php
  class TemporaryFile (line 9) | final class TemporaryFile
    method __construct (line 13) | public function __construct()
    method __destruct (line 24) | public function __destruct()
    method filePath (line 42) | public function filePath(): string
    method changePermissions (line 48) | public function changePermissions($mode): void
    method contents (line 55) | public function contents(): string
    method checkFileExists (line 68) | private function checkFileExists(): void

FILE: tests/TestCase/TestClock.php
  class TestClock (line 9) | final class TestClock implements ClockInterface
    method __construct (line 11) | public function __construct(private readonly \DateTimeImmutable $now)
    method now (line 15) | public function now(): \DateTimeImmutable

FILE: tests/TestCase/UnitTestCase.php
  class UnitTestCase (line 11) | abstract class UnitTestCase extends TestCase
    method createClosureSerializer (line 15) | public function createClosureSerializer(): ClosureSerializerInterface
    method encodeJson (line 20) | protected static function encodeJson(mixed $data): string

FILE: tests/Unit/Application/Cron/AbstractCronExpressionTestCase.php
  class AbstractCronExpressionTestCase (line 10) | abstract class AbstractCronExpressionTestCase extends TestCase
    method multiple_run_dates (line 19) | public function multiple_run_dates(
    method multipleRunDatesProvider (line 48) | public static function multipleRunDatesProvider(): iterable
    method createExpression (line 88) | abstract protected function createExpression(string $cronExpression): ...

FILE: tests/Unit/Application/Query/TaskInformation/TaskInformationHandlerTest.php
  class TaskInformationHandlerTest (line 19) | final class TaskInformationHandlerTest extends TestCase
    method handle_returns_task_information (line 26) | public function handle_returns_task_information(
    method taskInformationProvider (line 51) | public static function taskInformationProvider(): iterable
    method createHandler (line 113) | private function createHandler(Event $event, \DateTimeZone $comparison...

FILE: tests/Unit/CacheDirectoryFactory/CacheDirectoryFactoryTest.php
  class CacheDirectoryFactoryTest (line 12) | final class CacheDirectoryFactoryTest extends TestCase
    method sys_temp_dir_is_default_directory (line 15) | public function sys_temp_dir_is_default_directory(): void
    method change_cache_directory_through_environment_variable (line 26) | public function change_cache_directory_through_environment_variable():...
    method throw_exception_when_environment_variable_is_empty (line 39) | public function throw_exception_when_environment_variable_is_empty(): ...

FILE: tests/Unit/Configuration/ConfigurationParserTest.php
  class ConfigurationParserTest (line 16) | final class ConfigurationParserTest extends TestCase
    method use_empty_config_when_config_file_not_exists (line 19) | public function use_empty_config_when_config_file_not_exists(): void
    method use_parsed_config_when_config_file_exists (line 34) | public function use_parsed_config_when_config_file_exists(): void
    method createConfigurationParser (line 51) | private function createConfigurationParser(

FILE: tests/Unit/Configuration/ConfigurationTest.php
  class ConfigurationTest (line 13) | final class ConfigurationTest extends TestCase
    method get_can_return_path_split_by_dot (line 16) | public function get_can_return_path_split_by_dot(): void
    method get_return_default_value_if_path_not_exists (line 30) | public function get_return_default_value_if_path_not_exists(): void
    method source_path_is_relative_to_cwd (line 39) | public function source_path_is_relative_to_cwd(): void
    method source_path_fallback_to_tasks_directory (line 50) | public function source_path_fallback_to_tasks_directory(): void
    method set_configuration_key_value (line 60) | public function set_configuration_key_value(): void
    method set_configuration_key_array (line 75) | public function set_configuration_key_array(): void
    method createConfiguration (line 94) | private function createConfiguration(array $config = [], string $cwd =...

FILE: tests/Unit/Configuration/FileParserTest.php
  class FileParserTest (line 14) | class FileParserTest extends TestCase
    method parse_throws_exception_on_non_existing_file (line 17) | public function parse_throws_exception_on_non_existing_file(): void
    method parse_throws_exception_on_non_readable_file (line 29) | public function parse_throws_exception_on_non_readable_file(): void
    method parse_returns_parsed_file_content (line 47) | public function parse_returns_parsed_file_content(): void
    method createFileParser (line 65) | private function createFileParser()
    method isWindows (line 73) | private function isWindows()

FILE: tests/Unit/Console/Command/ScheduleListCommandTest.php
  class ScheduleListCommandTest (line 20) | final class ScheduleListCommandTest extends UnitTestCase
    method test_passing_unsupported_format_fails (line 22) | public function test_passing_unsupported_format_fails(): void
    method test_list_output_format (line 40) | public function test_list_output_format(\Closure $paramsGenerator): void
    method formatProvider (line 75) | public static function formatProvider(): iterable
    method createCommand (line 139) | private function createCommand(array $schedules = []): ScheduleListCom...
    method createInput (line 148) | private function createInput(string $format): InputInterface
    method createScheduleWithTask (line 157) | private static function createScheduleWithTask(

FILE: tests/Unit/Console/Command/ScheduleRunCommandTest.php
  class ScheduleRunCommandTest (line 26) | class ScheduleRunCommandTest extends TestCase
    method force_run_all_tasks (line 29) | public function force_run_all_tasks(): void
    method run_specific_task (line 61) | public function run_specific_task(): void
    method mockTimezoneProvider (line 94) | public static function mockTimezoneProvider(): MockObject&Timezone
    method mockScheduleFactory (line 117) | private function mockScheduleFactory(): ScheduleFactory
    method mockEventRunner (line 132) | private function mockEventRunner(OutputInterface $output): EventRunner
    method mockInput (line 164) | private function mockInput(array $options, array $arguments = []): Inp...
    method mockTaskCollection (line 179) | private function mockTaskCollection(string ...$taskFiles): CollectionI...
    method createTaskFile (line 189) | private function createTaskFile(string $taskContent, TemporaryFile $fi...
    method taskContent (line 200) | private function taskContent(): string
    method phpVersionTaskContent (line 219) | private function phpVersionTaskContent(): string
    method createTaskLoader (line 237) | private function createTaskLoader(): LoaderInterface

FILE: tests/Unit/EnvFlags/EnvFlagsTest.php
  class EnvFlagsTest (line 10) | final class EnvFlagsTest extends TestCase
    method deprecation_handler_status_is_correct (line 17) | public function deprecation_handler_status_is_correct(string $flagValu...
    method deprecation_handler_can_be_disabled (line 26) | public function deprecation_handler_can_be_disabled(): void
    method deprecation_handler_can_be_enabled (line 35) | public function deprecation_handler_can_be_enabled(): void
    method container_debug_flag_is_correct (line 48) | public function container_debug_flag_is_correct(string $flagValue, boo...
    method container_debug_can_be_disabled (line 57) | public function container_debug_can_be_disabled(): void
    method container_debug_can_be_enabled (line 66) | public function container_debug_can_be_enabled(): void
    method statusProvider (line 75) | public static function statusProvider(): iterable
    method containerDebugProvider (line 89) | public static function containerDebugProvider(): iterable
    method assertFlagValue (line 102) | private function assertFlagValue(string $flag, string $expectedValue):...

FILE: tests/Unit/EventRunnerTest.php
  class EventRunnerTest (line 20) | final class EventRunnerTest extends TestCase
    method url_is_pinged_before (line 23) | public function url_is_pinged_before(): void
    method url_is_pinged_after (line 38) | public function url_is_pinged_after(): void
    method test_event_logging_configuration (line 52) | public function test_event_logging_configuration(): void
    method test_lock_is_released_on_error (line 82) | public function test_lock_is_released_on_error(): void
    method createEventRunnerForPing (line 109) | private function createEventRunnerForPing($url)
    method createEventRunner (line 133) | private function createEventRunner(bool $realInvoker = false): EventRu...

FILE: tests/Unit/EventTest.php
  class EventTest (line 17) | final class EventTest extends UnitTestCase
    method setUp (line 29) | public function setUp(): void
    method tearDown (line 37) | public function tearDown(): void
    method test_unit_methods (line 45) | public function test_unit_methods(): void
    method test_low_level_methods (line 75) | public function test_low_level_methods(): void
    method test_weekday_methods (line 108) | public function test_weekday_methods(): void
    method test_cron_life_time (line 129) | public function test_cron_life_time(): void
    method test_get_from (line 165) | public function test_get_from(\Closure $paramsGenerator): void
    method test_get_to (line 183) | public function test_get_to(\Closure $paramsGenerator): void
    method test_get_between (line 201) | public function test_get_between(\Closure $paramsGenerator): void
    method test_cron_conditions (line 213) | public function test_cron_conditions(): void
    method more_than_five_parts_in_cron_expression_results_in_exception (line 231) | public function more_than_five_parts_in_cron_expression_results_in_exc...
    method test_build_command (line 240) | public function test_build_command(): void
    method test_is_due (line 247) | public function test_is_due(): void
    method test_name (line 264) | public function test_name(): void
    method in_change_working_directory_in_build_command_on_windows (line 273) | public function in_change_working_directory_in_build_command_on_window...
    method in_change_working_directory_in_build_command_on_unix (line 288) | public function in_change_working_directory_in_build_command_on_unix()...
    method on_do_not_run_task_every_minute (line 302) | public function on_do_not_run_task_every_minute(): void
    method setting_user_prepend_sudo_to_command (line 312) | public function setting_user_prepend_sudo_to_command(): void
    method custom_user_and_cwd (line 326) | public function custom_user_and_cwd(): void
    method not_implemented_user_change_on_windows (line 341) | public function not_implemented_user_change_on_windows(): void
    method closure_command_have_full_binary_paths (line 360) | public function closure_command_have_full_binary_paths(): void
    method whole_output_catches_stdout_and_stderr (line 380) | public function whole_output_catches_stdout_and_stderr(): void
    method task_will_prevent_overlapping_with_default_store (line 406) | public function task_will_prevent_overlapping_with_default_store(): void
    method task_will_prevent_overlapping_with_semaphore_store (line 412) | public function task_will_prevent_overlapping_with_semaphore_store(): ...
    method test_every_methods (line 422) | public function test_every_methods(string $method, string $expectedCro...
    method test_hourly_at_with_valid_minute (line 437) | public function test_hourly_at_with_valid_minute(): void
    method test_hourly_at_with_invalid_minute (line 451) | public function test_hourly_at_with_invalid_minute(
    method test_non_blocking_store_can_be_passed_to_prevent_overlapping (line 466) | public function test_non_blocking_store_can_be_passed_to_prevent_overl...
    method test_from_respects_time_zone (line 489) | public function test_from_respects_time_zone(\Closure $paramsGenerator...
    method test_to_respects_timezone (line 519) | public function test_to_respects_timezone(\Closure $paramsGenerator): ...
    method deprecatedEveryProvider (line 540) | public static function deprecatedEveryProvider(): iterable
    method everyMethodProvider (line 549) | public static function everyMethodProvider(): iterable
    method fromTimeZoneProvider (line 566) | public static function fromTimeZoneProvider(): iterable
    method toTimeZoneProvider (line 598) | public static function toTimeZoneProvider(): iterable
    method hourlyAtInvalidProvider (line 630) | public static function hourlyAtInvalidProvider(): iterable
    method dateFromToProvider (line 644) | public static function dateFromToProvider(): iterable
    method assertPreventOverlapping (line 668) | private function assertPreventOverlapping(?PersistingStoreInterface $s...
    method createPreventOverlappingEvent (line 678) | private function createPreventOverlappingEvent(?PersistingStoreInterfa...
    method setClockNow (line 689) | private function setClockNow(\DateTimeImmutable $dateTime): void
    method isWindows (line 697) | private function isWindows(): bool
    method createEvent (line 702) | private function createEvent(): Event

FILE: tests/Unit/Filesystem/FilesystemTest.php
  class FilesystemTest (line 12) | final class FilesystemTest extends UnitTestCase
    method cwd_is_correct (line 15) | public function cwd_is_correct(): void
    method file_exists_is_correct (line 27) | public function file_exists_is_correct(string $path, bool $expectedExi...
    method temp_directory_return_system_temp_directory (line 35) | public function temp_directory_return_system_temp_directory(): void
    method remove_directory_removes_directories_recursively (line 43) | public function remove_directory_removes_directories_recursively(): void
    method dump_file_writes_content_to_file (line 65) | public function dump_file_writes_content_to_file(): void
    method create_directory_creates_directory_recursive (line 80) | public function create_directory_creates_directory_recursive(): void
    method copy_files (line 100) | public function copy_files(): void
    method project_root_directory (line 120) | public function project_root_directory(): void
    method read_content_return_file_content (line 128) | public function read_content_return_file_content(): void
    method read_content_throws_exception_when_file_not_exists (line 137) | public function read_content_throws_exception_when_file_not_exists(): ...
    method fileExistsProvider (line 153) | public static function fileExistsProvider(): iterable
    method findProjectRootDirectory (line 170) | private function findProjectRootDirectory(): string

FILE: tests/Unit/Finder/FinderTest.php
  class FinderTest (line 12) | final class FinderTest extends TestCase
    method setUp (line 18) | public function setUp(): void
    method tearDown (line 37) | public function tearDown(): void
    method find_returns_spl_file_info_collection (line 49) | public function find_returns_spl_file_info_collection(string $suffix, ...
    method tasksProvider (line 66) | public static function tasksProvider(): iterable
    method find_files_in_symlinked_folder (line 94) | public function find_files_in_symlinked_folder(): void
    method createFiles (line 114) | private function createFiles(Path ...$files): void
    method isWindows (line 126) | private function isWindows(): bool

FILE: tests/Unit/HttpClient/StreamHttpClientTest.php
  class StreamHttpClientTest (line 11) | final class StreamHttpClientTest extends TestCase
    method ping_fail_with_invalid_address (line 14) | public function ping_fail_with_invalid_address(): void

FILE: tests/Unit/Infrastructure/Dragonmantank/CronExpression/DragonmantankCronExpressionTestCase.php
  class DragonmantankCronExpressionTestCase (line 12) | final class DragonmantankCronExpressionTestCase extends AbstractCronExpr...
    method createExpression (line 14) | protected function createExpression(string $cronExpression): CronExpre...

FILE: tests/Unit/Infrastructure/Psr/Logger/EnabledLoggerDecoratorTest.php
  class EnabledLoggerDecoratorTest (line 16) | final class EnabledLoggerDecoratorTest extends UnitTestCase
    method test_disabled_channels_not_log (line 19) | public function test_disabled_channels_not_log(
    method test_enabled_channels_log (line 35) | public function test_enabled_channels_log(
    method disabledChannelProvider (line 51) | public static function disabledChannelProvider(): iterable
    method enabledChannelProvider (line 65) | public static function enabledChannelProvider(): iterable
    method createEnabledLoggerDecorator (line 78) | private function createEnabledLoggerDecorator(

FILE: tests/Unit/Infrastructure/Psr/Logger/PsrStreamLoggerFactoryTest.php
  class PsrStreamLoggerFactoryTest (line 14) | final class PsrStreamLoggerFactoryTest extends UnitTestCase
    method test_factory_returns_decorated_logger (line 16) | public function test_factory_returns_decorated_logger(): void
    method createStreamLoggerFactory (line 28) | private function createStreamLoggerFactory(): PsrStreamLoggerFactory

FILE: tests/Unit/Infrastructure/Psr/Logger/PsrStreamLoggerTest.php
  class PsrStreamLoggerTest (line 15) | final class PsrStreamLoggerTest extends TestCase
    method test_supported_levels_are_logged (line 18) | public function test_supported_levels_are_logged(string $level): void
    method test_unsupported_levels_are_ignored (line 38) | public function test_unsupported_levels_are_ignored(string $level): void
    method test_empty_context_is_ignored (line 50) | public function test_empty_context_is_ignored(string $level): void
    method test_date_use_passed_time_zone (line 76) | public function test_date_use_passed_time_zone(string $level): void
    method test_logging_with_allowed_line_breaks (line 107) | public function test_logging_with_allowed_line_breaks(string $level): ...
    method test_logging_with_disallowed_line_breaks (line 137) | public function test_logging_with_disallowed_line_breaks(string $level...
    method supportedLevelsProvider (line 157) | public static function supportedLevelsProvider(): iterable
    method unsupportedLevelsProvider (line 164) | public static function unsupportedLevelsProvider(): iterable
    method createLogger (line 174) | private function createLogger(
    method formatLine (line 195) | private function formatLine(

FILE: tests/Unit/InvokerTest.php
  class InvokerTest (line 10) | class InvokerTest extends TestCase
    method call_executes_closure (line 13) | public function call_executes_closure(): void
    method call_executes_closure_with_params (line 29) | public function call_executes_closure_with_params(): void
    method call_can_catch_output (line 45) | public function call_can_catch_output(): void

FILE: tests/Unit/Logger/ConsoleLoggerTest.php
  class ConsoleLoggerTest (line 13) | final class ConsoleLoggerTest extends TestCase
    method logger_writes_normal_only_with_suitable_verbosity (line 20) | public function logger_writes_normal_only_with_suitable_verbosity(int ...
    method logger_writes_verbose_only_with_suitable_verbosity (line 38) | public function logger_writes_verbose_only_with_suitable_verbosity(int...
    method logger_writes_very_verbose_only_with_suitable_verbosity (line 56) | public function logger_writes_very_verbose_only_with_suitable_verbosit...
    method logger_writes_debug_only_with_suitable_verbosity (line 74) | public function logger_writes_debug_only_with_suitable_verbosity(int $...
    method verbosityProvider (line 88) | public static function verbosityProvider(): iterable
    method mockSymfonyStyle (line 98) | private function mockSymfonyStyle(int $ioVerbosity): object

FILE: tests/Unit/Logger/LoggerFactoryTest.php
  class LoggerFactoryTest (line 17) | final class LoggerFactoryTest extends TestCase
    method test_logger_factory_creates_logger (line 19) | public function test_logger_factory_creates_logger(): void
    method test_logger_factory_creates_event_logger (line 28) | public function test_logger_factory_creates_event_logger(): void
    method test_wrong_logger_class_throws_exception (line 42) | public function test_wrong_logger_class_throws_exception(): void
    method createLoggerFactory (line 53) | private function createLoggerFactory(array $configuration = []): Logge...

FILE: tests/Unit/MailerTest.php
  class MailerTest (line 12) | final class MailerTest extends TestCase
    method using_mail_transport_will_result_in_exception (line 15) | public function using_mail_transport_will_result_in_exception(): void
    method createMailer (line 24) | private function createMailer(string $transport): Mailer

FILE: tests/Unit/Output/OutputFactoryTest.php
  class OutputFactoryTest (line 13) | final class OutputFactoryTest extends TestCase
    method input_defines_output_verbosity (line 20) | public function input_defines_output_verbosity(InputInterface $input, ...
    method inputProvider (line 30) | public static function inputProvider(): iterable
    method createInput (line 63) | private static function createInput(string $option): InputInterface

FILE: tests/Unit/Path/PathTest.php
  class PathTest (line 11) | final class PathTest extends TestCase
    method create_requires_at_least_one_path (line 14) | public function create_requires_at_least_one_path(): void
    method parts_are_delimited_by_directory_separator (line 23) | public function parts_are_delimited_by_directory_separator(): void
    method path_can_be_created_from_strings (line 40) | public function path_can_be_created_from_strings(): void
    method doubled_directory_separator_is_normalized (line 58) | public function doubled_directory_separator_is_normalized(): void

FILE: tests/Unit/Pingable.php
  class Pingable (line 10) | class Pingable implements PingableInterface

FILE: tests/Unit/Pinger/PingableTest.php
  class PingableTest (line 11) | final class PingableTest extends TestCase
    method before_url_must_be_string (line 18) | public function before_url_must_be_string(mixed $url): void
    method before_url_must_be_non_empty_string (line 31) | public function before_url_must_be_non_empty_string(): void
    method after_url_must_be_non_empty_string (line 43) | public function after_url_must_be_non_empty_string(): void
    method after_url_must_be_string (line 57) | public function after_url_must_be_string(mixed $url): void
    method get_ping_before_without_url_fails (line 68) | public function get_ping_before_without_url_fails(): void
    method get_ping_after_without_url_fails (line 78) | public function get_ping_after_without_url_fails(): void
    method nonStringProvider (line 88) | public static function nonStringProvider(): iterable

FILE: tests/Unit/Process/ProcessTest.php
  class ProcessTest (line 10) | final class ProcessTest extends UnitTestCase
    method test_command_line_built_from_array (line 12) | public function test_command_line_built_from_array(): void
    method assertCommand (line 32) | private function assertCommand(string $expectedCommand, Process $proce...

FILE: tests/Unit/Schedule/ScheduleFactoryTest.php
  class ScheduleFactoryTest (line 14) | final class ScheduleFactoryTest extends TestCase
    method single_task_schedule (line 17) | public function single_task_schedule(): void
    method single_task (line 34) | public function single_task(): void
    method single_task_schedule_throws_exception_on_wrong_task_number (line 49) | public function single_task_schedule_throws_exception_on_wrong_task_nu...
    method single_task_throws_exception_on_wrong_task_number (line 64) | public function single_task_throws_exception_on_wrong_task_number(): void

FILE: tests/Unit/ScheduleTest.php
  class ScheduleTest (line 12) | final class ScheduleTest extends UnitTestCase
    method test_run (line 17) | public function test_run(\Closure $paramsGenerator): void
    method test_run_with_non_string_parameters (line 44) | public function test_run_with_non_string_parameters(\Closure $paramsGe...
    method runProvider (line 70) | public static function runProvider(): iterable
    method nonStringParametersProvider (line 114) | public static function nonStringParametersProvider(): iterable
    method assertCommand (line 149) | private function assertCommand(string $expectedCommand, Event $event):...

FILE: tests/Unit/Service/AbstractClosureSerializerTestCase.php
  class AbstractClosureSerializerTestCase (line 10) | abstract class AbstractClosureSerializerTestCase extends UnitTestCase
    method test_closure_code_can_be_extracted (line 12) | public function test_closure_code_can_be_extracted(): void
    method createSerializer (line 25) | abstract protected function createSerializer(): ClosureSerializerInter...

FILE: tests/Unit/Service/LaravelClosureSerializerTest.php
  class LaravelClosureSerializerTest (line 12) | final class LaravelClosureSerializerTest extends UnitTestCase
    method setUp (line 16) | protected function setUp(): void
    method test_serialize_simple_closure (line 22) | public function test_serialize_simple_closure(): void
    method test_serialize_closure_with_use_variable (line 34) | public function test_serialize_closure_with_use_variable(): void
    method test_serialize_closure_bound_to_object_with_closure_properties (line 47) | public function test_serialize_closure_bound_to_object_with_closure_pr...
    method test_serialize_closure_bound_to_object_with_serialize_and_closure_properties (line 65) | public function test_serialize_closure_bound_to_object_with_serialize_...
    method test_closure_code_can_be_extracted (line 76) | public function test_closure_code_can_be_extracted(): void

FILE: tests/Unit/Service/LaravelClosureSerializerTestCase.php
  class LaravelClosureSerializerTestCase (line 9) | final class LaravelClosureSerializerTestCase extends AbstractClosureSeri...
    method createSerializer (line 11) | protected function createSerializer(): LaravelClosureSerializer

FILE: tests/Unit/Task/TaskNumberTest.php
  class TaskNumberTest (line 11) | class TaskNumberTest extends TestCase
    method can_not_create_task_number_with_non_string_value_by_from_string (line 18) | public function can_not_create_task_number_with_non_string_value_by_fr...
    method task_number_can_not_be_non_numeric_string (line 31) | public function task_number_can_not_be_non_numeric_string(string $valu...
    method task_number_can_be_created_with_numeric_string_value (line 44) | public function task_number_can_be_created_with_numeric_string_value(s...
    method array_index_is_one_step_lower (line 52) | public function array_index_is_one_step_lower(): void
    method nonStringValueProvider (line 60) | public static function nonStringValueProvider(): iterable
    method numericValueProvider (line 70) | public static function numericValueProvider(): iterable
    method nonNumericProvider (line 83) | public static function nonNumericProvider(): iterable

FILE: tests/Unit/Task/TimezoneTest.php
  class TimezoneTest (line 13) | final class TimezoneTest extends TestCase
    method configured_timezone_cannot_be_empty (line 16) | public function configured_timezone_cannot_be_empty(): void

FILE: tests/Unit/Timezone/ProviderTest.php
  class ProviderTest (line 10) | class ProviderTest extends TestCase
    method default_timezone_is_returned (line 17) | public function default_timezone_is_returned(): void

FILE: tests/Unit/UserInterface/Cli/ClosureRunCommandTest.php
  class ClosureRunCommandTest (line 12) | final class ClosureRunCommandTest extends UnitTestCase
    method test_return_value_of_closure_is_omitted (line 15) | public function test_return_value_of_closure_is_omitted(int $returnVal...
    method command_is_hidden (line 29) | public function command_is_hidden(): void
    method closureValueProvider (line 37) | public static function closureValueProvider(): iterable
    method createInput (line 43) | private function createInput(\Closure $closure): ArrayInput
    method createCommand (line 58) | private function createCommand(): ClosureRunCommand
Condensed preview — 174 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (428K chars).
[
  {
    "path": ".editorconfig",
    "chars": 115,
    "preview": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 4\ncharset = utf-8\n"
  },
  {
    "path": ".gitattributes",
    "chars": 567,
    "preview": "* text=auto\n\n/tests export-ignore\n.gitattributes export-ignore\n.gitignore export-ignore\n.editorconfig export-ignore\nREAD"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 23,
    "preview": "github: PabloKowalczyk\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1_Bug_report.md",
    "chars": 540,
    "preview": "---\nname: \"\\U0001F41B Bug Report\"\nabout: Report errors and problems\n\n---\n\n**Crunz version**: x.y.z\n\n**PHP version**: x.y"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2_Feature_request.md",
    "chars": 369,
    "preview": "---\nname: \"\\U0001F680 Feature Request\"\nabout: RFC and ideas for new features and improvements\n\n---\n\n**Description**  \n<!"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 290,
    "preview": "| Q             | A\n| ------------- | ---\n| Fixed tickets | #...   <!-- #-prefixed issue number(s), if any -->\n\n<!--\nWri"
  },
  {
    "path": ".github/workflows/php.yaml",
    "chars": 4159,
    "preview": "name: PHP\n\non:\n    pull_request:\n        branches:\n            - '3.9'\n            - '3.10'\n    push: null\n\npermissions:"
  },
  {
    "path": ".gitignore",
    "chars": 138,
    "preview": "/vendor\n/tasks\ncomposer.phar\n/composer.lock\n.DS_Store\n*.log\n.idea/\nvar/\n.php-cs-fixer.cache\n/crunz.phar\n/crunz.yml\n/.php"
  },
  {
    "path": ".php-cs-fixer.dist.php",
    "chars": 2259,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nuse PhpCsFixer\\Runner\\Parallel\\ParallelConfigFactory;\n\nreturn (new PhpCsFixer\\Config())"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 19240,
    "preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
  },
  {
    "path": "LICENSE",
    "chars": 1074,
    "preview": "MIT License\n\nCopyright (c) 2018 Moe Reza Lavarian\n\nPermission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "Makefile",
    "chars": 54,
    "preview": "sh-php:\n\tdocker compose exec --user=www-data php82 sh\n"
  },
  {
    "path": "README.md",
    "chars": 28031,
    "preview": "# Crunz needs your funding 💲\n\n## Support further Crunz development by [GitHub](https://github.com/sponsors/PabloKowalczy"
  },
  {
    "path": "UPGRADE.md",
    "chars": 2219,
    "preview": "# Upgrading from v3.2 to v3.3\n\n## Pass only string parameters to `\\Crunz\\Schedule::run`\n\nConvert this:\n```php\n$schedule "
  },
  {
    "path": "bootstrap.php",
    "chars": 536,
    "preview": "<?php\n\nrequire_once __DIR__ . '/vendor/autoload.php';\n\nif (!\\defined('IS_WINDOWS')) {\n    \\define('IS_WINDOWS', PHP_OS_F"
  },
  {
    "path": "composer.json",
    "chars": 2975,
    "preview": "{\n    \"name\": \"crunzphp/crunz\",\n    \"description\": \"Schedule your tasks right from the code.\",\n    \"license\": \"MIT\",\n   "
  },
  {
    "path": "config/services.php",
    "chars": 9444,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nuse Crunz\\Application\\Cron\\CronExpressionFactoryInterface;\nuse Crunz\\Application\\Query\\"
  },
  {
    "path": "crunz",
    "chars": 1547,
    "preview": "#!/usr/bin/env php\n<?php\n\n/*\n|--------------------------------------------------------------------------\n| Crunz\n|------"
  },
  {
    "path": "docker/php82/Dockerfile",
    "chars": 554,
    "preview": "FROM php:8.2.30-cli-alpine\n\nRUN apk add --no-cache \\\n        shadow \\\n        su-exec && \\\n    usermod --non-unique --ui"
  },
  {
    "path": "docker/php82/php.ini",
    "chars": 547,
    "preview": "realpath_cache_size = 8192k\nrealpath_cache_ttl = 6000\n\nexpose_php = On\nerror_log = /var/log/php/error.log\nerror_reportin"
  },
  {
    "path": "docker-compose.yml",
    "chars": 588,
    "preview": "services:\n    php82:\n        build:\n            context: ./docker/php82\n        working_dir: /var/www/html\n        envir"
  },
  {
    "path": "phpstan-baseline.neon",
    "chars": 21920,
    "preview": "parameters:\n\tignoreErrors:\n\t\t-\n\t\t\tmessage: \"#^Parameter \\\\#5 \\\\$timeZone of class Crunz\\\\\\\\Application\\\\\\\\Query\\\\\\\\TaskI"
  },
  {
    "path": "phpstan.neon",
    "chars": 1528,
    "preview": "parameters:\n    level: 9\n    reportUnmatchedIgnoredErrors: false\n    inferPrivatePropertyTypeFromConstructor: true\n    i"
  },
  {
    "path": "phpunit.xml",
    "chars": 1131,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:noName"
  },
  {
    "path": "resources/config/crunz.yml",
    "chars": 2119,
    "preview": "# Crunz Configuration Settings\n\n# This option defines where the task files and\n# directories reside.\n# The path is relat"
  },
  {
    "path": "src/Application/Cron/CronExpressionFactoryInterface.php",
    "chars": 200,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Application\\Cron;\n\ninterface CronExpressionFactoryInterface\n{\n    publi"
  },
  {
    "path": "src/Application/Cron/CronExpressionInterface.php",
    "chars": 292,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Application\\Cron;\n\ninterface CronExpressionInterface\n{\n    /** @return "
  },
  {
    "path": "src/Application/Query/TaskInformation/TaskInformation.php",
    "chars": 322,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Application\\Query\\TaskInformation;\n\nuse Crunz\\Task\\TaskNumber;\n\nfinal c"
  },
  {
    "path": "src/Application/Query/TaskInformation/TaskInformationHandler.php",
    "chars": 3178,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Application\\Query\\TaskInformation;\n\nuse Crunz\\Application\\Cron\\CronExpr"
  },
  {
    "path": "src/Application/Query/TaskInformation/TaskInformationView.php",
    "chars": 1319,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Application\\Query\\TaskInformation;\n\nfinal class TaskInformationView\n{\n "
  },
  {
    "path": "src/Application/Service/ClosureSerializerInterface.php",
    "chars": 302,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Application\\Service;\n\ninterface ClosureSerializerInterface\n{\n    public"
  },
  {
    "path": "src/Application/Service/ConfigurationInterface.php",
    "chars": 421,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Application\\Service;\n\ninterface ConfigurationInterface\n{\n    /**\n     *"
  },
  {
    "path": "src/Application/Service/LoggerFactoryInterface.php",
    "chars": 247,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Application\\Service;\n\nuse Psr\\Log\\LoggerInterface;\n\n/**\n * @experimenta"
  },
  {
    "path": "src/Application.php",
    "chars": 8051,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz;\n\nuse Crunz\\CacheDirectoryFactory\\CacheDirectoryFactory;\nuse Crunz\\Cons"
  },
  {
    "path": "src/CacheDirectoryFactory/CacheDirectoryFactory.php",
    "chars": 820,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\CacheDirectoryFactory;\n\nuse Crunz\\Exception\\CrunzException;\nuse Crunz\\P"
  },
  {
    "path": "src/Clock/Clock.php",
    "chars": 204,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Clock;\n\nfinal class Clock implements ClockInterface\n{\n    public functi"
  },
  {
    "path": "src/Clock/ClockInterface.php",
    "chars": 133,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Clock;\n\ninterface ClockInterface\n{\n    public function now(): \\DateTime"
  },
  {
    "path": "src/Configuration/ConfigFileNotExistsException.php",
    "chars": 319,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Configuration;\n\nuse Crunz\\Exception\\CrunzException;\n\nfinal class Config"
  },
  {
    "path": "src/Configuration/ConfigFileNotReadableException.php",
    "chars": 319,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Configuration;\n\nuse Crunz\\Exception\\CrunzException;\n\nfinal class Config"
  },
  {
    "path": "src/Configuration/Configuration.php",
    "chars": 2325,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Configuration;\n\nuse Crunz\\Application\\Service\\ConfigurationInterface;\nu"
  },
  {
    "path": "src/Configuration/ConfigurationParser.php",
    "chars": 2605,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Configuration;\n\nuse Crunz\\Console\\Command\\ConfigGeneratorCommand;\nuse C"
  },
  {
    "path": "src/Configuration/ConfigurationParserInterface.php",
    "chars": 200,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Configuration;\n\ninterface ConfigurationParserInterface\n{\n    /** @retur"
  },
  {
    "path": "src/Configuration/Definition.php",
    "chars": 4575,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Configuration;\n\nuse Crunz\\Infrastructure\\Psr\\Logger\\PsrStreamLoggerFact"
  },
  {
    "path": "src/Configuration/FileParser.php",
    "chars": 945,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Configuration;\n\nuse Symfony\\Component\\Yaml\\Yaml;\n\nclass FileParser\n{\n  "
  },
  {
    "path": "src/Console/Command/Command.php",
    "chars": 589,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Console\\Command;\n\nuse Symfony\\Component\\Console\\Command\\Command as Base"
  },
  {
    "path": "src/Console/Command/ConfigGeneratorCommand.php",
    "chars": 4171,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Console\\Command;\n\nuse Crunz\\Filesystem\\FilesystemInterface;\nuse Crunz\\P"
  },
  {
    "path": "src/Console/Command/ScheduleListCommand.php",
    "chars": 5232,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Console\\Command;\n\nuse Crunz\\Application\\Service\\ConfigurationInterface;"
  },
  {
    "path": "src/Console/Command/ScheduleRunCommand.php",
    "chars": 4252,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Console\\Command;\n\nuse Crunz\\Application\\Service\\ConfigurationInterface;"
  },
  {
    "path": "src/Console/Command/TaskGeneratorCommand.php",
    "chars": 7707,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Console\\Command;\n\nuse Crunz\\Application\\Service\\ConfigurationInterface;"
  },
  {
    "path": "src/EnvFlags/EnvFlags.php",
    "chars": 2183,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\EnvFlags;\n\nuse Crunz\\Exception\\CrunzException;\n\nfinal class EnvFlags\n{\n"
  },
  {
    "path": "src/Event.php",
    "chars": 30076,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz;\n\nuse Closure;\nuse Cron\\CronExpression;\nuse Crunz\\Application\\Service\\C"
  },
  {
    "path": "src/EventRunner.php",
    "chars": 9487,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz;\n\nuse Crunz\\Application\\Service\\ConfigurationInterface;\nuse Crunz\\HttpC"
  },
  {
    "path": "src/Exception/CrunzException.php",
    "chars": 105,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Exception;\n\nclass CrunzException extends \\Exception\n{\n}\n"
  },
  {
    "path": "src/Exception/EmptyTimezoneException.php",
    "chars": 117,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Exception;\n\nclass EmptyTimezoneException extends CrunzException\n{\n}\n"
  },
  {
    "path": "src/Exception/MailerException.php",
    "chars": 116,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Exception;\n\nfinal class MailerException extends CrunzException\n{\n}\n"
  },
  {
    "path": "src/Exception/NotImplementedException.php",
    "chars": 118,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Exception;\n\nclass NotImplementedException extends CrunzException\n{\n}\n"
  },
  {
    "path": "src/Exception/TaskNotExistException.php",
    "chars": 116,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Exception;\n\nclass TaskNotExistException extends CrunzException\n{\n}\n"
  },
  {
    "path": "src/Exception/WrongTaskNumberException.php",
    "chars": 119,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Exception;\n\nclass WrongTaskNumberException extends CrunzException\n{\n}\n"
  },
  {
    "path": "src/Filesystem/Filesystem.php",
    "chars": 3578,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Filesystem;\n\nuse Crunz\\Path\\Path;\n\nfinal class Filesystem implements Fi"
  },
  {
    "path": "src/Filesystem/FilesystemInterface.php",
    "chars": 1085,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Filesystem;\n\nuse Crunz\\Path\\Path;\n\ninterface FilesystemInterface\n{\n    "
  },
  {
    "path": "src/Finder/Finder.php",
    "chars": 998,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Finder;\n\nuse Crunz\\Path\\Path;\n\nfinal class Finder implements FinderInte"
  },
  {
    "path": "src/Finder/FinderInterface.php",
    "chars": 244,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Finder;\n\nuse Crunz\\Path\\Path;\n\ninterface FinderInterface\n{\n    /**\n    "
  },
  {
    "path": "src/HttpClient/CurlHttpClient.php",
    "chars": 1158,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\HttpClient;\n\nfinal class CurlHttpClient implements HttpClientInterface\n"
  },
  {
    "path": "src/HttpClient/FallbackHttpClient.php",
    "chars": 1714,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\HttpClient;\n\nuse Crunz\\Logger\\ConsoleLoggerInterface;\n\nfinal class Fall"
  },
  {
    "path": "src/HttpClient/HttpClientException.php",
    "chars": 152,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\HttpClient;\n\nuse Crunz\\Exception\\CrunzException;\n\nclass HttpClientExcep"
  },
  {
    "path": "src/HttpClient/HttpClientInterface.php",
    "chars": 228,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\HttpClient;\n\ninterface HttpClientInterface\n{\n    /**\n     * @param non-"
  },
  {
    "path": "src/HttpClient/HttpClientLoggerDecorator.php",
    "chars": 618,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\HttpClient;\n\nuse Crunz\\Logger\\ConsoleLoggerInterface;\n\nfinal class Http"
  },
  {
    "path": "src/HttpClient/StreamHttpClient.php",
    "chars": 736,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\HttpClient;\n\nfinal class StreamHttpClient implements HttpClientInterfac"
  },
  {
    "path": "src/Infrastructure/Dragonmantank/CronExpression/DragonmantankCronExpression.php",
    "chars": 875,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Infrastructure\\Dragonmantank\\CronExpression;\n\nuse Cron\\CronExpression;\n"
  },
  {
    "path": "src/Infrastructure/Dragonmantank/CronExpression/DragonmantankCronExpressionFactory.php",
    "chars": 567,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Infrastructure\\Dragonmantank\\CronExpression;\n\nuse Cron\\CronExpression;\n"
  },
  {
    "path": "src/Infrastructure/Laravel/LaravelClosureSerializer.php",
    "chars": 1065,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Infrastructure\\Laravel;\n\nuse Crunz\\Application\\Service\\ClosureSerialize"
  },
  {
    "path": "src/Infrastructure/Psr/Logger/EnabledLoggerDecorator.php",
    "chars": 1175,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Infrastructure\\Psr\\Logger;\n\nuse Crunz\\Application\\Service\\Configuration"
  },
  {
    "path": "src/Infrastructure/Psr/Logger/PsrStreamLogger.php",
    "chars": 5224,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Infrastructure\\Psr\\Logger;\n\nuse Crunz\\Clock\\ClockInterface;\nuse Crunz\\E"
  },
  {
    "path": "src/Infrastructure/Psr/Logger/PsrStreamLoggerFactory.php",
    "chars": 1141,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Infrastructure\\Psr\\Logger;\n\nuse Crunz\\Application\\Service\\Configuration"
  },
  {
    "path": "src/Invoker.php",
    "chars": 583,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz;\n\nclass Invoker\n{\n    /**\n     * Call the given Closure with buffering "
  },
  {
    "path": "src/Logger/ConsoleLogger.php",
    "chars": 1288,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Logger;\n\nuse Symfony\\Component\\Console\\Style\\SymfonyStyle;\n\nfinal class"
  },
  {
    "path": "src/Logger/ConsoleLoggerInterface.php",
    "chars": 926,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Logger;\n\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\n\ninterfa"
  },
  {
    "path": "src/Logger/Logger.php",
    "chars": 676,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Logger;\n\nuse Psr\\Log\\LoggerInterface;\n\nclass Logger\n{\n    public functi"
  },
  {
    "path": "src/Logger/LoggerFactory.php",
    "chars": 2994,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Logger;\n\nuse Crunz\\Application\\Service\\ConfigurationInterface;\nuse Crun"
  },
  {
    "path": "src/Mailer.php",
    "chars": 3146,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz;\n\nuse Crunz\\Application\\Service\\ConfigurationInterface;\nuse Crunz\\Excep"
  },
  {
    "path": "src/Output/OutputFactory.php",
    "chars": 1341,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Output;\n\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfon"
  },
  {
    "path": "src/Path/Path.php",
    "chars": 927,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Path;\n\nuse Crunz\\Exception\\CrunzException;\n\nfinal class Path\n{\n    priv"
  },
  {
    "path": "src/Pinger/PingableException.php",
    "chars": 146,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Pinger;\n\nuse Crunz\\Exception\\CrunzException;\n\nclass PingableException e"
  },
  {
    "path": "src/Pinger/PingableInterface.php",
    "chars": 705,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Pinger;\n\ninterface PingableInterface\n{\n    /**\n     * @param string $ur"
  },
  {
    "path": "src/Pinger/PingableTrait.php",
    "chars": 1476,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Pinger;\n\ntrait PingableTrait\n{\n    /** @var string */\n    private $ping"
  },
  {
    "path": "src/Process/Process.php",
    "chars": 1933,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Process;\n\nuse Symfony\\Component\\Process\\Process as SymfonyProcess;\n\n/**"
  },
  {
    "path": "src/Schedule/ScheduleFactory.php",
    "chars": 1229,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Schedule;\n\nuse Crunz\\Event;\nuse Crunz\\Exception\\TaskNotExistException;\n"
  },
  {
    "path": "src/Schedule.php",
    "chars": 5200,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz;\n\nuse Crunz\\Pinger\\PingableInterface;\nuse Crunz\\Pinger\\PingableTrait;\nu"
  },
  {
    "path": "src/Stubs/BasicTask.php",
    "chars": 694,
    "preview": "<?php\n\ndeclare(strict_types=1);\n/*\n|------------------------------------------------------------------------------------"
  },
  {
    "path": "src/Task/Collection.php",
    "chars": 1530,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Task;\n\nuse Crunz\\Application\\Service\\ConfigurationInterface;\nuse Crunz\\"
  },
  {
    "path": "src/Task/CollectionInterface.php",
    "chars": 175,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Task;\n\ninterface CollectionInterface\n{\n    /** @return \\SplFileInfo[] *"
  },
  {
    "path": "src/Task/Loader.php",
    "chars": 960,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Task;\n\nuse Crunz\\Schedule;\n\nfinal class Loader implements LoaderInterfa"
  },
  {
    "path": "src/Task/LoaderInterface.php",
    "chars": 194,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Task;\n\nuse Crunz\\Schedule;\n\ninterface LoaderInterface\n{\n    /** @return"
  },
  {
    "path": "src/Task/TaskException.php",
    "chars": 140,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Task;\n\nuse Crunz\\Exception\\CrunzException;\n\nclass TaskException extends"
  },
  {
    "path": "src/Task/TaskNumber.php",
    "chars": 1166,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Task;\n\nuse Crunz\\Exception\\WrongTaskNumberException;\n\nclass TaskNumber\n"
  },
  {
    "path": "src/Task/Timezone.php",
    "chars": 1262,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Task;\n\nuse Crunz\\Application\\Service\\ConfigurationInterface;\nuse Crunz\\"
  },
  {
    "path": "src/Task/WrongTaskInstanceException.php",
    "chars": 514,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Task;\n\nuse Crunz\\Schedule;\n\nfinal class WrongTaskInstanceException exte"
  },
  {
    "path": "src/Timezone/Provider.php",
    "chars": 243,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Timezone;\n\nfinal class Provider implements ProviderInterface\n{\n    publ"
  },
  {
    "path": "src/Timezone/ProviderInterface.php",
    "chars": 176,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Timezone;\n\ninterface ProviderInterface\n{\n    /**\n     * @return \\DateTi"
  },
  {
    "path": "src/UserInterface/Cli/ClosureRunCommand.php",
    "chars": 1710,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\UserInterface\\Cli;\n\nuse Crunz\\Application\\Service\\ClosureSerializerInte"
  },
  {
    "path": "src/UserInterface/Cli/DebugTaskCommand.php",
    "chars": 3827,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\UserInterface\\Cli;\n\nuse Crunz\\Application\\Query\\TaskInformation\\TaskInf"
  },
  {
    "path": "tests/EndToEnd/ClosureRunTest.php",
    "chars": 1633,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\EndToEnd;\n\nuse Crunz\\Tests\\TestCase\\EndToEndTestCase;\n\nfinal clas"
  },
  {
    "path": "tests/EndToEnd/ConfigProviderTest.php",
    "chars": 1153,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\EndToEnd;\n\nuse Crunz\\Console\\Command\\ConfigGeneratorCommand;\nuse "
  },
  {
    "path": "tests/EndToEnd/ConfigRecognitionTest.php",
    "chars": 1544,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\EndToEnd;\n\nuse Crunz\\Path\\Path;\nuse Crunz\\Tests\\TestCase\\EndToEnd"
  },
  {
    "path": "tests/EndToEnd/DebugTaskTest.php",
    "chars": 2795,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\EndToEnd;\n\nuse Crunz\\Tests\\TestCase\\EndToEndTestCase;\n\nfinal clas"
  },
  {
    "path": "tests/EndToEnd/LoggerTest.php",
    "chars": 2341,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\EndToEnd;\n\nuse Crunz\\Tests\\TestCase\\EndToEndTestCase;\n\nfinal clas"
  },
  {
    "path": "tests/EndToEnd/TasksSourceRecognitionTest.php",
    "chars": 1692,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\EndToEnd;\n\nuse Crunz\\Path\\Path;\nuse Crunz\\Tests\\TestCase\\EndToEnd"
  },
  {
    "path": "tests/EndToEnd/VersionTest.php",
    "chars": 923,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\EndToEnd;\n\nuse Composer\\InstalledVersions;\nuse Crunz\\Tests\\TestCa"
  },
  {
    "path": "tests/EndToEnd/WrongTaskTest.php",
    "chars": 1197,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\EndToEnd;\n\nuse Crunz\\Tests\\TestCase\\EndToEndTestCase;\n\nfinal clas"
  },
  {
    "path": "tests/Functional/ConfigProviderTest.php",
    "chars": 687,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Functional;\n\nuse Crunz\\Application;\nuse PHPUnit\\Framework\\TestCas"
  },
  {
    "path": "tests/Functional/DifferentBaseCacheDirTest.php",
    "chars": 641,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Functional;\n\nuse Crunz\\Application;\nuse Crunz\\CacheDirectoryFacto"
  },
  {
    "path": "tests/Functional/ScheduleListTest.php",
    "chars": 648,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Functional;\n\nuse Crunz\\Application;\nuse PHPUnit\\Framework\\TestCas"
  },
  {
    "path": "tests/Functional/ScheduleRunTest.php",
    "chars": 639,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Functional;\n\nuse Crunz\\Application;\nuse PHPUnit\\Framework\\TestCas"
  },
  {
    "path": "tests/Functional/TaskGeneratorTest.php",
    "chars": 2380,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Functional;\n\nuse Crunz\\Application;\nuse Crunz\\Path\\Path;\nuse PHPU"
  },
  {
    "path": "tests/TestCase/EndToEnd/Environment/Environment.php",
    "chars": 6706,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase\\EndToEnd\\Environment;\n\nuse Crunz\\Console\\Command\\ConfigG"
  },
  {
    "path": "tests/TestCase/EndToEnd/Environment/EnvironmentBuilder.php",
    "chars": 1151,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase\\EndToEnd\\Environment;\n\nuse Crunz\\Filesystem\\FilesystemIn"
  },
  {
    "path": "tests/TestCase/EndToEndTestCase.php",
    "chars": 1284,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase;\n\nuse Crunz\\Filesystem\\Filesystem;\nuse Crunz\\Filesystem\\"
  },
  {
    "path": "tests/TestCase/FakeConfiguration.php",
    "chars": 2284,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase;\n\nuse Crunz\\Application\\Service\\ConfigurationInterface;\n"
  },
  {
    "path": "tests/TestCase/FakeLoader.php",
    "chars": 399,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase;\n\nuse Crunz\\Schedule;\nuse Crunz\\Task\\LoaderInterface;\n\nf"
  },
  {
    "path": "tests/TestCase/FakeTaskCollection.php",
    "chars": 384,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase;\n\nuse Crunz\\Task\\CollectionInterface;\n\nfinal class FakeT"
  },
  {
    "path": "tests/TestCase/Faker.php",
    "chars": 2064,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase;\n\nuse Crunz\\Exception\\CrunzException;\n\nfinal class Faker"
  },
  {
    "path": "tests/TestCase/Logger/NullLogger.php",
    "chars": 472,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase\\Logger;\n\nuse Crunz\\Logger\\ConsoleLoggerInterface;\n\nfinal"
  },
  {
    "path": "tests/TestCase/Logger/SpyPsrLogger.php",
    "chars": 570,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase\\Logger;\n\nuse Psr\\Log\\AbstractLogger;\n\nfinal class SpyPsr"
  },
  {
    "path": "tests/TestCase/SerializableTaskRunnerStub.php",
    "chars": 921,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase;\n\nfinal class SerializableTaskRunnerStub\n{\n    public st"
  },
  {
    "path": "tests/TestCase/TaskRunnerStub.php",
    "chars": 446,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase;\n\nfinal class TaskRunnerStub\n{\n    public string $taskNa"
  },
  {
    "path": "tests/TestCase/TemporaryFile.php",
    "chars": 1654,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase;\n\nuse Crunz\\Exception\\CrunzException;\n\nfinal class Tempo"
  },
  {
    "path": "tests/TestCase/TestClock.php",
    "chars": 323,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase;\n\nuse Crunz\\Clock\\ClockInterface;\n\nfinal class TestClock"
  },
  {
    "path": "tests/TestCase/UnitTestCase.php",
    "chars": 627,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\TestCase;\n\nuse Crunz\\Application\\Service\\ClosureSerializerInterfa"
  },
  {
    "path": "tests/Unit/Application/Cron/AbstractCronExpressionTestCase.php",
    "chars": 2369,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Application\\Cron;\n\nuse Crunz\\Application\\Cron\\CronExpression"
  },
  {
    "path": "tests/Unit/Application/Query/TaskInformation/TaskInformationHandlerTest.php",
    "chars": 4335,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Application\\Query\\TaskInformation;\n\nuse Crunz\\Application\\Qu"
  },
  {
    "path": "tests/Unit/CacheDirectoryFactory/CacheDirectoryFactoryTest.php",
    "chars": 1515,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\CacheDirectoryFactory;\n\nuse Crunz\\CacheDirectoryFactory\\Cach"
  },
  {
    "path": "tests/Unit/Configuration/ConfigurationParserTest.php",
    "chars": 2340,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Configuration;\n\nuse Crunz\\Configuration\\ConfigFileNotExistsE"
  },
  {
    "path": "tests/Unit/Configuration/ConfigurationTest.php",
    "chars": 3561,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Configuration;\n\nuse Crunz\\Configuration\\Configuration;\nuse C"
  },
  {
    "path": "tests/Unit/Configuration/FileParserTest.php",
    "chars": 2051,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Configuration;\n\nuse Crunz\\Configuration\\ConfigFileNotExistsE"
  },
  {
    "path": "tests/Unit/Console/Command/ScheduleListCommandTest.php",
    "chars": 5444,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Console\\Command;\n\nuse Crunz\\Console\\Command\\ScheduleListComm"
  },
  {
    "path": "tests/Unit/Console/Command/ScheduleRunCommandTest.php",
    "chars": 7059,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Console\\Command;\n\nuse Crunz\\Console\\Command\\ScheduleRunComma"
  },
  {
    "path": "tests/Unit/EnvFlags/EnvFlagsTest.php",
    "chars": 2630,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\EnvFlags;\n\nuse Crunz\\EnvFlags\\EnvFlags;\nuse PHPUnit\\Framewor"
  },
  {
    "path": "tests/Unit/EventRunnerTest.php",
    "chars": 4528,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit;\n\nuse Crunz\\EventRunner;\nuse Crunz\\HttpClient\\HttpClientInte"
  },
  {
    "path": "tests/Unit/EventTest.php",
    "chars": 21730,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit;\n\nuse Crunz\\Event;\nuse Crunz\\Exception\\CrunzException;\nuse C"
  },
  {
    "path": "tests/Unit/Filesystem/FilesystemTest.php",
    "chars": 5327,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Filesystem;\n\nuse Crunz\\Filesystem\\Filesystem;\nuse Crunz\\Path"
  },
  {
    "path": "tests/Unit/Finder/FinderTest.php",
    "chars": 3717,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Finder;\n\nuse Crunz\\Filesystem\\Filesystem;\nuse Crunz\\Finder\\F"
  },
  {
    "path": "tests/Unit/HttpClient/StreamHttpClientTest.php",
    "chars": 679,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\HttpClient;\n\nuse Crunz\\HttpClient\\HttpClientException;\nuse C"
  },
  {
    "path": "tests/Unit/Infrastructure/Dragonmantank/CronExpression/DragonmantankCronExpressionTestCase.php",
    "chars": 673,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Infrastructure\\Dragonmantank\\CronExpression;\n\nuse Cron\\CronE"
  },
  {
    "path": "tests/Unit/Infrastructure/Psr/Logger/EnabledLoggerDecoratorTest.php",
    "chars": 2467,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Infrastructure\\Psr\\Logger;\n\nuse Crunz\\Application\\Service\\Co"
  },
  {
    "path": "tests/Unit/Infrastructure/Psr/Logger/PsrStreamLoggerFactoryTest.php",
    "chars": 1007,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Infrastructure\\Psr\\Logger;\n\nuse Crunz\\Infrastructure\\Psr\\Log"
  },
  {
    "path": "tests/Unit/Infrastructure/Psr/Logger/PsrStreamLoggerTest.php",
    "chars": 6300,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Infrastructure\\Psr\\Logger;\n\nuse Crunz\\Exception\\CrunzExcepti"
  },
  {
    "path": "tests/Unit/InvokerTest.php",
    "chars": 1158,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit;\n\nuse Crunz\\Invoker;\nuse PHPUnit\\Framework\\TestCase;\n\nclass "
  },
  {
    "path": "tests/Unit/Logger/ConsoleLoggerTest.php",
    "chars": 3413,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Logger;\n\nuse Crunz\\Logger\\ConsoleLogger;\nuse Crunz\\Logger\\Co"
  },
  {
    "path": "tests/Unit/Logger/LoggerFactoryTest.php",
    "chars": 1783,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Logger;\n\nuse Crunz\\Clock\\Clock;\nuse Crunz\\Event;\nuse Crunz\\E"
  },
  {
    "path": "tests/Unit/MailerTest.php",
    "chars": 914,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit;\n\nuse Crunz\\Exception\\MailerException;\nuse Crunz\\Mailer;\nuse"
  },
  {
    "path": "tests/Unit/Output/OutputFactoryTest.php",
    "chars": 1699,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Output;\n\nuse Crunz\\Output\\OutputFactory;\nuse PHPUnit\\Framewo"
  },
  {
    "path": "tests/Unit/Path/PathTest.php",
    "chars": 1529,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Path;\n\nuse Crunz\\Exception\\CrunzException;\nuse Crunz\\Path\\Pa"
  },
  {
    "path": "tests/Unit/Pingable.php",
    "chars": 202,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit;\n\nuse Crunz\\Pinger\\PingableInterface;\nuse Crunz\\Pinger\\Pinga"
  },
  {
    "path": "tests/Unit/Pinger/PingableTest.php",
    "chars": 2481,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Pinger;\n\nuse Crunz\\Pinger\\PingableException;\nuse Crunz\\Tests"
  },
  {
    "path": "tests/Unit/Process/ProcessTest.php",
    "chars": 1169,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Process;\n\nuse Crunz\\Process\\Process;\nuse Crunz\\Tests\\TestCas"
  },
  {
    "path": "tests/Unit/Schedule/ScheduleFactoryTest.php",
    "chars": 2246,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Schedule;\n\nuse Crunz\\Event;\nuse Crunz\\Exception\\TaskNotExist"
  },
  {
    "path": "tests/Unit/ScheduleTest.php",
    "chars": 4680,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit;\n\nuse Crunz\\Event;\nuse Crunz\\Schedule;\nuse Crunz\\Tests\\TestC"
  },
  {
    "path": "tests/Unit/Service/AbstractClosureSerializerTestCase.php",
    "chars": 703,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Service;\n\nuse Crunz\\Application\\Service\\ClosureSerializerInt"
  },
  {
    "path": "tests/Unit/Service/LaravelClosureSerializerTest.php",
    "chars": 2565,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Service;\n\nuse Crunz\\Infrastructure\\Laravel\\LaravelClosureSer"
  },
  {
    "path": "tests/Unit/Service/LaravelClosureSerializerTestCase.php",
    "chars": 348,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Service;\n\nuse Crunz\\Infrastructure\\Laravel\\LaravelClosureSer"
  },
  {
    "path": "tests/Unit/Task/TaskNumberTest.php",
    "chars": 2217,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Task;\n\nuse Crunz\\Exception\\WrongTaskNumberException;\nuse Cru"
  },
  {
    "path": "tests/Unit/Task/TimezoneTest.php",
    "chars": 644,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Task;\n\nuse Crunz\\Exception\\EmptyTimezoneException;\nuse Crunz"
  },
  {
    "path": "tests/Unit/Timezone/ProviderTest.php",
    "chars": 550,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\Timezone;\n\nuse Crunz\\Timezone\\Provider;\nuse PHPUnit\\Framewor"
  },
  {
    "path": "tests/Unit/UserInterface/Cli/ClosureRunCommandTest.php",
    "chars": 1609,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Crunz\\Tests\\Unit\\UserInterface\\Cli;\n\nuse Crunz\\Tests\\TestCase\\UnitTestCase;\nu"
  },
  {
    "path": "tests/crunz.yml",
    "chars": 401,
    "preview": "source: tasks\nsuffix: Tasks.php\ntimezone: UTC\ntimezone_log: false\nlog_errors: false\nerrors_log_file: ~\nlog_output: false"
  },
  {
    "path": "tests/resources/fixtures/finder/direct/directHere.php",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/resources/fixtures/finder/symlink/symlinkHere.php",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/resources/tasks/ClosureTasks.php",
    "chars": 337,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nuse Crunz\\Schedule;\n\n$x = 153;\n\n$scheduler = new Schedule();\n$scheduler\n    ->run(\n    "
  },
  {
    "path": "tests/resources/tasks/CustomOutputTasks.php",
    "chars": 237,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nuse Crunz\\Schedule;\n\n$scheduler = new Schedule();\n\n$scheduler\n    ->run('php --help')\n "
  },
  {
    "path": "tests/resources/tasks/FailTasks.php",
    "chars": 289,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nuse Crunz\\Schedule;\n\n$scheduler = new Schedule();\n$scheduler\n    ->run(\n        functio"
  },
  {
    "path": "tests/resources/tasks/NoOverlappingClosureTasks.php",
    "chars": 380,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nuse Crunz\\Schedule;\n\n$scheduler = new Schedule();\n$scheduler\n    ->run(\n        functio"
  },
  {
    "path": "tests/resources/tasks/PhpVersionTasks.php",
    "chars": 190,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nuse Crunz\\Schedule;\n\n$scheduler = new Schedule();\n\n$scheduler\n    ->run('php -v')\n    -"
  },
  {
    "path": "tests/resources/tasks/WrongTasks.php",
    "chars": 44,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nreturn [];\n"
  },
  {
    "path": "tests/tasks/TestTasks.php",
    "chars": 246,
    "preview": "<?php\n\ndeclare(strict_types=1);\nuse Crunz\\Schedule;\n\n$schedule = new Schedule();\n\n$schedule->run(PHP_BINARY . ' -v')\n   "
  }
]

About this extraction

This page contains the full source code of the crunzphp/crunz GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 174 files (386.9 KB), approximately 99.8k tokens, and a symbol index with 758 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!