Completed
Pull Request — master (#76)
by Jan Philipp
01:42
created

ScriptParser   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 188
Duplicated Lines 4.26 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 2
dl 8
loc 188
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A loadScript() 8 31 5
A isNotHandledAsCommand() 0 32 4
A findInclude() 0 12 3
A isExecutableLine() 0 14 3
A removeFromStart() 0 4 1
A startsWith() 0 4 1
A loadFileContents() 0 4 1
A concatenateStatements() 0 17 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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