1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
namespace Shopware\Psh\Application; |
4
|
|
|
|
5
|
|
|
use Khill\Duration\Duration; |
6
|
|
|
use League\CLImate\CLImate; |
7
|
|
|
use Shopware\Psh\Config\Config; |
8
|
|
|
use Shopware\Psh\Config\RequiredValue; |
9
|
|
|
use Shopware\Psh\Listing\Script; |
10
|
|
|
use Shopware\Psh\Listing\ScriptFinder; |
11
|
|
|
use Shopware\Psh\Listing\ScriptNotFound; |
12
|
|
|
use Shopware\Psh\PshErrorMessage; |
13
|
|
|
use Shopware\Psh\ScriptRuntime\Execution\ExecutionError; |
14
|
|
|
use function array_key_exists; |
15
|
|
|
use function array_map; |
16
|
|
|
use function count; |
17
|
|
|
use function implode; |
18
|
|
|
use function max; |
19
|
|
|
use function mb_strlen; |
20
|
|
|
use function sprintf; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Main application entry point. moves the requested data around and outputs user information. |
24
|
|
|
*/ |
25
|
|
|
class Application |
26
|
|
|
{ |
27
|
|
|
private const MIN_PADDING_SIZE = 30; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var CLImate |
31
|
|
|
*/ |
32
|
|
|
public $cliMate; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var string |
36
|
|
|
*/ |
37
|
|
|
private $rootDirectory; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var ApplicationFactory |
41
|
|
|
*/ |
42
|
|
|
private $applicationFactory; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var Duration |
46
|
|
|
*/ |
47
|
|
|
private $duration; |
48
|
|
|
|
49
|
|
|
public function __construct(string $rootDirectory) |
50
|
|
|
{ |
51
|
|
|
$this->rootDirectory = $rootDirectory; |
52
|
|
|
$this->applicationFactory = new ApplicationFactory(); |
53
|
|
|
$this->cliMate = new CLImate(); |
54
|
|
|
$this->duration = new Duration(); |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Main entry point to execute the application. |
59
|
|
|
* |
60
|
|
|
* @return int exit code |
61
|
|
|
*/ |
62
|
|
|
public function run(array $inputArgs): int |
63
|
|
|
{ |
64
|
|
|
try { |
65
|
|
|
$config = $this->prepare($inputArgs); |
66
|
|
|
|
67
|
|
|
$scriptFinder = $this->applicationFactory->createScriptFinder($config); |
68
|
|
|
|
69
|
|
|
$this->executeScript($scriptFinder, $config); |
70
|
|
|
|
71
|
|
|
$this->showListing($scriptFinder->getAllVisibleScripts()); |
72
|
|
|
|
73
|
|
|
throw ExitSignal::success(); |
74
|
|
|
} catch (PshErrorMessage $error) { |
75
|
|
|
$this->notifyError("\n" . $error->getMessage() . "\n"); |
76
|
|
|
|
77
|
|
|
return ExitSignal::error()->signal(); |
78
|
|
|
} catch (ExitSignal $signal) { |
79
|
|
|
return $signal->signal(); |
80
|
|
|
} |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* @param Script[] $scripts |
85
|
|
|
*/ |
86
|
|
|
private function showListing(array $scripts): void |
87
|
|
|
{ |
88
|
|
|
$this->cliMate->green()->bold('Available commands:')->br(); |
89
|
|
|
|
90
|
|
|
if (!count($scripts)) { |
91
|
|
|
$this->cliMate->yellow()->bold("-> Currently no scripts available\n"); |
92
|
|
|
|
93
|
|
|
return; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
$paddingSize = $this->getPaddingSize($scripts); |
97
|
|
|
$padding = $this->cliMate->padding($paddingSize)->char(' '); |
98
|
|
|
|
99
|
|
|
$scriptEnvironment = 'default'; |
100
|
|
|
foreach ($scripts as $script) { |
101
|
|
|
if ($script->getEnvironment() !== $scriptEnvironment) { |
102
|
|
|
$scriptEnvironment = $script->getEnvironment(); |
103
|
|
|
$this->cliMate->green()->br()->bold(($scriptEnvironment ?? 'default') . ':'); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
$padding |
107
|
|
|
->label(sprintf('<bold> - %s</bold>', $script->getName())) |
108
|
|
|
->result(sprintf('<dim>%s</dim>', $script->getDescription())); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
$this->cliMate->green()->bold(sprintf("\n %s script(s) available\n", count($scripts))); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
private function execute(Script $script, Config $config, ScriptFinder $scriptFinder): void |
115
|
|
|
{ |
116
|
|
|
$commands = $this->applicationFactory |
117
|
|
|
->createCommands($script, $scriptFinder); |
118
|
|
|
|
119
|
|
|
$logger = new ClimateLogger($this->cliMate, $this->duration); |
120
|
|
|
$executor = $this->applicationFactory |
121
|
|
|
->createProcessExecutor($script, $config, $logger); |
122
|
|
|
|
123
|
|
|
try { |
124
|
|
|
$executor->execute($script, $commands); |
125
|
|
|
} catch (ExecutionError $e) { |
126
|
|
|
$this->notifyError("\nExecution aborted, a subcommand failed!\n"); |
127
|
|
|
|
128
|
|
|
throw ExitSignal::error(); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
$this->notifySuccess("All commands successfully executed!\n"); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* @param $string |
136
|
|
|
*/ |
137
|
|
|
private function notifySuccess(string $string): void |
138
|
|
|
{ |
139
|
|
|
$this->cliMate->bold()->green($string); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* @param $string |
144
|
|
|
*/ |
145
|
|
|
private function notifyError(string $string): void |
146
|
|
|
{ |
147
|
|
|
$this->cliMate->bold()->red($string); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
private function getPaddingSize(array $scripts): int |
151
|
|
|
{ |
152
|
|
|
return self::MIN_PADDING_SIZE + max(array_map(static function (Script $script) { |
153
|
|
|
return mb_strlen($script->getName()); |
154
|
|
|
}, $scripts)); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
private function showAutocompleteListing(Config $config): void |
158
|
|
|
{ |
159
|
|
|
$scriptFinder = $this->applicationFactory |
160
|
|
|
->createScriptFinder($config); |
161
|
|
|
|
162
|
|
|
$scripts = $scriptFinder->getAllVisibleScripts(); |
163
|
|
|
|
164
|
|
|
$commands = array_map(static function (Script $script) { |
165
|
|
|
return $script->getName(); |
166
|
|
|
}, $scripts); |
167
|
|
|
|
168
|
|
|
$this->cliMate->out(implode(' ', $commands)); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
private function showScriptNotFoundListing(string $scriptName, ScriptFinder $scriptFinder): void |
172
|
|
|
{ |
173
|
|
|
$this->notifyError(sprintf("Script with name %s not found\n", $scriptName)); |
174
|
|
|
|
175
|
|
|
$scripts = $scriptFinder->findScriptsByPartialName($scriptName); |
176
|
|
|
|
177
|
|
|
if (count($scripts) > 0) { |
178
|
|
|
$this->cliMate->yellow()->bold('Have you been looking for this?'); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
$this->showListing($scripts); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
private function printHead(Config $config, ApplicationConfigLogger $logger): void |
185
|
|
|
{ |
186
|
|
|
if ($config->hasOption(ApplicationOptions::FLAG_NO_HEADER)) { |
187
|
|
|
return; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
$this->cliMate->green()->bold()->out("\n################### PSH@" . PSH_VERSION); |
191
|
|
|
|
192
|
|
|
if ($config->getHeader()) { |
193
|
|
|
$this->cliMate->out("\n" . $config->getHeader()); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
$logger->printOut($this->cliMate); |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
private function validateConfig(Config $config, ?string $environment = null): void |
200
|
|
|
{ |
201
|
|
|
$allPlaceholders = $config->getAllPlaceholders($environment); |
202
|
|
|
|
203
|
|
|
$missing = []; |
204
|
|
|
foreach ($config->getRequiredVariables($environment) as $requiredVariable) { |
205
|
|
|
if (!array_key_exists($requiredVariable->getName(), $allPlaceholders)) { |
206
|
|
|
$missing[] = $requiredVariable; |
207
|
|
|
$this->printMissingRequiredVariable($requiredVariable); |
208
|
|
|
} |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
if (count($missing)) { |
212
|
|
|
$this->cliMate->error("\n<bold>Please define the missing value(s) first</bold>\n"); |
213
|
|
|
throw ExitSignal::error(); |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
private function printMissingRequiredVariable(RequiredValue $requiredVariable): void |
218
|
|
|
{ |
219
|
|
|
if ($requiredVariable->hasDescription()) { |
220
|
|
|
$this->cliMate->error(sprintf( |
221
|
|
|
"\t - <bold>Missing required const or var named <underline>%s</underline></bold> <dim>(%s)</dim>", |
222
|
|
|
$requiredVariable->getName(), |
223
|
|
|
$requiredVariable->getDescription() |
224
|
|
|
)); |
225
|
|
|
} else { |
226
|
|
|
$this->cliMate->error(sprintf( |
227
|
|
|
"\t - <bold>Missing required const or var named <underline>%s</underline></bold>", |
228
|
|
|
$requiredVariable->getName() |
229
|
|
|
)); |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
private function prepare(array $inputArgs): Config |
234
|
|
|
{ |
235
|
|
|
$configLogger = new ApplicationConfigLogger($this->rootDirectory, $this->cliMate); |
|
|
|
|
236
|
|
|
|
237
|
|
|
$config = $this->applicationFactory |
238
|
|
|
->createConfig($configLogger, $this->rootDirectory, $inputArgs); |
239
|
|
|
|
240
|
|
|
if (count($inputArgs) > 1 && $inputArgs[1] === 'bash_autocompletion_dump') { |
241
|
|
|
$this->showAutocompleteListing($config); |
242
|
|
|
|
243
|
|
|
throw ExitSignal::success(); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
$this->printHead($config, $configLogger); |
247
|
|
|
$this->validateConfig($config); |
248
|
|
|
|
249
|
|
|
return $config; |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
private function executeScript(ScriptFinder $scriptFinder, Config $config): void |
253
|
|
|
{ |
254
|
|
|
$scriptNames = $config->getScriptNames(); |
255
|
|
|
|
256
|
|
|
if (!count($scriptNames)) { |
257
|
|
|
return; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
try { |
261
|
|
|
$scripts = $scriptFinder->findScriptsInOrder($scriptNames); |
262
|
|
|
} catch (ScriptNotFound $e) { |
263
|
|
|
$this->showScriptNotFoundListing($e->getScriptName(), $scriptFinder); |
264
|
|
|
throw ExitSignal::error(); |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
foreach ($scripts as $script) { |
268
|
|
|
$this->execute($script, $config, $scriptFinder); |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
throw ExitSignal::success(); |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.