Completed
Pull Request — master (#109)
by Jan Philipp
01:37
created

Application::run()   A

Complexity

Conditions 2
Paths 5

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 2
nc 5
nop 1
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Psh\Application;
4
5
use InvalidArgumentException;
6
use Khill\Duration\Duration;
7
use League\CLImate\CLImate;
8
use Shopware\Psh\Config\Config;
9
use Shopware\Psh\Config\RequiredValue;
10
use Shopware\Psh\Listing\Script;
11
use Shopware\Psh\Listing\ScriptFinder;
12
use Shopware\Psh\Listing\ScriptNotFoundException;
13
use Shopware\Psh\ScriptRuntime\Execution\ExecutionErrorException;
14
use function array_key_exists;
15
use function array_map;
16
use function array_merge;
17
use function count;
18
use function explode;
19
use function implode;
20
use function mb_strlen;
21
use function sprintf;
22
use function str_replace;
23
24
/**
25
 * Main application entry point. moves the requested data around and outputs user information.
26
 */
27
class Application
28
{
29
    const MIN_PADDING_SIZE = 30;
30
31
    /**
32
     * @var CLImate
33
     */
34
    public $cliMate;
35
36
    /**
37
     * @var string
38
     */
39
    private $rootDirectory;
40
41
    /**
42
     * @var ApplicationFactory
43
     */
44
    private $applicationFactory;
45
46
    /**
47
     * @var Duration
48
     */
49
    private $duration;
50
51
    public function __construct(string $rootDirectory)
52
    {
53
        $this->rootDirectory = $rootDirectory;
54
        $this->applicationFactory = new ApplicationFactory();
55
        $this->cliMate = new CLImate();
56
        $this->duration = new Duration();
57
    }
58
59
    /**
60
     * Main entry point to execute the application.
61
     *
62
     * @return int exit code
63
     */
64
    public function run(array $inputArgs): int
65
    {
66
        try {
67
            $config = $this->prepare($inputArgs);
68
69
            $scriptFinder = $this->applicationFactory->createScriptFinder($config);
70
71
            $this->executeScript($inputArgs, $scriptFinder, $config);
72
73
            $this->showListing($scriptFinder->getAllVisibleScripts());
74
75
            throw ExitSignal::success();
76
        } catch (ExitSignal $signal) {
77
            return $signal->signal();
78
        }
79
    }
80
81
    /**
82
     * @param Script[] $scripts
83
     */
84
    public function showListing(array $scripts): void
85
    {
86
        $this->cliMate->green()->bold('Available commands:')->br();
87
88
        if (!count($scripts)) {
89
            $this->cliMate->yellow()->bold('-> Currently no scripts available');
90
        }
91
92
        $paddingSize = $this->getPaddingSize($scripts);
93
        $padding = $this->cliMate->padding($paddingSize)->char(' ');
94
95
        $scriptEnvironment = false;
96
97
        foreach ($scripts as $script) {
98
            if ($scriptEnvironment !== $script->getEnvironment()) {
99
                $scriptEnvironment = $script->getEnvironment();
100
                $this->cliMate->green()->br()->bold(($scriptEnvironment ?? 'default') . ':');
101
            }
102
103
            $padding
104
                ->label('<bold> - ' . $script->getName() . '</bold>')
105
                ->result('<dim>' . $script->getDescription() . '</dim>');
106
        }
107
108
        $this->cliMate->green()->bold("\n" . count($scripts) . " script(s) available\n");
109
    }
110
111
    protected function extractScriptNames(array $inputArgs): array
112
    {
113
        if (!isset($inputArgs[1])) {
114
            return [];
115
        }
116
117
        return explode(',', $inputArgs[1]);
118
    }
119
120
    protected function execute(Script $script, Config $config, ScriptFinder $scriptFinder): void
121
    {
122
        $commands = $this->applicationFactory
123
            ->createCommands($script, $scriptFinder);
124
125
        $logger = new ClimateLogger($this->cliMate, $this->duration);
126
        $executor = $this->applicationFactory
127
            ->createProcessExecutor($script, $config, $logger, $this->rootDirectory);
128
129
        try {
130
            $executor->execute($script, $commands);
131
        } catch (ExecutionErrorException $e) {
132
            $this->notifyError("\nExecution aborted, a subcommand failed!\n");
133
134
            throw ExitSignal::error();
135
        }
136
137
        $this->notifySuccess("All commands successfully executed!\n");
138
    }
139
140
    /**
141
     * @param $string
142
     */
143
    public function notifySuccess(string $string): void
144
    {
145
        $this->cliMate->bold()->green($string);
146
    }
147
148
    /**
149
     * @param $string
150
     */
151
    public function notifyError(string $string): void
152
    {
153
        $this->cliMate->bold()->red($string);
154
    }
155
156
    /**
157
     * @param $config
158
     */
159
    protected function printHeader(Config $config): void
160
    {
161
        $this->cliMate->green()->bold()->out("\n###################");
162
163
        if ($config->getHeader()) {
164
            $this->cliMate->out("\n" . $config->getHeader());
165
        }
166
    }
167
168
    protected function printConfigFiles(array $configFiles): void
169
    {
170
        $countConfigFiles = count($configFiles);
171
        for ($i = 0; $i < $countConfigFiles; $i++) {
172
            $configFiles[$i] = str_replace($this->rootDirectory . '/', '', $configFiles[$i]);
173
        }
174
175
        if (count($configFiles) === 1) {
176
            $this->cliMate->yellow()->out(sprintf("Using %s \n", $configFiles[0]));
177
178
            return;
179
        }
180
181
        $this->cliMate->yellow()->out(sprintf("Using %s extended by %s \n", $configFiles[0], $configFiles[1]));
182
    }
183
184
    private function getPaddingSize(array $scripts): int
185
    {
186
        $maxScriptNameLength = 0;
187
        foreach ($scripts as $script) {
188
            if (mb_strlen($script->getName()) > $maxScriptNameLength) {
189
                $maxScriptNameLength = mb_strlen($script->getName());
190
            }
191
        }
192
193
        return $maxScriptNameLength + self::MIN_PADDING_SIZE;
194
    }
195
196
    private function showAutocompleteListing(Config $config): void
197
    {
198
        $scriptFinder = $this->applicationFactory
199
            ->createScriptFinder($config);
200
201
        $scripts = $scriptFinder->getAllVisibleScripts();
202
203
        $commands = array_map(function (Script $script) {
204
            return $script->getName();
205
        }, $scripts);
206
207
        $this->cliMate->out(implode(' ', $commands));
208
    }
209
210
    private function showScriptNotFoundListing(ScriptNotFoundException $ex, array $scriptNames, ScriptFinder $scriptFinder): void
211
    {
212
        $this->notifyError("Script with name {$ex->getScriptName()} not found\n");
213
214
        $scripts = [];
215
        foreach ($scriptNames as $scriptName) {
216
            $newScripts = $scriptFinder->findScriptsByPartialName($scriptName);
217
            $scripts = array_merge($scripts, $newScripts);
218
        }
219
220
        if (count($scripts) > 0) {
221
            $this->cliMate->yellow()->bold('Have you been looking for this?');
222
            $this->showListing($scripts);
223
        }
224
    }
225
226
    private function printHead(Config $config): void
227
    {
228
        $this->printHeader($config);
229
230
        $configFiles = $this->applicationFactory->getConfigFiles($this->rootDirectory);
231
        $this->printConfigFiles($configFiles);
232
    }
233
234
    private function validateConfig(Config $config, string $environment = null): void
235
    {
236
        $allPlaceholders = $config->getAllPlaceholders($environment);
237
238
        $missing = [];
239
        foreach ($config->getRequiredVariables($environment) as $requiredVariable) {
240
            if (!array_key_exists($requiredVariable->getName(), $allPlaceholders)) {
241
                $missing[] = $requiredVariable;
242
                $this->printMissingRequiredVariable($requiredVariable);
243
            }
244
        }
245
246
        if (count($missing)) {
247
            $this->cliMate->error("\n<bold>Please define the missing value(s) first</bold>\n");
248
            throw ExitSignal::error();
249
        }
250
    }
251
252
    private function printMissingRequiredVariable(RequiredValue $requiredVariable): void
253
    {
254
        if ($requiredVariable->hasDescription()) {
255
            $this->cliMate->error(sprintf(
256
                "\t - <bold>Missing required const or var named <underline>%s</underline></bold> <dim>(%s)</dim>",
257
                $requiredVariable->getName(),
258
                $requiredVariable->getDescription()
259
            ));
260
        } else {
261
            $this->cliMate->error(sprintf(
262
                "\t - <bold>Missing required const or var named <underline>%s</underline></bold>",
263
                $requiredVariable->getName()
264
            ));
265
        }
266
    }
267
268
    private function prepare(array $inputArgs): Config
269
    {
270
        try {
271
            $config = $this->applicationFactory
272
                ->createConfig($this->rootDirectory, $inputArgs);
273
        } catch (InvalidParameterException $e) {
274
            $this->notifyError($e->getMessage() . "\n");
275
276
            throw ExitSignal::error();
277
        } catch (InvalidArgumentException $e) {
278
            $this->notifyError("\n" . $e->getMessage() . "\n");
279
280
            throw ExitSignal::error();
281
        }
282
283
        if (count($inputArgs) > 1 && $inputArgs[1] === 'bash_autocompletion_dump') {
284
            $this->showAutocompleteListing($config);
285
286
            throw ExitSignal::success();
287
        }
288
289
        $this->printHead($config);
290
        $this->validateConfig($config);
291
292
        return $config;
293
    }
294
295
    private function executeScript(array $inputArgs, ScriptFinder $scriptFinder, Config $config): void
296
    {
297
        $scriptNames = $this->extractScriptNames($inputArgs);
298
299
        if (!count($scriptNames)) {
300
            return;
301
        }
302
303
        try {
304
            foreach ($scriptNames as $scriptName) {
305
                $this->execute($scriptFinder->findScriptByName($scriptName), $config, $scriptFinder);
306
            }
307
        } catch (ScriptNotFoundException $e) {
308
            $this->showScriptNotFoundListing($e, $scriptNames, $scriptFinder);
309
310
            throw ExitSignal::error();
311
        }
312
313
        throw ExitSignal::success();
314
    }
315
}
316