Completed
Push — master ( 1ece6c...656998 )
by Jan Philipp
13s queued 11s
created

PshScriptParser::parseContent()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.3222
c 0
b 0
f 0
cc 5
nc 6
nop 3
1
<?php declare(strict_types=1);
2
3
4
namespace Shopware\Psh\ScriptRuntime\ScriptLoader;
5
6
use function pathinfo;
7
use const PATHINFO_BASENAME;
8
use const PATHINFO_DIRNAME;
9
use Shopware\Psh\Listing\Script;
10
use Shopware\Psh\Listing\ScriptFinder;
11
12
/**
13
 * Load scripts and parse it into commands
14
 */
15
class PshScriptParser implements ScriptParser
16
{
17
    const TOKEN_MODIFIER_TTY = 'TTY: ';
18
19
    const TOKEN_MODIFIER_IGNORE_ERROR = 'I: ';
20
21
    const TOKEN_MODIFIER_DEFERRED = 'D: ';
22
23
    const TOKEN_INCLUDE = 'INCLUDE: ';
24
25
    const TOKEN_ACTION = 'ACTION: ';
26
27
    const TOKEN_WAIT = 'WAIT:';
28
29
    const TOKEN_TEMPLATE = 'TEMPLATE: ';
30
31
    const CONCATENATE_PREFIX = '   ';
32
33
    const TOKEN_WILDCARD = '*';
34
35
    /**
36
     * @var CommandBuilder
37
     */
38
    private $commandBuilder;
39
40
    /**
41
     * @var ScriptFinder
42
     */
43
    private $scriptFinder;
44
45
    /**
46
     * @param CommandBuilder $commandBuilder
47
     * @param ScriptFinder $scriptFinder
48
     */
49
    public function __construct(CommandBuilder $commandBuilder, ScriptFinder $scriptFinder)
50
    {
51
        $this->commandBuilder = $commandBuilder;
52
        $this->scriptFinder = $scriptFinder;
53
    }
54
55
    /**
56
     * {@inheritdoc}
57
     */
58
    public function parseContent(string $content, Script $script, ScriptLoader $loader): array
59
    {
60
        $lines = $this->splitIntoLines($content);
61
        $tokenHandler = $this->createTokenHandler($loader);
62
63
        foreach ($lines as $lineNumber => $currentLine) {
64
            foreach ($tokenHandler as $token => $handler) {
65
                if ($this->startsWith($token, $currentLine)) {
66
                    $currentLine = $handler($currentLine, $lineNumber, $script);
67
                }
68
69
                if ($currentLine === '') {
70
                    break;
71
                }
72
            }
73
        }
74
75
        return $this->commandBuilder->getAll();
76
    }
77
78
    private function createTokenHandler(ScriptLoader $loader): array
79
    {
80
        return [
81
            self::TOKEN_ACTION => function (string $currentLine) use ($loader): string {
82
                $scriptName = $this->removeFromStart(self::TOKEN_ACTION, $currentLine);
83
                $actionScript = $this->scriptFinder->findScriptByName($scriptName);
84
85
                $commands = $loader->loadScript($actionScript);
86
                $this->commandBuilder->replaceCommands($commands);
87
88
                return '';
89
            },
90
91
            self::TOKEN_INCLUDE => function (string $currentLine, int $lineNumber, Script $script) use ($loader): string {
92
                $path = $this->findInclude($script, $this->removeFromStart(self::TOKEN_INCLUDE, $currentLine));
93
                $includeScript = new Script(pathinfo($path, PATHINFO_DIRNAME), pathinfo($path, PATHINFO_BASENAME));
94
95
                $commands = $loader->loadScript($includeScript);
96
                $this->commandBuilder->replaceCommands($commands);
97
98
                return '';
99
            },
100
101
            self::TOKEN_TEMPLATE => function (string $currentLine, int $lineNumber, Script $script): string {
102
                $definition = $this->removeFromStart(self::TOKEN_TEMPLATE, $currentLine);
103
                list($rawSource, $rawDestination) = explode(':', $definition);
104
105
                $source = $script->getDirectory() . '/' . $rawSource;
106
                $destination = $script->getDirectory() . '/' . $rawDestination;
107
108
                $this->commandBuilder
109
                    ->addTemplateCommand($source, $destination, $lineNumber);
110
111
                return '';
112
            },
113
114
            self::TOKEN_WAIT => function (string $currentLine, int $lineNumber): string {
115
                $this->commandBuilder
116
                    ->addWaitCommand($lineNumber);
117
118
119
                return '';
120
            },
121
122
            self::TOKEN_MODIFIER_IGNORE_ERROR => function (string $currentLine): string {
123
                $this->commandBuilder->setIgnoreError();
124
125
                return $this->removeFromStart(self::TOKEN_MODIFIER_IGNORE_ERROR, $currentLine);
126
            },
127
128
            self::TOKEN_MODIFIER_TTY => function (string $currentLine): string {
129
                $this->commandBuilder->setTty();
130
131
                return  $this->removeFromStart(self::TOKEN_MODIFIER_TTY, $currentLine);
132
            },
133
134
            self::TOKEN_MODIFIER_DEFERRED => function (string $currentLine): string {
135
                $this->commandBuilder->setDeferredExecution();
136
137
                return $this->removeFromStart(self::TOKEN_MODIFIER_DEFERRED, $currentLine);
138
            },
139
140
            self::TOKEN_WILDCARD => function (string $currentLine, int $lineNumber): string {
141
                $this->commandBuilder
142
                    ->addProcessCommand($currentLine, $lineNumber);
143
144
                return '';
145
            },
146
        ];
147
    }
148
149
    /**
150
     * @param Script $fromScript
151
     * @param string $includeStatement
152
     * @return string
153
     */
154
    private function findInclude(Script $fromScript, string $includeStatement): string
155
    {
156
        if (file_exists($includeStatement)) {
157
            return $includeStatement;
158
        }
159
160
        if (file_exists($fromScript->getDirectory() . '/' . $includeStatement)) {
161
            return $fromScript->getDirectory() . '/' . $includeStatement;
162
        }
163
164
        throw new \RuntimeException('Unable to parse include statement "' . $includeStatement . '" in "' . $fromScript->getPath() . '"');
165
    }
166
167
    /**
168
     * @param string $command
169
     * @return bool
170
     */
171
    private function isExecutableLine(string $command): bool
172
    {
173
        $command = trim($command);
174
175
        if (!$command) {
176
            return false;
177
        }
178
179
        if ($this->startsWith('#', $command)) {
180
            return false;
181
        }
182
183
        return true;
184
    }
185
186
    /**
187
     * @param string $needle
188
     * @param string $haystack
189
     * @return string
190
     */
191
    private function removeFromStart(string $needle, string $haystack): string
192
    {
193
        return substr($haystack, strlen($needle));
194
    }
195
196
    /**
197
     * @param string $needle
198
     * @param string $haystack
199
     * @return bool
200
     */
201
    private function startsWith(string $needle, string $haystack): bool
202
    {
203
        return (self::TOKEN_WILDCARD === $needle && $haystack !== '') || strpos($haystack, $needle) === 0;
204
    }
205
206
    /**
207
     * @param string $contents
208
     * @return string[]
209
     */
210
    private function splitIntoLines(string $contents): array
211
    {
212
        $lines = [];
213
        $lineNumber = -1;
214
215
        foreach (explode("\n", $contents) as $line) {
216
            $lineNumber++;
217
218
            if (!$this->isExecutableLine($line)) {
219
                continue;
220
            }
221
222
            if ($this->startsWith(self::CONCATENATE_PREFIX, $line)) {
223
                $lastValue = array_pop($lines);
224
                $lines[] = $lastValue  . ' ' . trim($line);
225
226
                continue;
227
            }
228
229
            $lines[$lineNumber] = $line;
230
        }
231
232
        return $lines;
233
    }
234
}
235