|
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 ScriptLoader |
|
12
|
|
|
{ |
|
13
|
|
|
const MODIFIER_IS_TTY = 'TTY: '; |
|
14
|
|
|
|
|
15
|
|
|
const MODIFIER_IGNORE_ERROR_PREFIX = 'I: '; |
|
16
|
|
|
|
|
17
|
|
|
const MODIFIER_DEFERRED_EXECUTION_PREFIX = 'D: '; |
|
18
|
|
|
|
|
19
|
|
|
const INCLUDE_STATEMENT_PREFIX = 'INCLUDE: '; |
|
20
|
|
|
|
|
21
|
|
|
const WAIT_STATEMENT = 'WAIT:'; |
|
22
|
|
|
|
|
23
|
|
|
const TEMPLATE_STATEMENT_PREFIX = 'TEMPLATE: '; |
|
24
|
|
|
|
|
25
|
|
|
const CONCATENATE_PREFIX = ' '; |
|
26
|
|
|
|
|
27
|
|
|
/** |
|
28
|
|
|
* @var CommandBuilder |
|
29
|
|
|
*/ |
|
30
|
|
|
private $commandBuilder; |
|
31
|
|
|
|
|
32
|
|
|
/** |
|
33
|
|
|
* @param CommandBuilder $commandBuilder |
|
34
|
|
|
*/ |
|
35
|
|
|
public function __construct(CommandBuilder $commandBuilder) |
|
36
|
|
|
{ |
|
37
|
|
|
$this->commandBuilder = $commandBuilder; |
|
38
|
|
|
} |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* @param Script $script |
|
42
|
|
|
* @return Command[] |
|
43
|
|
|
*/ |
|
44
|
|
|
public function loadScript(Script $script): array |
|
45
|
|
|
{ |
|
46
|
|
|
$content = $this->loadFileContents($script->getPath()); |
|
47
|
|
|
$lines = explode("\n", $content); |
|
48
|
|
|
|
|
49
|
|
|
foreach ($lines as $lineNumber => $currentLine) { |
|
50
|
|
|
$ignoreError = false; |
|
51
|
|
|
$tty = false; |
|
52
|
|
|
$deferred = false; |
|
53
|
|
|
|
|
54
|
|
|
if (!$this->isExecutableLine($currentLine)) { |
|
55
|
|
|
continue; |
|
56
|
|
|
} |
|
57
|
|
|
|
|
58
|
|
|
if ($this->startsWith(self::CONCATENATE_PREFIX, $currentLine)) { |
|
59
|
|
|
$this->commandBuilder->add($currentLine); |
|
60
|
|
|
continue; |
|
61
|
|
|
} |
|
62
|
|
|
|
|
63
|
|
|
if ($this->startsWith(self::INCLUDE_STATEMENT_PREFIX, $currentLine)) { |
|
64
|
|
|
$path = $this->findInclude($script, $this->removeFromStart(self::INCLUDE_STATEMENT_PREFIX, $currentLine)); |
|
65
|
|
|
$includeScript = new Script(pathinfo($path, PATHINFO_DIRNAME), pathinfo($path, PATHINFO_BASENAME)); |
|
66
|
|
|
|
|
67
|
|
|
$commands = $this->loadScript($includeScript); |
|
68
|
|
|
$this->commandBuilder->setCommands($commands); |
|
69
|
|
|
|
|
70
|
|
|
continue; |
|
71
|
|
|
} |
|
72
|
|
|
|
|
73
|
|
|
if ($this->startsWith(self::TEMPLATE_STATEMENT_PREFIX, $currentLine)) { |
|
74
|
|
|
$definition = $this->removeFromStart(self::TEMPLATE_STATEMENT_PREFIX, $currentLine); |
|
75
|
|
|
list($rawSource, $rawDestination) = explode(':', $definition); |
|
76
|
|
|
|
|
77
|
|
|
$source = $script->getDirectory() . '/' . $rawSource; |
|
78
|
|
|
$destination = $script->getDirectory() . '/' . $rawDestination; |
|
79
|
|
|
|
|
80
|
|
|
$this->commandBuilder |
|
81
|
|
|
->addTemplateCommand($source, $destination, $lineNumber); |
|
82
|
|
|
|
|
83
|
|
|
continue; |
|
84
|
|
|
} |
|
85
|
|
|
|
|
86
|
|
|
if ($this->startsWith(self::WAIT_STATEMENT, $currentLine)) { |
|
87
|
|
|
$this->commandBuilder |
|
88
|
|
|
->addWaitCommand($lineNumber); |
|
89
|
|
|
continue; |
|
90
|
|
|
} |
|
91
|
|
|
|
|
92
|
|
|
if ($this->startsWith(self::MODIFIER_IGNORE_ERROR_PREFIX, $currentLine)) { |
|
93
|
|
|
$currentLine = $this->removeFromStart(self::MODIFIER_IGNORE_ERROR_PREFIX, $currentLine); |
|
94
|
|
|
$ignoreError = true; |
|
95
|
|
|
} |
|
96
|
|
|
|
|
97
|
|
View Code Duplication |
if ($this->startsWith(self::MODIFIER_IS_TTY, $currentLine)) { |
|
|
|
|
|
|
98
|
|
|
$currentLine = $this->removeFromStart(self::MODIFIER_IS_TTY, $currentLine); |
|
99
|
|
|
$tty = true; |
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
View Code Duplication |
if ($this->startsWith(self::MODIFIER_DEFERRED_EXECUTION_PREFIX, $currentLine)) { |
|
|
|
|
|
|
103
|
|
|
$currentLine = $this->removeFromStart(self::MODIFIER_DEFERRED_EXECUTION_PREFIX, $currentLine); |
|
104
|
|
|
$deferred = true; |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
|
|
$this->commandBuilder |
|
108
|
|
|
->next($currentLine, $lineNumber, $ignoreError, $tty, $deferred); |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
return $this->commandBuilder->getAll(); |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
|
|
/** |
|
115
|
|
|
* @param Script $fromScript |
|
116
|
|
|
* @param string $includeStatement |
|
117
|
|
|
* @return string |
|
118
|
|
|
*/ |
|
119
|
|
|
private function findInclude(Script $fromScript, string $includeStatement): string |
|
120
|
|
|
{ |
|
121
|
|
|
if (file_exists($includeStatement)) { |
|
122
|
|
|
return $includeStatement; |
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
|
|
if (file_exists($fromScript->getDirectory() . '/' . $includeStatement)) { |
|
126
|
|
|
return $fromScript->getDirectory() . '/' . $includeStatement; |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
throw new \RuntimeException('Unable to parse include statement "' . $includeStatement . '" in "' . $fromScript->getPath() . '"'); |
|
130
|
|
|
} |
|
131
|
|
|
|
|
132
|
|
|
/** |
|
133
|
|
|
* @param string $command |
|
134
|
|
|
* @return bool |
|
135
|
|
|
*/ |
|
136
|
|
|
private function isExecutableLine(string $command): bool |
|
137
|
|
|
{ |
|
138
|
|
|
$command = trim($command); |
|
139
|
|
|
|
|
140
|
|
|
if (!$command) { |
|
141
|
|
|
return false; |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
if ($this->startsWith('#', $command)) { |
|
145
|
|
|
return false; |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
return true; |
|
149
|
|
|
} |
|
150
|
|
|
|
|
151
|
|
|
/** |
|
152
|
|
|
* @param string $needle |
|
153
|
|
|
* @param string $haystack |
|
154
|
|
|
* @return string |
|
155
|
|
|
*/ |
|
156
|
|
|
private function removeFromStart(string $needle, string $haystack): string |
|
157
|
|
|
{ |
|
158
|
|
|
return substr($haystack, strlen($needle)); |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
/** |
|
162
|
|
|
* @param string $needle |
|
163
|
|
|
* @param string $haystack |
|
164
|
|
|
* @return bool |
|
165
|
|
|
*/ |
|
166
|
|
|
private function startsWith(string $needle, string $haystack): bool |
|
167
|
|
|
{ |
|
168
|
|
|
return strpos($haystack, $needle) === 0; |
|
169
|
|
|
} |
|
170
|
|
|
|
|
171
|
|
|
/** |
|
172
|
|
|
* @param string $file |
|
173
|
|
|
* @return string |
|
174
|
|
|
*/ |
|
175
|
|
|
protected function loadFileContents(string $file): string |
|
176
|
|
|
{ |
|
177
|
|
|
return file_get_contents($file); |
|
178
|
|
|
} |
|
179
|
|
|
} |
|
180
|
|
|
|
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.