Passed
Push — master ( 6b8e06...02b784 )
by stéphane
04:35
created

Loader::parse()   F

Complexity

Conditions 27
Paths 705

Size

Total Lines 68
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 27
eloc 51
nc 705
nop 1
dl 0
loc 68
rs 0.4097
c 0
b 0
f 0

How to fix   Long Method    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
declare(strict_types=1);
0 ignored issues
show
Coding Style introduced by
Missing file doc comment
Loading history...
3
4
namespace Dallgoot\Yaml;
5
6
use Dallgoot\Yaml\Yaml as Y;
7
8
/**
9
 * TODO
10
 *
11
 * @author  Stéphane Rebai <[email protected]>
12
 * @license Apache 2.0
13
 * @link    TODO : url to specific online doc
14
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
15
final class Loader
16
{
17
    //public
18
19
    /* @var null|string */
20
    public static $error;
21
22
    public const EXCLUDE_DIRECTIVES = 1;//DONT include_directive
23
    public const IGNORE_COMMENTS    = 2;//DONT include_comments
24
    public const NO_PARSING_EXCEPTIONS = 4;//THROW Exception on parsing Errors
25
    public const NO_OBJECT_FOR_DATE = 8;//DONT import date strings as dateTime Object
26
    //privates
27
    /* @var null|false|array| */
28
    private $content;
0 ignored issues
show
Coding Style introduced by
Private member variable "content" must be prefixed with an underscore
Loading history...
29
    /* @var null|string */
30
    private $filePath;
0 ignored issues
show
Coding Style introduced by
Private member variable "filePath" must be prefixed with an underscore
Loading history...
31
    /* @var integer */
32
    private $debug = 0;///TODO: determine levels
0 ignored issues
show
Coding Style introduced by
Private member variable "debug" must be prefixed with an underscore
Loading history...
33
    /* @var integer */
34
    private $options = 0;
0 ignored issues
show
Coding Style introduced by
Private member variable "options" must be prefixed with an underscore
Loading history...
35
    //Exceptions messages
36
    private const INVALID_VALUE        = self::class.": at line %d";
37
    private const EXCEPTION_NO_FILE    = self::class.": file '%s' does not exists (or path is incorrect?)";
38
    private const EXCEPTION_READ_ERROR = self::class.": file '%s' failed to be loaded (permission denied ?)";
39
    private const EXCEPTION_LINE_SPLIT = self::class.": content is not a string(maybe a file error?)";
40
41
    public function __construct($absolutePath = null, $options = null, $debug = 0)
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
42
    {
43
        $this->debug   = is_int($debug) ? min($debug, 3) : 1;
44
        $this->options = is_int($options) ? $options : $this->options;
45
        if (is_string($absolutePath)) {
46
            $this->load($absolutePath);
47
        }
48
    }
49
50
    /**
51
     * Load a file and save its content as $content
52
     *
53
     * @param string $absolutePath The absolute path of a file
54
     *
55
     * @throws \Exception if file don't exist OR reading failed
56
     *
57
     * @return self  ( returns the same Loader  )
58
     */
59
    public function load(string $absolutePath):Loader
60
    {
61
        if (!file_exists($absolutePath)) {
62
            throw new \Exception(sprintf(self::EXCEPTION_NO_FILE, $absolutePath));
63
        }
64
        $this->filePath = $absolutePath;
65
        $adle = "auto_detect_line_endings";
66
        $prevADLE = ini_get($adle);
67
        !$prevADLE && ini_set($adle, "true");
68
        $content = file($absolutePath, FILE_IGNORE_NEW_LINES);
69
        !$prevADLE && ini_set($adle, "false");
70
        if (is_bool($content)) {
71
            throw new \Exception(sprintf(self::EXCEPTION_READ_ERROR, $absolutePath));
72
        }
73
        $this->content = $content;
74
        return $this;
75
    }
76
77
    /**
78
     * Parse Yaml lines into a hierarchy of Node
79
     *
80
     * @param string $strContent The Yaml string or null to parse loaded content
81
     *
82
     * @throws \Exception    if content is not available as $strContent or as $this->content (from file)
83
     * @throws \ParseError  if any error during parsing or building
84
     *
85
     * @return array|YamlObject|null      null on errors if NO_PARSING_EXCEPTIONS is set, otherwise an array of YamlObject or just YamlObject
86
     */
87
    public function parse($strContent = null)
