Completed
Push — master ( be53ba...d8ac27 )
by Jan Philipp
11s
created

ScriptLoader::isExecutableLine()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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