Completed
Pull Request — master (#2)
by
unknown
01:43
created

Comments::isBlank()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Consolidation\Comments;
4
5
/**
6
 * Remember comments in one text file (usually a yaml file), and
7
 * re-inject them into an edited copy of the same file.
8
 *
9
 * This is a workaround for the fact that the Symfony Yaml parser
10
 * does not record comments.
11
 *
12
 * Comments at the beginning and end of the file are guarenteed to
13
 * be retained at the beginning and the end of the resulting file.
14
 *
15
 * Comments that appear before sections of yaml that is deleted will
16
 * be deliberately discarded as well.
17
 *
18
 * If the resulting yaml file contents are reordered, comments may
19
 * become mis-ordered (attached to the wrong element).
20
 *
21
 * Comments that appear before sections of yaml that are edited may
22
 * be inadvertantly lost. It is recommended to always place comments
23
 * immediately before identifier lines (i.e. "foo:").
24
 */
25
class Comments
26
{
27
    protected $hasStored;
28
    protected $headComments;
29
    protected $accumulated;
30
    protected $lineIds;
31
    protected $stored;
32
    protected $endComments;
33
34
    public function __construct()
35
    {
36
        $this->hasStored = false;
37
        $this->headComments = false;
38
        $this->accumulated = [];
39
        $this->lineIds = [];
40
        $this->stored = [];
41
        $this->endComments = [];
42
    }
43
44
    /**
45
     * Collect all of the comments from a text file
46
     * (usually a yaml file).
47
     *
48
     * @param array $contentLines
49
     */
50
    public function collect(array $contentLines)
51
    {
52
        $contentLines = $this->removeTrailingBlankLines($contentLines);
53
54
        // Put look through the rest of the lines and store the comments as needed
55
        foreach ($contentLines as $line) {
56
            if ($this->isBlank($line)) {
57
                $this->accumulateEmptyLine();
58
            } elseif ($this->isComment($line)) {
59
                $this->accumulate($line);
60
            } else {
61
                $this->storeAccumulated($line);
62
            }
63
        }
64
        $this->endCollect();
65
    }
66
67
    /**
68
     * Description
69
     * @param array $contentLines
70
     * @return array of lines with comments re-intersperced
71
     */
72
    public function inject(array $contentLines)
73
    {
74
        $contentLines = $this->removeTrailingBlankLines($contentLines);
75
76
        // If there were any comments at the beginning of the
77
        // file, then put them back at the beginning.
78
        $result = $this->headComments === false ? [] : $this->headComments;
79
        foreach ($contentLines as $line) {
80
            $fetched = $this->find($line);
81
            $result = array_merge($result, $fetched);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type true; however, parameter $array1 of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

81
            $result = array_merge(/** @scrutinizer ignore-type */ $result, $fetched);
Loading history...
82
83
            $result[] = $line;
84
        }
85
        // Any comments found at the end of the file will stay at
86
        // the end of the file.
87
        $result = array_merge($result, $this->endComments);
88
        return $result;
89
    }
90
91
    /**
92
     * @param string $line
93
     * @return true if provided line is a comment
94
     */
95
    protected function isComment($line)
96
    {
97
        return preg_match('%^ *#%', $line);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match('%^ *#%', $line) returns the type integer which is incompatible with the documented return type true.
Loading history...
98
    }
99
100
    /**
101
     * Stop collecting. Any accumulated comments will be
102
     * remembered so that they may be re-injected at the
103
     * end of the new file.
104
     */
105
    protected function endCollect()
106
    {
107
        $this->endComments = $this->accumulated;
108
        $this->accumulated = [];
109
    }
110
111
    protected function accumulateEmptyLine()
112
    {
113
        if ($this->hasStored) {
114
            return $this->accumulate('');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->accumulate('') targeting Consolidation\Comments\Comments::accumulate() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
115
        }
116
117
        if ($this->headComments === false) {
118
            $this->headComments = [];
119
        }
120
121
        $this->headComments = array_merge($this->headComments, $this->accumulated);
0 ignored issues
show
Bug introduced by
It seems like $this->headComments can also be of type true; however, parameter $array1 of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

121
        $this->headComments = array_merge(/** @scrutinizer ignore-type */ $this->headComments, $this->accumulated);
Loading history...
122
        $this->accumulated = [''];
123
    }
124
125
    /**
126
     * Accumulate comments and blank lines in our cache.
127
     * @param string $line
128
     */
129
    protected function accumulate($line)
130
    {
131
        $this->accumulated[] = $line;
132
    }
133
134
    /**
135
     * When a non-comment line is found, remember all of
136
     * the comment lines that came before it in our cache.
137
     *
138
     * @param string $line
139
     */
140
    protected function storeAccumulated($line)
141
    {
142
143
        // Remember that we called storeAccumulated at least once
144
        $this->hasStored = true;
145
146
        // The very first time storeAccumulated is called, the
147
        // accumulated comments will be placed in $this->headComments
148
        // instead of stored, so they may be restored to the
149
        // beginning of the new file.
150
        if ($this->headComments === false) {
151
            $this->headComments = $this->accumulated;
152
            $this->accumulated = [];
153
            return;
154
        }
155
        if (!empty($this->accumulated)) {
156
            $lineId = $this->getLineId($line, true);
157
            $this->stored[$lineId][] = $this->accumulated;
158
            $this->accumulated = [];
159
        }
160
    }
161
162
    /**
163
     * Generates unique line id based on the key it contains.
164
     *
165
     * @param string $line
166
     * @param bool $isCollecting
167
     */
168
    protected function getLineId($line, $isCollecting = true)
169
    {
170
        list($id) = explode(':', $line, 2);
171
172
        if ($isCollecting) {
173
            if (isset($this->lineIds[$id])) {
174
                $this->lineIds[$id][] = end($this->lineIds[$id]) + 1;
175
            } else {
176
                $this->lineIds[$id] = [1];
177
            }
178
179
            return end($this->lineIds[$id]) . '|' . $id;
180
        }
181
182
        if (isset($this->lineIds[$id])) {
183
            return array_shift($this->lineIds[$id]) . '|' . $id;
184
        } else {
185
            return  '1|' . $id;
186
        }
187
    }
188
189
    /**
190
     * Check to see if the provided line has any associated comments.
191
     *
192
     * @param string $line
193
     */
194
    protected function find($line)
195
    {
196
        $lineId = $this->getLineId($line, false);
197
        if (!isset($this->stored[$lineId]) || empty($this->stored[$lineId])) {
198
            return [];
199
        }
200
        // The stored result is a stack of accumulated comments. Pop
201
        // one off; if more remain, they will be attached to the next
202
        // line with the same value.
203
        return array_shift($this->stored[$lineId]);
204
    }
205
206
    /**
207
     * Remove all of the blank lines from the end of an array of lines.
208
     */
209
    protected function removeTrailingBlankLines($lines)
210
    {
211
        // Remove all of the trailing blank lines.
212
        while (!empty($lines) && $this->isBlank(end($lines))) {
213
            array_pop($lines);
214
        }
215
        return $lines;
216
    }
217
218
    /**
219
     * Return 'true' if the provided line is empty (save for whitespace)
220
     */
221
    protected function isBlank($line)
222
    {
223
        return preg_match('#^\s*$#', $line);
224
    }
225
}
226