Completed
Push — master ( 640397...4ec2f8 )
by Mikael
02:52
created

Frontmatter::blockYamlFrontmatter()   C

Complexity

Conditions 14
Paths 6

Size

Total Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 14

Importance

Changes 0
Metric Value
dl 0
loc 42
ccs 25
cts 25
cp 1
rs 6.2666
c 0
b 0
f 0
cc 14
nc 6
nop 1
crap 14

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Anax\TextFilter\Filter;
4
5
use \Symfony\Component\Yaml\Yaml;
6
7
/**
8
 * Filter and format content.
9
 */
10
class Frontmatter implements FilterInterface
11
{
12
    /**
13
     * @var array $config with options on how to parse text.
14
     */
15
    protected $config;
16
17
18
19
    /**
20
     * @var array $lines text split into lines.
21
     */
22
    protected $lines;
23
24
25
26
    /**
27
     * @var array $lineNumber current line number being parsed.
28
     */
29
    protected $lineNumber;
30
31
32
33
    /**
34
     * @var integer $linesRemoved keep track on how many lines is removed.
35
     */
36
    protected $linesRemoved;
37
38
39
40
    /**
41
     * @var array $frontmatter parsed as frontmatter, accumulated when parsing
42
     *                         the text.
43
     */
44
    protected $frontmatter;
45
46
47
48
    /**
49
     * @var array $blockTypes with details on block to detect.
50
     */
51
    protected $blockTypes = [
52
        "#" => ["Include"],
53
        "-" => ["YamlFrontmatter"],
54
        "{" => ["JsonFrontmatter"],
55
    ];
56
57
58
59
    /**
60
     * Parse the text through the filter and do what the filter does,
61
     * return the resulting text and with some optional additional details,
62
     * all wrapped in an key-value array.
63
     *
64
     * @param string $text        to parse.
65
     * @param array  $frontmatter optional to use while parsing.
66
     * @param array  $options     custom options to use while parsing.
67
     *
68
     * @return array with the resulting text and frontmatter.
69
     */
70 36
    public function parse($text, array $frontmatter, array $options = [])
71
    {
72
        $config = [
73 36
            "include"               => true,
74
            "include_base"          => null,
75
            "frontmatter_json"      => true,
76
            "frontmatter_yaml"      => true,
77
            "yaml_parser_pecl"      => true,
78
            "yaml_parser_symfony"   => true,
79
            "yaml_parser_spyc"      => true,
80
        ];
81 36
        $this->config = array_merge($config, $options);
82
83 36
        if ($this->config["include"]
84 36
            && !is_dir($this->config["include_base"])) {
85 2
            throw new Exception("Include base is not a readable directory.");
86
        }
87
88
        // Unify lineendings
89 34
        $text = str_replace(array("\r\n", "\r"), "\n", $text);
90 34
        $this->lines = explode("\n", $text);
91 34
        $this->lineNumber = 0;
0 ignored issues
show
Documentation Bug introduced by
It seems like 0 of type integer is incompatible with the declared type array of property $lineNumber.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
92 34
        $this->linesRemoved = 0;     // Only needed for log?
93 34
        $this->frontmatter = $frontmatter;
94
95 34
        return $this->parseLines();
96
    }
97
98
99
100
//     /**
101
//      * Debugging and visualising parsing by printing information on current row.
102
//      *
103
//      * @param string $msg  additional message to print.
104
//      */
105
//     private function log($msg)
106
//     {
107
//         $lineNumber = $this->lineNumber + $this->linesRemoved;
108
//         $line = $this->currentLine();
109
//
110
//         echo <<<EOD
111
//
112
// ***{$lineNumber}: $msg
113
// {$line}
114
// ***
115
//
116
// EOD;
117
//     }
118
119
120
121
    /**
122
     * Parse each line and look into it to see whats need to be done.
123
     *
124
     * @return array with the resulting text and optional additional items.
125
     */
126 34
    public function parseLines()
127
    {
128 34
        $text = [];
129
130 34
        while (($line = $this->nextLine()) !== false) {
131 34
            $line = rtrim($line);
132
133
            // Skip empty lines
134 34
            if ($line == "") {
135 10
                $text[] = null;
136 10
                continue;
137
            }
138
139
            // Look at start of line to detect valid blocktypes
140 34
            $blockTypes = [];
141 34
            $marker = $line[0];
142 34
            if (isset($this->blockTypes[$marker])) {
143 34
                foreach ($this->blockTypes[$marker] as $blockType) {
144 34
                    $blockTypes[] = $blockType;
145
                }
146
            }
147
148
            // Check if line is matching a detected blocktype
149 34
            foreach ($blockTypes as $blockType) {
150 34
                if ($this->{"block".$blockType}($line)) {
151 28
                    continue;
152
                }
153
            }
154
        }
155
156 28
        $text = implode("\n", $this->lines);
157
158
        return [
159 28
            "text" => $text,
160 28
            "frontmatter" => $this->frontmatter,
161
        ];
162
    }
163
164
165
166
    /**
167
     * Get current line to parse.
168
     *
169
     * @return string|boolean containing text for current row or false when
170
     *                        reached EOF.
171
     */
172 34
    public function currentLine()
173
    {
174 34
        return isset($this->lines[$this->lineNumber - 1])
175 34
            ? $this->lines[$this->lineNumber - 1]
176 34
            : false;
177
    }
178
179
180
181
    /**
182
     * Get next line to parse and keep track on current line being parsed.
183
     *
184
     * @return string|boolean containing text for next row or false when
185
     *                        reached EOF.
186
     */
187 34
    public function nextLine()
188
    {
189 34
        $this->lineNumber++;
190 34
        return $this->currentLine();
191
    }
192
193
194
195
    /**
196
     * Detect and include external file, add it to lines array and parse it.
197
     *
198
     * @param string $line to begin parsing.
199
     *
200
     * @return boolean|void true when block is found and parsed, else void.
201
     */
202 7
    protected function blockInclude($line)
203
    {
204 7
        if ($this->config["include"]
205 7
            && preg_match("/^#include[ \t]([\w.]+)$/", $line, $matches)
206
        ) {
207 6
            $file = $this->config["include_base"]."/".$matches[1];
208
209 6
            if (!is_readable($file)) {
210 1
                throw new Exception("Could not find include file: '$file'");
211
            }
212
213 5
            $include = file_get_contents($file);
214 5
            $include = str_replace(array("\r\n", "\r"), "\n", $include);
215 5
            $include = explode("\n", $include);
216 5
            array_splice(
217 5
                $this->lines,
218 5
                $this->lineNumber - 1,
219 5
                1,
220 5
                $include
221
            );
222
223 5
            $this->lineNumber--;
224 5
            return true;
225
        }
226 1
    }
227
228
229
230
    /**
231
     * Detect and extract block with YAML frontmatter from the lines array.
232
     *
233
     * @param string $line to begin parsing.
234
     *
235
     * @return boolean|void true when block is found and parsed, else void.
236
     *
237
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
238
     */
239 20
    protected function blockYamlFrontmatter($line)
240
    {
241 20
        if ($this->config["frontmatter_yaml"]
242 20
            && strlen($line) === 3
243 20
            && $line[2] === "-"
244 20
            && $line[1] === "-"
245
        ) {
246 19
            $startLineNumber = $this->lineNumber;
247
248
            // Detect end of block and move it to frontmatter
249 19
            while (($line = $this->nextLine()) !== false) {
250 19
                $line = rtrim($line);
251
252
                // Block ends with --- or ...
253 19
                if (strlen($line) === 3
254
                    && (
255 18
                    ($line[2] === "-" && $line[1] === "-" && $line[0] === "-") ||
256 19
                    ($line[2] === "." && $line[1] === "." && $line[0] === ".")
257
                    )
258
                ) {
259 18
                    $linesRemoved = $this->lineNumber + 1 - $startLineNumber;
260 18
                    $this->linesRemoved += $linesRemoved;
261 18
                    $frontmatter = array_splice(
262 18
                        $this->lines,
263 18
                        $startLineNumber - 1,
264 18
                        $linesRemoved
265
                    );
266
267 18
                    unset($frontmatter[$linesRemoved - 1]);
268 18
                    unset($frontmatter[0]);
269 18
                    $this->addYamlFrontmatter($frontmatter);
270 16
                    $this->lineNumber = $startLineNumber - 1;
0 ignored issues
show
Documentation Bug introduced by
It seems like $startLineNumber - 1 of type integer or double is incompatible with the declared type array of property $lineNumber.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
271
272 16
                    return true;
273
                }
274
            }
275
276 1
            if ($this->currentLine() === false) {
277 1
                throw new Exception("Start of YAML detected at line: $startLineNumber but no end of block detected.");
278
            }
279
        }
280 1
    }
281
282
283
284
    /**
285
     * Extract YAML frontmatter from text and merge into existing frontmatter.
286
     *
287
     * @param array $lines the YAML to parsed.
288
     *
289
     * @return void.
0 ignored issues
show
Documentation introduced by
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
290
     */
291 18
    protected function addYamlFrontmatter($lines)
292
    {
293 18
        $text = implode("\n", $lines);
294 18
        $parsed = $this->parseYaml($text);
295
296 16
        if (!is_array($parsed)) {
297 1
            $parsed = [$parsed];
298
        }
299
300 16
        $this->frontmatter = array_merge($this->frontmatter, $parsed);
301 16
    }
302
303
304
305
    /**
306
     * Parse YAML front matter from text, use one of several available
307
     * implementations of a YAML parser.
308
     *
309
     * @param string $text the YAML to parsed.
310
     *
311
     * @return void.
0 ignored issues
show
Documentation introduced by
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
312
     *
313
     * @throws Exception when parsing frontmatter fails of is not installed.
314
     */
315 18
    protected function parseYaml($text)
316
    {
317 18
        if ($this->config["yaml_parser_pecl"]
318 18
            && function_exists("yaml_parse")
319
        ) {
320
            // PECL php5-yaml extension
321
            $parsed = yaml_parse($text);
322
323
            if ($parsed === false) {
324
                throw new Exception("Failed parsing YAML frontmatter using PECL.");
325
            }
326
            return $parsed;
327
        }
328
329 18
        if ($this->config["yaml_parser_symfony"]
330 18
            && method_exists("Symfony\Component\Yaml\Yaml", "parse")
331
        ) {
332
            // symfony/yaml
333 16
            $parsed = Yaml::parse($text);
334 15
            return $parsed;
335
        }
336
337 2
        if ($this->config["yaml_parser_spyc"]
338 2
            && function_exists("spyc_load")
339
        ) {
340
            // mustangostang/spyc
341 1
            $parsed = spyc_load($text);
342 1
            return $parsed;
343
        }
344
345 1
        throw new Exception("Could not find support for YAML.");
346
    }
347
348
349
350
    /**
351
     * Detect and extract block with JSON frontmatter from the lines array.
352
     *
353
     * @param string $line to begin parsing.
354
     *
355
     * @return boolean|void true when block is found and parsed, else void.
356
     *
357
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
358
     */
359 13
    protected function blockJsonFrontmatter($line)
360
    {
361 13
        if ($this->config["frontmatter_json"]
362 13
            && strlen($line) === 3
363 13
            && $line[2] === "{"
364 13
            && $line[1] === "{"
365
        ) {
366 12
            $startLineNumber = $this->lineNumber;
367
368
            // Detect end of block and move it to frontmatter
369 12
            while (($line = $this->nextLine()) !== false) {
370 12
                $line = rtrim($line);
371
372
                // Block ends with }}}
373 12
                if (strlen($line) === 3
374 12
                    && $line[2] === "}"
375 12
                    && $line[1] === "}"
376 12
                    && $line[0] === "}"
377
                ) {
378 11
                    $linesRemoved = $this->lineNumber + 1 - $startLineNumber;
379 11
                    $this->linesRemoved += $linesRemoved;
380 11
                    $frontmatter = array_splice(
381 11
                        $this->lines,
382 11
                        $startLineNumber - 1,
383 11
                        $linesRemoved
384
                    );
385
386 11
                    unset($frontmatter[$linesRemoved - 1]);
387 11
                    unset($frontmatter[0]);
388 11
                    $this->addJsonFrontmatter($frontmatter);
389 10
                    $this->lineNumber = $startLineNumber - 1;
0 ignored issues
show
Documentation Bug introduced by
It seems like $startLineNumber - 1 of type integer or double is incompatible with the declared type array of property $lineNumber.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
390 10
                    return true;
391
                }
392
            }
393
394 1
            if ($this->currentLine() === false) {
395 1
                throw new Exception("Start of JSON detected at line: $startLineNumber but no end of block detected.");
396
            }
397
        }
398 1
    }
399
400
401
402
    /**
403
     * Extract JSON frontmatter from text and merge into existing frontmatter.
404
     *
405
     * @param array $lines the JSON to parsed.
406
     *
407
     * @return void.
0 ignored issues
show
Documentation introduced by
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
408
     */
409 11
    protected function addJsonFrontmatter($lines)
410
    {
411 11
        if (!function_exists("json_decode")) {
412
            throw new Exception("Missing JSON support, perhaps install JSON module with PHP.");
413
        }
414
415 11
        $text = implode("\n", $lines);
416 11
        $parsed = json_decode($text."\n", true);
417
418 11
        if (is_null($parsed)) {
419 1
            throw new Exception("Failed parsing JSON frontmatter.");
420
        }
421
422 10
        if (!is_array($parsed)) {
423 1
            $parsed = [$parsed];
424
        }
425
426 10
        $this->frontmatter = array_merge($this->frontmatter, $parsed);
427 10
    }
428
}
429