Passed
Push — master ( 7a3e19...503fea )
by Pol
06:56 queued 16s
created

Runner::getWorkingDir()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 1
dl 0
loc 2
ccs 0
cts 1
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace PhpTaskman\Core;
6
7
use Composer\Autoload\ClassLoader;
8
use Consolidation\AnnotatedCommand\AnnotatedCommand;
9
use League\Container\Inflector\Inflector;
10
use PhpTaskman\Core\Plugin\Task\YamlTask;
11
use PhpTaskman\Core\Robo\Plugin\Commands\YamlCommands;
12
use League\Container\ContainerAwareTrait;
13
use Robo\Application;
14
use Robo\Collection\CollectionBuilder;
15
use Robo\Common\ConfigAwareTrait;
16
use Robo\Contract\BuilderAwareInterface;
17
use Symfony\Component\Console\Input\ArgvInput;
18
use Symfony\Component\Console\Input\InputArgument;
19
use Symfony\Component\Console\Input\InputInterface;
20
use Symfony\Component\Console\Output\ConsoleOutput;
21
use Symfony\Component\Console\Output\OutputInterface;
22
23
/**
24
 * Class Runner.
25
 */
26
final class Runner
27
{
28
    use ConfigAwareTrait;
29
    use ContainerAwareTrait;
30
31
    /**
32
     * @var Application
33
     */
34
    private $application;
35
36
    /**
37
     * @var \Composer\Autoload\ClassLoader
38
     */
39
    private $classLoader;
40
41
    /**
42
     * @var InputInterface
43
     */
44
    private $input;
45
46
    /**
47
     * @var OutputInterface
48
     */
49
    private $output;
50
51
    /**
52
     * @var \Robo\Runner
53
     */
54
    private $runner;
55
56
    /**
57
     * @var string
58
     */
59
    private $workingDir;
60
61
    /**
62
     * Runner constructor.
63
     *
64
     * @param InputInterface $input
65
     * @param OutputInterface $output
66
     * @param ClassLoader $classLoader
67
     */
68
    public function __construct(
69
        InputInterface $input = null,
70
        OutputInterface $output = null,
71
        ClassLoader $classLoader = null
72
    ) {
73
        $this->input = $input ?? new ArgvInput();
74
        $this->output = $output ?? new ConsoleOutput();
75
        $this->classLoader = $classLoader ?? new ClassLoader();
76
77
        \chdir($this->input->getParameterOption('--working-dir', \getcwd()));
78
79
        $this->config = Taskman::createConfiguration(
80
            [],
81
            $this->workingDir
82
        );
83
        $this->application = Taskman::createDefaultApplication(
84
            null,
85
            null,
86
            $this->workingDir
87
        );
88
        $this->container = Taskman::createContainer(
89
            $this->input,
90
            $this->output,
91
            $this->application,
92
            $this->config,
93
            $this->classLoader
94
        );
95
96
        $this->runner = Taskman::createDefaultRunner($this->container);
97
    }
98
99
    /**
100
     * @param mixed $args
101
     *
102
     * @throws \ReflectionException
103
     *
104
     * @return int
105
     */
106
    public function run($args)
0 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

106
    public function run(/** @scrutinizer ignore-unused */ $args)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
107
    {
108
        // Register command classes.
109
        $this->runner->registerCommandClasses($this->application, [YamlCommands::class]);
110
111
        // Register commands defined in task.yml file.
112
        $this->registerDynamicCommands($this->application);
113
114
        // Register tasks
115
        $this->registerDynamicTasks($this->application);
116
117
        // Run command.
118
        return $this->runner->run($this->input, $this->output, $this->application);
119
    }
120
121
    /**
122
     * @param bool $hasDefault
123
     * @param mixed $defaultValue
124
     *
125
     * @return int
126
     */
127
    protected function getCommandArgumentMode(bool $hasDefault, $defaultValue)
128
    {
129
        if (!$hasDefault) {
130
            return InputArgument::REQUIRED;
131
        }
132
        if (\is_array($defaultValue)) {
133
            return InputArgument::IS_ARRAY;
134
        }
135
136
        return InputArgument::OPTIONAL;
137
    }
138
139
    /**
140
     * @param \Consolidation\AnnotatedCommand\AnnotatedCommand $command
141
     * @param array $commandDefinition
142
     */
143
    private function addOptions(AnnotatedCommand $command, array $commandDefinition)
144
    {
145
        // This command doesn't define any option.
146
        if (empty($commandDefinition['options'])) {
147
            return;
148
        }
149
150
        $defaults = \array_fill_keys(['shortcut', 'mode', 'description', 'default'], null);
151
        foreach ($commandDefinition['options'] as $optionName => $optionDefinition) {
152
            $optionDefinition += $defaults;
153
            $command->addOption(
154
                '--' . $optionName,
155
                $optionDefinition['shortcut'],
156
                $optionDefinition['mode'],
157
                $optionDefinition['description'],
158
                $optionDefinition['default']
159
            );
160
        }
161
    }
162
163
    /**
164
     * @param \Robo\Application $application
165
     */
166
    private function registerDynamicCommands(Application $application)
167
    {
168
        $commandDefinitions = $this->getConfig()->get('commands', []);
169
170
        foreach ($commandDefinitions as $name => $commandDefinition) {
171
            /** @var \PhpTaskman\Core\Robo\Plugin\Commands\YamlCommands $commandClass */
172
            $commandClass = $this->container->get(YamlCommands::class . 'Commands');
173
174
            /** @var \Consolidation\AnnotatedCommand\AnnotatedCommandFactory $commandFactory */
175
            $commandFactory = $this->container->get('commandFactory');
176
177
            $commandInfo = $commandFactory->createCommandInfo($commandClass, 'runTasks');
178
179
            $commandDefinition += ['options' => []];
180
            foreach ($commandDefinition['options'] as &$option) {
181
                if (isset($option['mode'])) {
182
                    continue;
183
                }
184
185
                $option['mode'] = $this->getCommandArgumentMode(
186
                    isset($option['default']),
187
                    $option['default'] ?? null
188
                );
189
            }
190
191
            $command = $commandFactory->createCommand($commandInfo, $commandClass)->setName($name);
192
193
            // Override default description.
194
            if (isset($commandDefinition['description'])) {
195
                $command->setDescription($commandDefinition['description']);
196
            }
197
            // Override default help.
198
            if (isset($commandDefinition['help'])) {
199
                $command->setHelp($commandDefinition['help']);
200
            }
201
202
            // Dynamic commands may define their own options.
203
            $this->addOptions($command, $commandDefinition);
204
205
            $tasks = $commandDefinition['tasks'] ?? $commandDefinition;
206
207
            // Append also options of subsequent tasks.
208
            foreach ($tasks as $taskEntry) {
209
                if (!\is_array($taskEntry)) {
210
                    continue;
211
                }
212
213
                if (!isset($taskEntry['task'])) {
214
                    continue;
215
                }
216
217
                if ('run' !== $taskEntry['task']) {
218
                    continue;
219
                }
220
221
                if (empty($taskEntry['command'])) {
222
                    continue;
223
                }
224
225
                // This is a 'run' task.
226
                if (!empty($customCommands[$taskEntry['command']])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $customCommands seems to never exist and therefore empty should always be true.
Loading history...
227
                    // Add the options of another custom command.
228
                    $this->addOptions($command, $customCommands[$taskEntry['command']]);
229
                } else {
230
                    // Add the options of an already registered command.
231
                    if ($this->application->has($taskEntry['command'])) {
232
                        $subCommand = $this->application->get($taskEntry['command']);
233
                        $command->addOptions($subCommand->getDefinition()->getOptions());
234
                    }
235
                }
236
            }
237
238
            $application->add($command);
239
        }
240
    }
241
242
    /**
243
     * @param \Robo\Application $application
244
     *
245
     * @throws \ReflectionException
246
     */
247
    private function registerDynamicTasks(Application $application)
0 ignored issues
show
Unused Code introduced by
The parameter $application is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

247
    private function registerDynamicTasks(/** @scrutinizer ignore-unused */ Application $application)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
248
    {
249
        $classes = Taskman::discoverTasksClasses('Plugin');
250
251
        /** @var \ReflectionClass[] $tasks */
252
        $tasks = [];
253
        foreach ($classes as $className) {
254
            $class = new \ReflectionClass($className);
255
            if (!$class->isInstantiable()) {
256
                continue;
257
            }
258
            $tasks[] = $class;
259
        }
260
261
        $builder = CollectionBuilder::create($this->container, '');
262
263
        $inflector = $this->container->inflector(BuilderAwareInterface::class);
264
        if ($inflector instanceof Inflector) {
0 ignored issues
show
introduced by
$inflector is always a sub-type of League\Container\Inflector\Inflector.
Loading history...
265
            $inflector->invokeMethod('setBuilder', [$builder]);
266
        }
267
268
        // Register custom Task classes.
269
        foreach ($tasks as $taskReflectionClass) {
270
            $this->container->add(
271
                'task.' . $taskReflectionClass->getConstant('NAME'),
272
                $taskReflectionClass->getName()
273
            );
274
        }
275
276
        // Register custom YAML tasks.
277
        $customTasks = $this->getConfig()->get('tasks', []);
278
279
        foreach ($customTasks as $name => $tasks) {
280
            $this->container->add(
281
                'task.' . $name,
282
                YamlTask::class
283
            )->withArgument($tasks);
284
        }
285
    }
286
}
287