• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eliashaeussler / task-runner / 20577971652

29 Dec 2025 04:46PM UTC coverage: 98.039% (-2.0%) from 100.0%
20577971652

push

github

eliashaeussler
[BUGFIX] Write progress to error output if available

6 of 7 new or added lines in 1 file covered. (85.71%)

50 of 51 relevant lines covered (98.04%)

6.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.06
/src/TaskRunner.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the Composer package "eliashaeussler/task-runner".
7
 *
8
 * Copyright (C) 2025 Elias Häußler <elias@haeussler.dev>
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23

24
namespace EliasHaeussler\TaskRunner;
25

26
use Closure;
27
use ReflectionFunction;
28
use ReflectionNamedType;
29
use Symfony\Component\Console;
30
use Throwable;
31

32
/**
33
 * TaskRunner.
34
 *
35
 * @author Elias Häußler <elias@haeussler.dev>
36
 * @license GPL-3.0-or-later
37
 */
38
final readonly class TaskRunner
39
{
40
    public function __construct(
11✔
41
        private Console\Output\OutputInterface $output,
42
        private Decorator\ProgressDecorator $progressDecorator = new Decorator\SimpleProgressDecorator(),
43
    ) {}
11✔
44

45
    /**
46
     * @template T
47
     *
48
     * @param Closure(RunnerContext): T                   $task
49
     * @param Console\Output\OutputInterface::VERBOSITY_* $verbosity
50
     *
51
     * @return (T is void ? TaskResult : T)
52
     */
53
    public function run(
11✔
54
        string $message,
55
        Closure $task,
56
        int $verbosity = Console\Output\OutputInterface::VERBOSITY_NORMAL,
57
    ): mixed {
58
        $taskOutput = new Console\Output\BufferedOutput(
11✔
59
            $this->output->getVerbosity(),
11✔
60
            $this->output->isDecorated(),
11✔
61
            $this->output->getFormatter(),
11✔
62
        );
11✔
63
        $context = new RunnerContext($taskOutput);
11✔
64
        $newLine = false;
11✔
65
        $decoratedMessage = $this->progressDecorator->progress($message, $newLine);
11✔
66

67
        if ($this->output instanceof Console\Output\ConsoleOutputInterface) {
11✔
NEW
68
            $errorOutput = $this->output->getErrorOutput();
×
69
        } else {
70
            $errorOutput = $this->output;
11✔
71
        }
72

73
        $errorOutput->write($decoratedMessage, $newLine, $verbosity);
11✔
74

75
        try {
76
            $isVoidReturn = $this->isVoidReturn($task);
11✔
77
            $returnValue = (static fn () => $task($context))();
11✔
78
            $taskResult = TaskResult::fromContext($context);
9✔
79

80
            if (TaskResult::Success === $taskResult) {
9✔
81
                $errorOutput->writeln($this->progressDecorator->done($returnValue), $verbosity);
7✔
82
            } else {
83
                $errorOutput->writeln($this->progressDecorator->failed(), $verbosity);
2✔
84
            }
85

86
            if (!$isVoidReturn) {
9✔
87
                return $returnValue;
6✔
88
            }
89

90
            return $taskResult;
3✔
91
        } catch (Throwable $exception) {
2✔
92
            $errorOutput->writeln($this->progressDecorator->failed($exception), $verbosity);
2✔
93

94
            // Early return if exceptions should not be re-thrown
95
            if (!$context->throwExceptions) {
2✔
96
                return TaskResult::Failure;
1✔
97
            }
98

99
            throw $exception;
1✔
100
        } finally {
101
            $this->output->write($taskOutput->fetch());
11✔
102
        }
103
    }
104

105
    private function isVoidReturn(Closure $closure): bool
11✔
106
    {
107
        $reflection = new ReflectionFunction($closure);
11✔
108
        $returnType = $reflection->getReturnType();
11✔
109

110
        return $returnType instanceof ReflectionNamedType && 'void' === $returnType->getName();
11✔
111
    }
112
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc