From f6e2e1e5d3bad8be640fe42bc63769370855b77a Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 7 Aug 2025 15:50:15 +0200 Subject: [PATCH 1/5] add missing change in changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e7f413..b65fbf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Changed - Requires `innmind/foundation:~1.5` +- `Innmind\Cron\Crontab::_invoke()` now returns an `Innmind\Immutable\Attempt` ## 3.2.0 - 2023-09-23 From 24549bf1e95f767c8e3bdcbcc452ef130ff8e673 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 7 Aug 2025 16:02:51 +0200 Subject: [PATCH 2/5] make Read return an Attempt to allow expose why it failed --- CHANGELOG.md | 1 + src/Read.php | 60 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b65fbf0..00f33ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Requires `innmind/foundation:~1.5` - `Innmind\Cron\Crontab::_invoke()` now returns an `Innmind\Immutable\Attempt` +- `Innmind\Cron\Read::_invoke()` now returns an `Innmind\Immutable\Attempt` ## 3.2.0 - 2023-09-23 diff --git a/src/Read.php b/src/Read.php index f63c815..90814f0 100644 --- a/src/Read.php +++ b/src/Read.php @@ -9,7 +9,7 @@ }; use Innmind\Immutable\{ Sequence, - Str, + Attempt, Maybe, Monoid\Concat, }; @@ -24,32 +24,28 @@ private function __construct(Command $command) } /** - * @return Maybe> Returns nothing when unable to read the crontab + * @return Attempt> */ - public function __invoke(Server $server): Maybe + public function __invoke(Server $server): Attempt { - $process = $server->processes()->execute($this->command)->unwrap(); - $success = $process->wait()->match( - static fn() => true, - static fn() => false, - ); - - if (!$success) { - /** @var Maybe> */ - return Maybe::nothing(); - } - - $jobs = $process - ->output() - ->map(static fn($chunk) => $chunk->data()) - ->fold(new Concat) - ->split("\n") - ->filter(static function(Str $line): bool { - return !$line->startsWith('#') && !$line->trim()->empty(); - }) + return $server + ->processes() + ->execute($this->command) + ->flatMap( + static fn($process) => $process + ->wait() + ->attempt(static fn($error) => new \RuntimeException($error::class)), + ) ->map( - static fn(Str $line) => Job::maybe($line->toString()), - ); + static fn($success) => $success + ->output() + ->map(static fn($chunk) => $chunk->data()) + ->fold(new Concat) + ->split("\n") + ->filter(static fn($line) => !$line->startsWith('#') && !$line->trim()->empty()) + ->map(static fn($line) => Job::maybe($line->toString())), + ) + ->flatMap(self::parse(...)); /** * @psalm-suppress NamedArgumentNotAllowed @@ -82,4 +78,20 @@ public static function forUser(string $user): self ->withShortOption('l'), ); } + + /** + * @param Sequence> $jobs + * + * @return Attempt> + */ + private static function parse(Sequence $jobs): Attempt + { + /** @var Sequence */ + $parsed = Sequence::of(); + + return $jobs + ->sink($parsed) + ->maybe(static fn($parsed, $job) => $job->map($parsed)) + ->attempt(static fn() => new \RuntimeException('Failed to parse job')); + } } From 5dcab820d799bae023c9c183f6a336af9fffb322 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 7 Aug 2025 16:06:42 +0200 Subject: [PATCH 3/5] expose why a job failed to be parsed --- src/Job.php | 30 ++++++++++++++++++++++-------- src/Read.php | 7 +++---- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/Job.php b/src/Job.php index 1afec91..8f3781e 100644 --- a/src/Job.php +++ b/src/Job.php @@ -8,6 +8,7 @@ use Innmind\Immutable\{ Str, Maybe, + Attempt, }; /** @@ -33,10 +34,7 @@ public function __construct(Schedule $schedule, Command $command) */ public static function of(string $value): self { - return self::maybe($value)->match( - static fn($self) => $self, - static fn() => throw new \DomainException($value), - ); + return self::attempt($value)->unwrap(); } /** @@ -45,6 +43,16 @@ public static function of(string $value): self * @return Maybe */ public static function maybe(string $value): Maybe + { + return self::attempt($value)->maybe(); + } + + /** + * @psalm-pure + * + * @return Attempt + */ + public static function attempt(string $value): Attempt { $parts = Str::of($value) ->split(' ') @@ -53,15 +61,21 @@ public static function maybe(string $value): Maybe $command = Str::of(' ')->join($parts->drop(5))->toString(); if ($command === '') { - /** @var Maybe */ - return Maybe::nothing(); + /** @var Attempt */ + return Attempt::error(new \RuntimeException('Job without a command')); } - return Schedule::maybe(Str::of(' ')->join($parts->take(5))->toString()) + $schedule = Str::of(' ')->join($parts->take(5))->toString(); + + return Schedule::maybe($schedule) ->map(static fn($schedule) => new self( $schedule, Command::foreground($command), - )); + )) + ->attempt(static fn() => new \RuntimeException(\sprintf( + 'Invalid schedule %s', + $schedule, + ))); } public function toString(): string diff --git a/src/Read.php b/src/Read.php index 90814f0..7120c12 100644 --- a/src/Read.php +++ b/src/Read.php @@ -43,7 +43,7 @@ public function __invoke(Server $server): Attempt ->fold(new Concat) ->split("\n") ->filter(static fn($line) => !$line->startsWith('#') && !$line->trim()->empty()) - ->map(static fn($line) => Job::maybe($line->toString())), + ->map(static fn($line) => Job::attempt($line->toString())), ) ->flatMap(self::parse(...)); @@ -80,7 +80,7 @@ public static function forUser(string $user): self } /** - * @param Sequence> $jobs + * @param Sequence> $jobs * * @return Attempt> */ @@ -91,7 +91,6 @@ private static function parse(Sequence $jobs): Attempt return $jobs ->sink($parsed) - ->maybe(static fn($parsed, $job) => $job->map($parsed)) - ->attempt(static fn() => new \RuntimeException('Failed to parse job')); + ->attempt(static fn($parsed, $job) => $job->map($parsed)); } } From d90aca0b17fec1f595435af5bcb6efeb9a873ce1 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 7 Aug 2025 16:10:05 +0200 Subject: [PATCH 4/5] flag Range as internal --- CHANGELOG.md | 1 + src/Job/Schedule/Range.php | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00f33ed..6e0d6e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Requires `innmind/foundation:~1.5` - `Innmind\Cron\Crontab::_invoke()` now returns an `Innmind\Immutable\Attempt` - `Innmind\Cron\Read::_invoke()` now returns an `Innmind\Immutable\Attempt` +- `Innmind\Cron\Schedule\Range` is now declared internal ## 3.2.0 - 2023-09-23 diff --git a/src/Job/Schedule/Range.php b/src/Job/Schedule/Range.php index 7958fc5..72e34ca 100644 --- a/src/Job/Schedule/Range.php +++ b/src/Job/Schedule/Range.php @@ -6,6 +6,7 @@ use Innmind\Immutable\Str; /** + * @internal * @psalm-immutable */ final class Range From 773a774ff8226fa7b9364ae7eac2d648d2894af1 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 7 Aug 2025 16:13:19 +0200 Subject: [PATCH 5/5] fix generating valid values --- tests/Job/Schedule/DaysOfMonthTest.php | 6 +++++- tests/Job/Schedule/DaysOfWeekTest.php | 6 +++++- tests/Job/Schedule/HoursTest.php | 6 +++++- tests/Job/Schedule/MinutesTest.php | 6 +++++- tests/Job/Schedule/MonthsTest.php | 6 +++++- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/tests/Job/Schedule/DaysOfMonthTest.php b/tests/Job/Schedule/DaysOfMonthTest.php index a7806cc..9790b1c 100644 --- a/tests/Job/Schedule/DaysOfMonthTest.php +++ b/tests/Job/Schedule/DaysOfMonthTest.php @@ -126,7 +126,11 @@ public function testRangeOfDaysOfMonthSteppedFromRawString() public function testReturnNothingWhenUsingRandomString() { $this - ->forAll(Set::strings()->filter(static fn($value) => !\is_numeric($value))) + ->forAll( + Set::strings() + ->filter(static fn($value) => !\is_numeric($value)) + ->filter(static fn($value) => $value !== '*'), + ) ->then(function($value) { $schedule = DaysOfMonth::maybe($value)->match( static fn($schedule) => $schedule, diff --git a/tests/Job/Schedule/DaysOfWeekTest.php b/tests/Job/Schedule/DaysOfWeekTest.php index c3e362b..66d3baf 100644 --- a/tests/Job/Schedule/DaysOfWeekTest.php +++ b/tests/Job/Schedule/DaysOfWeekTest.php @@ -126,7 +126,11 @@ public function testRangeOfDaysOfWeekSteppedFromRawString() public function testReturnNothingWhenUsingRandomString() { $this - ->forAll(Set::strings()->filter(static fn($value) => !\is_numeric($value))) + ->forAll( + Set::strings() + ->filter(static fn($value) => !\is_numeric($value)) + ->filter(static fn($value) => $value !== '*'), + ) ->then(function($value) { $schedule = DaysOfWeek::maybe($value)->match( static fn($schedule) => $schedule, diff --git a/tests/Job/Schedule/HoursTest.php b/tests/Job/Schedule/HoursTest.php index ac8d1ee..a3311b9 100644 --- a/tests/Job/Schedule/HoursTest.php +++ b/tests/Job/Schedule/HoursTest.php @@ -126,7 +126,11 @@ public function testRangeOfHoursSteppedFromRawString() public function testReturnNothingWhenUsingRandomString() { $this - ->forAll(Set::strings()->filter(static fn($string) => !\is_numeric($string))) + ->forAll( + Set::strings() + ->filter(static fn($value) => !\is_numeric($value)) + ->filter(static fn($value) => $value !== '*'), + ) ->then(function($value) { $schedule = Hours::maybe($value)->match( static fn($schedule) => $schedule, diff --git a/tests/Job/Schedule/MinutesTest.php b/tests/Job/Schedule/MinutesTest.php index 26256a7..d2028d6 100644 --- a/tests/Job/Schedule/MinutesTest.php +++ b/tests/Job/Schedule/MinutesTest.php @@ -126,7 +126,11 @@ public function testRangeOfMinutesSteppedFromRawString() public function testReturnNothingWhenUsingRandomString() { $this - ->forAll(Set::strings()->filter(static fn($value) => !\is_numeric($value))) + ->forAll( + Set::strings() + ->filter(static fn($value) => !\is_numeric($value)) + ->filter(static fn($value) => $value !== '*'), + ) ->then(function($value) { $schedule = Minutes::maybe($value)->match( static fn($schedule) => $schedule, diff --git a/tests/Job/Schedule/MonthsTest.php b/tests/Job/Schedule/MonthsTest.php index 8b83329..29e7308 100644 --- a/tests/Job/Schedule/MonthsTest.php +++ b/tests/Job/Schedule/MonthsTest.php @@ -126,7 +126,11 @@ public function testRangeOfMonthsSteppedFromRawString() public function testReturnNothingWhenUsingRandomString() { $this - ->forAll(Set::strings()->filter(static fn($value) => !\is_numeric($value))) + ->forAll( + Set::strings() + ->filter(static fn($value) => !\is_numeric($value)) + ->filter(static fn($value) => $value !== '*'), + ) ->then(function($value) { $schedule = Months::maybe($value)->match( static fn($schedule) => $schedule,