Passed
Push — master ( a91ab9...0e37d9 )
by Pol
02:27
created

Runner::getCommandArgumentMode()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 2
dl 0
loc 10
ccs 0
cts 9
cp 0
crap 12
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 PhpTaskman\Core\Robo\Plugin\Commands\YamlCommands;
10
use League\Container\ContainerAwareTrait;
11
use Robo\Application;
12
use Robo\Common\ConfigAwareTrait;
13
use Symfony\Component\Console\Input\ArgvInput;
14
use Symfony\Component\Console\Input\InputArgument;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Output\ConsoleOutput;
17
use Symfony\Component\Console\Output\OutputInterface;
18
19
/**
20
 * Class Runner.
21
 */
22
final class Runner
23
{
24
    use ConfigAwareTrait;
25
    use ContainerAwareTrait;
26
27
    /**
28
     * @var Application
29
     */
30
    private $application;
31
32
    /**
33
     * @var \Composer\Autoload\ClassLoader
34
     */
35
    private $classLoader;
36
37
    /**
38
     * @var InputInterface
39
     */
40
    private $input;
41
42
    /**
43
     * @var OutputInterface
44
     */
45
    private $output;
46
47
    /**
48
     * @var \Robo\Runner
49
     */
50
    private $runner;
51
52
    /**
53
     * @var string
54
     */
55
    private $workingDir;
56
57
    /**
58
     * Runner constructor.
59
     *
60
     * @param InputInterface $input
61
     * @param OutputInterface $output
62
     * @param ClassLoader $classLoader
63
     */
64
    public function __construct(
65
        InputInterface $input = null,
66
        OutputInterface $output = null,
67
        ClassLoader $classLoader = null
68
    ) {
69
        $this->input = $input ?? new ArgvInput();
70
        $this->output = $output ?? new ConsoleOutput();
71
        $this->classLoader = $classLoader ?? new ClassLoader();
72
73
        $this->workingDir = $this->getWorkingDir($this->input);
74
        \chdir($this->workingDir);
75
76
        $this->config = Taskman::createConfiguration(
77
            [],
78
            $this->workingDir
79
        );
80
        $this->application = Taskman::createDefaultApplication(
81
            null,
82
            null,
83
            $this->workingDir
84
        );
85
        $this->container = Taskman::createContainer(
86
            $this->input,
87
            $this->output,
88
            $this->application,
89
            $this->config,
90
            $this->classLoader
91
        );
92
93
        $this->runner = Taskman::createDefaultRunner($this->container);
94
    }
95
96
    /**
97
     * @param mixed $args
98
     *
99
     * @return int
100
     */
101
    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

101
    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...
102
    {
103
        // Register command classes.
104
        $this->runner->registerCommandClasses($this->application, [YamlCommands::class]);
105
106
        // Register commands defined in task.yml file.
107
        $this->registerDynamicCommands($this->application);
108
109
        // Run command.
110
        return $this->runner->run($this->input, $this->output, $this->application);
111
    }
112
113
    /**
114
     * @param $hasDefault
115
     * @param $defaultValue
116
     *
117
     * @return int
118
     */
119
    protected function getCommandArgumentMode($hasDefault, $defaultValue)
120
    {
121
        if (!$hasDefault) {
122
            return InputArgument::REQUIRED;
123
        }
124
        if (\is_array($defaultValue)) {
125
            return InputArgument::IS_ARRAY;
126
        }
127
128
        return InputArgument::OPTIONAL;
129
    }
130
131
    /**
132
     * @param \Consolidation\AnnotatedCommand\AnnotatedCommand $command
133
     * @param array $commandDefinition
134
     */
135
    private function addOptions(AnnotatedCommand $command, array $commandDefinition)
136
    {
137
        // This command doesn't define any option.
138
        if (empty($commandDefinition['options'])) {
139
            return;
140
        }
141
142
        $defaults = \array_fill_keys(['shortcut', 'mode', 'description', 'default'], null);
143
        foreach ($commandDefinition['options'] as $optionName => $optionDefinition) {
144
            $optionDefinition += $defaults;
145
            $command->addOption(
146
                '--' . $optionName,
147
                $optionDefinition['shortcut'],
148
                $optionDefinition['mode'],
149
                $optionDefinition['description'],
150
                $optionDefinition['default']
151
            );
152
        }
153
    }
154
155
    /**
156
     * @param string $command
157
     *
158
     * @throws \InvalidArgumentException
159
     *
160
     * @return array
161
     */
162
    private function getTasks($command)
163
    {
164
        $commands = $this->getConfig()->get('commands', []);
165
166
        if (!isset($commands[$command])) {
167
            throw new \InvalidArgumentException("Custom command '${command}' not defined.");
168
        }
169
170
        return !empty($commands[$command]['tasks']) ? $commands[$command]['tasks'] : $commands[$command];
171
    }
172
173
    /**
174
     * @param \Symfony\Component\Console\Input\InputInterface $input
175
     *
176
     * @return mixed
177
     */
178
    private function getWorkingDir(InputInterface $input)
179
    {
180
        return $input->getParameterOption('--working-dir', \getcwd());
181
    }
182
183
    /**
184
     * @param \Robo\Application $application
185
     */
186
    private function registerDynamicCommands(Application $application)
187
    {
188
        $customCommands = $this->getConfig()->get('commands', []);
189
        foreach ($customCommands as $name => $commandDefinition) {
190
            /** @var \Consolidation\AnnotatedCommand\AnnotatedCommandFactory $commandFactory */
191
            $commandFileName = YamlCommands::class . 'Commands';
192
            $commandClass = $this->container->get($commandFileName);
193
            $commandFactory = $this->container->get('commandFactory');
194
            $commandInfo = $commandFactory->createCommandInfo($commandClass, 'runTasks');
195
196
            $commandDefinition += ['options' => []];
197
            $commandDefinition['description'] = 'foo';
198
199
            foreach ($commandDefinition['options'] as &$option) {
200
                if (isset($option['mode'])) {
201
                    continue;
202
                }
203
204
                $option['mode'] = $this->getCommandArgumentMode(
205
                    isset($option['default']),
206
                    $option['default'] ?? null
207
                );
208
            }
209
210
            $command = $commandFactory->createCommand($commandInfo, $commandClass)->setName($name);
211
212
            // Dynamic commands may define their own options.
213
            $this->addOptions($command, $commandDefinition);
214
215
            // Append also options of subsequent tasks.
216
            foreach ($this->getTasks($name) as $taskEntry) {
217
                if (!\is_array($taskEntry)) {
218
                    continue;
219
                }
220
221
                if (!isset($taskEntry['task'])) {
222
                    continue;
223
                }
224
225
                if ('run' !== $taskEntry['task']) {
226
                    continue;
227
                }
228
229
                if (empty($taskEntry['command'])) {
230
                    continue;
231
                }
232
233
                // This is a 'run' task.
234
                if (!empty($customCommands[$taskEntry['command']])) {
235
                    // Add the options of another custom command.
236
                    $this->addOptions($command, $customCommands[$taskEntry['command']]);
237
                } else {
238
                    // Add the options of an already registered command.
239
                    if ($this->application->has($taskEntry['command'])) {
240
                        $subCommand = $this->application->get($taskEntry['command']);
241
                        $command->addOptions($subCommand->getDefinition()->getOptions());
242
                    }
243
                }
244
            }
245
246
            $application->add($command);
247
        }
248
    }
249
}
250