88
    {
89
        $source = $this->content ?? preg_split("/\n/m", preg_replace('/(\r\n|\r)/', "\n", $strContent), 0, PREG_SPLIT_DELIM_CAPTURE);
90
        //TODO : be more permissive on $strContent values
91
        if (!is_array($source) || !count($source)) throw new \Exception(self::EXCEPTION_LINE_SPLIT);
92
        $previous = $root = new Node();
93
        $emptyLines = [];
94
        try { //var_dump($source);
95
            $gen = function () use($source) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after USE keyword; found 0
Loading history...
96
                foreach ($source as $key => $value) {
97
                    yield ++$key => $value;
98
                }
99
            };
100
            foreach ($gen() as $lineNb => $lineString) {
101
                $n = new Node($lineString, $lineNb);
102
                $deepest = $previous->getDeepestNode();
103
                if ($n->type & (Y::LITTERALS|Y::BLANK|Y::COMMENT|Y::TAG) || $deepest->type & Y::PARTIAL) {
104
                    if ($this->onSpecialType($n, $previous, $emptyLines, $lineString)) continue;
105
                }
106
                //Note: 6.6 comments: Note that outside scalar content, a line containing only white space characters is taken to be a comment line.
107
                    foreach ($emptyLines as $blankNode) {
108
                        if ($blankNode !== $previous) {
109
                            $blankNode->getParent()->add($blankNode);
110
                        }
111
                    }
112
                $emptyLines = [];
113
                switch ($n->indent <=> $previous->indent) {
114
                    case -1: $target = $previous->getParent($n->indent, $n->type & Y::ITEM ? Y::KEY : null);
115
                        break;
116
                    case 0:
117
                        if ($n->type & Y::KEY && $n->indent === 0) {
118
                            $target = $root;
119
                        } elseif($n->type & Y::ITEM && $deepest->type & Y::KEY && is_null($deepest->value)) {
0 ignored issues
show
Coding Style introduced by
Expected "} elseif (...) \n"; found " elseif(...) {\n"
Loading history...
120
                            $target = $deepest;
121
                        } else {
122
                            $target = $previous->getParent();
123
                        }
124
                        break;
125
                    default:
126
                        $target = $previous;
127
                        if ($previous->type & Y::ITEM) {
128
                            if ($deepest->type & (Y::KEY|Y::TAG) && is_null($deepest->value)) {
129
                                $target = $deepest;
130
                            }
131
                        }
132
                }
133
                if ($this->onContextType($n, $target, $lineString)) continue;
134
                $target->add($n);
135
                $previous = $n;
136
            }
137
            if ($this->debug === 2) {
138
                print_r((new \ReflectionClass(Y::class))->getConstants());
139
                echo "\033[33mParsed Structure\033[0m\n";
140
                print_r($root);
141
                exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
142
            }
143
            $out = Builder::buildContent($root, $this->debug);
144
            return $out;
145
        } catch (\Error|\Exception|\ParseError $e) {
146
            $file = $this->filePath ? basename($this->filePath) : '#YAML STRING#';
147
            $message = basename($e->getFile())."@".$e->getLine().": ".$e->getMessage()." for '$file' @".($lineNb)."\n";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $lineNb seems to be defined by a foreach iteration on line 100. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
148
            if ($this->options & self::NO_PARSING_EXCEPTIONS) {
149
                // trigger_error($message, E_USER_WARNING);
150
                self::$error = $message;
151
                return null;
152
            }
153
            $this->debug && print_r($root);
154
            throw new \Exception($message, 3);
155
        }
156
    }
157
158
    private function onSpecialType(Node &$n, Node &$previous, &$emptyLines, $lineString):bool
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
Coding Style introduced by
Private method name "Loader::onSpecialType" must be prefixed with an underscore
Loading history...
159
    {
160
        $deepest = $previous->getDeepestNode();
161
        if ($deepest->type & Y::PARTIAL) {
162
            $add = trim($lineString) === '' ? "\n" : trim($lineString);
163
            if ($add !== "\n" && $deepest->value[-1] !== "\n") {
164
                $add = ' '.$add;
165
            }
166
            $deepest->parse($deepest->value.$add);
167
            return true;
168
        }
169
        if ($n->type & Y::BLANK) {
170
            if ($previous->type & Y::SCALAR)   $emptyLines[] = $n->setParent($previous->getParent());
171
            if ($deepest->type & Y::LITTERALS) $emptyLines[] = $n->setParent($deepest);
172
            return true;
173
        } elseif ($n->type & Y::COMMENT
174
                  && !($previous->getParent()->value->type & Y::LITTERALS)
0 ignored issues
show
Coding Style introduced by
Multi-line IF statement not indented correctly; expected 12 spaces but found 18
Loading history...
175
                  && !($deepest->type & Y::LITTERALS)) {
0 ignored issues
show
Coding Style introduced by
Multi-line IF statement not indented correctly; expected 12 spaces but found 18
Loading history...
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
176
            $previous->getParent(0)->add($n);
177
            return true;
178
        } elseif ($n->type & Y::TAG && is_null($n->value) && $previous->type & (Y::ROOT|Y::DOC_START|Y::DOC_END)) {
179
            $n->value = '';
180
        }
181
        return false;
182
    }
183
184
    private function onContextType(Node &$n, Node &$previous, $lineString):bool
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
Coding Style introduced by
Private method name "Loader::onContextType" must be prefixed with an underscore
Loading history...
185
    {
186
        $deepest = $previous->getDeepestNode();
187
        if (($previous->type & Y::LITTERALS && $n->indent >= $previous->indent) || (($deepest->type & Y::LITTERALS) && is_null($deepest->value))) {
188
            $n->type = Y::SCALAR;
189
            $n->identifier = null;
190
            $n->value = trim($lineString);
191
            $previous = $deepest->getParent();
192
            return false;
193
        }
194
        if (is_null($deepest->value) && $deepest->type & (Y::LITTERALS|Y::REF_DEF|Y::SET_VALUE|Y::TAG)) {
195
            $previous = $deepest;
196
        }
197
        // if ($n->type & Y::SCALAR && $previous->type & Y::SCALAR) {
198
        //     $previous = $previous->getParent();
199
        // }
200
        return false;
201
    }
202
}
203