Passed
Push — master ( 5545f1...83deac )
by Kirill
03:22
created

Source::normalizeIndents()   B

Complexity

Conditions 8
Paths 17

Size

Total Lines 42
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 26
c 1
b 0
f 0
nc 17
nop 2
dl 0
loc 42
rs 8.4444
1
<?php
2
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Reactor\Partial;
13
14
use Spiral\Reactor\AbstractDeclaration;
15
use Spiral\Reactor\Exception\MultilineException;
16
17
/**
18
 * Represents set of lines (function source, docComment).
19
 */
20
class Source extends AbstractDeclaration
21
{
22
    /**
23
     * @var array
24
     */
25
    private $lines;
26
27
    /**
28
     * @param array $lines
29
     */
30
    public function __construct(array $lines = [])
31
    {
32
        $this->lines = $lines;
33
    }
34
35
    /**
36
     * @return string
37
     */
38
    public function __toString(): string
39
    {
40
        return $this->render(0);
41
    }
42
43
    /**
44
     * @return bool
45
     */
46
    public function isEmpty(): bool
47
    {
48
        return empty($this->lines);
49
    }
50
51
    /**
52
     * @param array $lines
53
     * @return self|$this
54
     */
55
    public function setLines(array $lines): Source
56
    {
57
        $this->lines = $lines;
58
59
        return $this;
60
    }
61
62
    /**
63
     * @param string $line
64
     * @return self
65
     * @throws MultilineException
66
     */
67
    public function addLine(string $line): Source
68
    {
69
        if (strpos($line, "\n") !== false) {
70
            throw new MultilineException(
71
                'New line character is forbidden in addLine method argument'
72
            );
73
        }
74
75
        $this->lines[] = $line;
76
77
        return $this;
78
    }
79
80
    /**
81
     * @param string $string
82
     * @param bool   $cutIndents Function Strings::normalizeIndents will be applied.
83
     * @return self
84
     */
85
    public function setString(string $string, bool $cutIndents = false): Source
86
    {
87
        return $this->setLines($this->fetchLines($string, $cutIndents));
88
    }
89
90
    /**
91
     * @return array
92
     */
93
    public function getLines(): array
94
    {
95
        return $this->lines;
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function render(int $indentLevel = 0): string
102
    {
103
        $lines = $this->lines;
104
        array_walk($lines, function (&$line) use ($indentLevel): void {
105
            $line = $this->addIndent($line, $indentLevel);
106
        });
107
108
        return implode("\n", $lines);
109
    }
110
111
    /**
112
     * Create version of source cut from specific string location.
113
     *
114
     * @param string $string
115
     * @param bool   $cutIndents Function Strings::normalizeIndents will be applied.
116
     * @return Source
117
     */
118
    public static function fromString(string $string, bool $cutIndents = false): Source
119
    {
120
        $source = new self();
121
122
        return $source->setString($string, $cutIndents);
123
    }
124
125
    /**
126
     * Normalize string endings to avoid EOL problem. Replace \n\r and multiply new lines with
127
     * single \n.
128
     *
129
     * @param string $string String to be normalized.
130
     * @param bool   $joinMultiple Join multiple new lines into one.
131
     * @return string
132
     */
133
    public static function normalizeEndings(string $string, bool $joinMultiple = true): string
134
    {
135
        if (!$joinMultiple) {
136
            return str_replace("\r\n", "\n", $string);
137
        }
138
139
        return preg_replace('/[\n\r]+/', "\n", $string);
140
    }
141
142
    /**
143
     * Shift all string lines to have minimum indent size set to 0.
144
     *
145
     * Example:
146
     * |-a
147
     * |--b
148
     * |--c
149
     * |---d
150
     *
151
     * Output:
152
     * |a
153
     * |-b
154
     * |-c
155
     * |--d
156
     *
157
     * @param string $string Input string with multiple lines.
158
     * @param string $tabulationCost How to treat \t symbols relatively to spaces. By default, this
159
     *                               is set to 4 spaces.
160
     * @return string
161
     */
162
    public static function normalizeIndents(string $string, string $tabulationCost = '   '): string
163
    {
164
        $string = self::normalizeEndings($string, false);
165
        $lines = explode("\n", $string);
166
        $minIndent = null;
167
        foreach ($lines as $line) {
168
            if (!trim($line)) {
169
                continue;
170
            }
171
            $line = str_replace("\t", $tabulationCost, $line);
172
            //Getting indent size
173
            if (!preg_match('/^( +)/', $line, $matches)) {
174
                //Some line has no indent
175
                return $string;
176
            }
177
            if ($minIndent === null) {
178
                $minIndent = strlen($matches[1]);
179
            }
180
            $minIndent = min($minIndent, strlen($matches[1]));
181
        }
182
        //Fixing indent
183
        foreach ($lines as &$line) {
184
            if (empty($line)) {
185
                continue;
186
            }
187
            //Getting line indent
188
            preg_match("/^([ \t]+)/", $line, $matches);
189
            $indent = $matches[1];
190
            if (!trim($line)) {
191
                $line = '';
192
                continue;
193
            }
194
            //Getting new indent
195
            $useIndent = str_repeat(
196
                ' ',
197
                strlen(str_replace("\t", $tabulationCost, $indent)) - $minIndent
198
            );
199
            $line = $useIndent . substr($line, strlen($indent));
200
            unset($line);
201
        }
202
203
        return implode("\n", $lines);
204
    }
205
206
    /**
207
     * Converts input string into set of lines.
208
     *
209
     * @param string $string
210
     * @param bool   $cutIndents
211
     * @return array
212
     */
213
    protected function fetchLines(string $string, bool $cutIndents): array
214
    {
215
        if ($cutIndents) {
216
            $string = self::normalizeIndents($string, '');
217
        }
218
219
        $lines = explode("\n", self::normalizeEndings($string, false));
220
221
        //Pre-processing
222
        return array_filter(array_map([$this, 'prepareLine'], $lines), static function ($line): bool {
223
            return $line !== null;
224
        });
225
    }
226
227
    /**
228
     * Applied to every string before adding it to lines.
229
     *
230
     * @param string $line
231
     * @return string
232
     */
233
    protected function prepareLine(string $line): ?string
234
    {
235
        return $line;
236
    }
237
}
238