|
1
|
|
|
<?php declare(strict_types=1); |
|
2
|
|
|
|
|
3
|
|
|
|
|
4
|
|
|
namespace Shopware\Psh\ScriptRuntime; |
|
5
|
|
|
|
|
6
|
|
|
use Shopware\Psh\Listing\Script; |
|
7
|
|
|
|
|
8
|
|
|
/** |
|
9
|
|
|
* Load scripts and parse it into commands |
|
10
|
|
|
*/ |
|
11
|
|
|
class ScriptParser |
|
12
|
|
|
{ |
|
13
|
|
|
const MODIFIER_IS_TTY = 'TTY: '; |
|
14
|
|
|
|
|
15
|
|
|
const MODIFIER_IGNORE_ERROR_PREFIX = 'I: '; |
|
16
|
|
|
|
|
17
|
|
|
const INCLUDE_STATEMENT_PREFIX = 'INCLUDE: '; |
|
18
|
|
|
|
|
19
|
|
|
const TEMPLATE_STATEMENT_PREFIX = 'TEMPLATE: '; |
|
20
|
|
|
|
|
21
|
|
|
const CONCATENATE_PREFIX = ' '; |
|
22
|
|
|
|
|
23
|
|
|
/** |
|
24
|
|
|
* @var CommandBuilder |
|
25
|
|
|
*/ |
|
26
|
|
|
private $commandBuilder; |
|
27
|
|
|
|
|
28
|
|
|
/** |
|
29
|
|
|
* @param CommandBuilder $commandBuilder |
|
30
|
|
|
*/ |
|
31
|
|
|
public function __construct(CommandBuilder $commandBuilder) |
|
32
|
|
|
{ |
|
33
|
|
|
$this->commandBuilder = $commandBuilder; |
|
34
|
|
|
} |
|
35
|
|
|
|
|
36
|
|
|
/** |
|
37
|
|
|
* @param Script $script |
|
38
|
|
|
* @return array |
|
39
|
|
|
*/ |
|
40
|
|
|
public function loadScript(Script $script): array |
|
41
|
|
|
{ |
|
42
|
|
|
$content = $this->loadFileContents($script->getPath()); |
|
43
|
|
|
$lines = explode("\n", $content); |
|
44
|
|
|
|
|
45
|
|
|
$lines = $this->concatenateStatements($lines); |
|
46
|
|
|
|
|
47
|
|
|
foreach ($lines as $lineNumber => $currentLine) { |
|
48
|
|
|
if ($this->isNotHandledAsCommand($script, $lineNumber, $currentLine)) { |
|
49
|
|
|
continue; |
|
50
|
|
|
} |
|
51
|
|
|
|
|
52
|
|
|
$this->commandBuilder->finishLast(); |
|
53
|
|
|
|
|
54
|
|
View Code Duplication |
if ($this->startsWith(self::MODIFIER_IGNORE_ERROR_PREFIX, $currentLine)) { |
|
|
|
|
|
|
55
|
|
|
$currentLine = $this->removeFromStart(self::MODIFIER_IGNORE_ERROR_PREFIX, $currentLine); |
|
56
|
|
|
$this->commandBuilder->setIgnoreError(); |
|
57
|
|
|
} |
|
58
|
|
|
|
|
59
|
|
View Code Duplication |
if ($this->startsWith(self::MODIFIER_IS_TTY, $currentLine)) { |
|
|
|
|
|
|
60
|
|
|
$currentLine = $this->removeFromStart(self::MODIFIER_IS_TTY, $currentLine); |
|
61
|
|
|
$this->commandBuilder->setTty(); |
|
62
|
|
|
} |
|
63
|
|
|
|
|
64
|
|
|
$this->commandBuilder |
|
65
|
|
|
->setExecutable($currentLine, $lineNumber); |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
return $this->commandBuilder->finishLast() |
|
69
|
|
|
->getAll(); |
|
70
|
|
|
} |
|
71
|
|
|
|
|
72
|
|
|
/** |
|
73
|
|
|
* @param Script $script |
|
74
|
|
|
* @param int $lineNumber |
|
75
|
|
|
* @param string $currentLine |
|
76
|
|
|
* @return bool |
|
77
|
|
|
*/ |
|
78
|
|
|
private function isNotHandledAsCommand(Script $script, int $lineNumber, string $currentLine): bool |
|
79
|
|
|
{ |
|
80
|
|
|
if (!$this->isExecutableLine($currentLine)) { |
|
81
|
|
|
return true; |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
|
|
if ($this->startsWith(self::INCLUDE_STATEMENT_PREFIX, $currentLine)) { |
|
85
|
|
|
$path = $this->findInclude($script, $this->removeFromStart(self::INCLUDE_STATEMENT_PREFIX, $currentLine)); |
|
86
|
|
|
$includeScript = new Script(pathinfo($path, PATHINFO_DIRNAME), pathinfo($path, PATHINFO_BASENAME)); |
|
87
|
|
|
|
|
88
|
|
|
$commands = $this->loadScript($includeScript); |
|
89
|
|
|
$this->commandBuilder->setCommands($commands); |
|
90
|
|
|
|
|
91
|
|
|
return true; |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
if ($this->startsWith(self::TEMPLATE_STATEMENT_PREFIX, $currentLine)) { |
|
95
|
|
|
$definition = $this->removeFromStart(self::TEMPLATE_STATEMENT_PREFIX, $currentLine); |
|
96
|
|
|
list($rawSource, $rawDestination) = explode(':', $definition); |
|
97
|
|
|
|
|
98
|
|
|
$source = $script->getDirectory() . '/' . $rawSource; |
|
99
|
|
|
$destination = $script->getDirectory() . '/' . $rawDestination; |
|
100
|
|
|
|
|
101
|
|
|
$this->commandBuilder |
|
102
|
|
|
->finishLast() |
|
103
|
|
|
->addTemplateCommand($source, $destination, $lineNumber); |
|
104
|
|
|
|
|
105
|
|
|
return true; |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
return false; |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
/** |
|
112
|
|
|
* @param Script $fromScript |
|
113
|
|
|
* @param string $includeStatement |
|
114
|
|
|
* @return string |
|
115
|
|
|
*/ |
|
116
|
|
|
private function findInclude(Script $fromScript, string $includeStatement): string |
|
117
|
|
|
{ |
|
118
|
|
|
if (file_exists($includeStatement)) { |
|
119
|
|
|
return $includeStatement; |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
if (file_exists($fromScript->getDirectory() . '/' . $includeStatement)) { |
|
123
|
|
|
return $fromScript->getDirectory() . '/' . $includeStatement; |
|
124
|
|
|
} |
|
125
|
|
|
|
|
126
|
|
|
throw new \RuntimeException('Unable to parse include statement "' . $includeStatement . '" in "' . $fromScript->getPath() . '"'); |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
/** |
|
130
|
|
|
* @param string $command |
|
131
|
|
|
* @return bool |
|
132
|
|
|
*/ |
|
133
|
|
|
private function isExecutableLine(string $command): bool |
|
134
|
|
|
{ |
|
135
|
|
|
$command = trim($command); |
|
136
|
|
|
|
|
137
|
|
|
if (!$command) { |
|
138
|
|
|
return false; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
if ($this->startsWith('#', $command)) { |
|
142
|
|
|
return false; |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
return true; |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* @param string $needle |
|
150
|
|
|
* @param string $haystack |
|
151
|
|
|
* @return string |
|
152
|
|
|
*/ |
|
153
|
|
|
private function removeFromStart(string $needle, string $haystack): string |
|
154
|
|
|
{ |
|
155
|
|
|
return substr($haystack, strlen($needle)); |
|
156
|
|
|
} |
|
157
|
|
|
|
|
158
|
|
|
/** |
|
159
|
|
|
* @param string $needle |
|
160
|
|
|
* @param string $haystack |
|
161
|
|
|
* @return bool |
|
162
|
|
|
*/ |
|
163
|
|
|
private function startsWith(string $needle, string $haystack): bool |
|
164
|
|
|
{ |
|
165
|
|
|
return strpos($haystack, $needle) === 0; |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
|
|
/** |
|
169
|
|
|
* @param string $file |
|
170
|
|
|
* @return string |
|
171
|
|
|
*/ |
|
172
|
|
|
private function loadFileContents(string $file): string |
|
173
|
|
|
{ |
|
174
|
|
|
return file_get_contents($file); |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
|
|
/** |
|
178
|
|
|
* @param array $lines |
|
179
|
|
|
* @return array |
|
180
|
|
|
*/ |
|
181
|
|
|
private function concatenateStatements(array $lines): array |
|
182
|
|
|
{ |
|
183
|
|
|
$filteredResult = []; |
|
184
|
|
|
$previousLine = key($lines); |
|
185
|
|
|
|
|
186
|
|
|
foreach ($lines as $lineNumber => $currentLine) { |
|
187
|
|
|
if ($this->startsWith(self::CONCATENATE_PREFIX, $currentLine)) { |
|
188
|
|
|
$filteredResult[$previousLine] .= ' ' . trim($currentLine); |
|
189
|
|
|
continue; |
|
190
|
|
|
} |
|
191
|
|
|
|
|
192
|
|
|
$filteredResult[$lineNumber] = $currentLine; |
|
193
|
|
|
$previousLine = $lineNumber; |
|
194
|
|
|
} |
|
195
|
|
|
|
|
196
|
|
|
return $filteredResult; |
|
197
|
|
|
} |
|
198
|
|
|
} |
|
199
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.