Passed
Pull Request — master (#2)
by
unknown
01:51
created

Comments   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 226
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 28
eloc 68
c 4
b 0
f 0
dl 0
loc 226
rs 10

12 Methods

Rating   Name   Duplication   Size   Complexity  
A isComment() 0 3 1
A removeTrailingBlankLines() 0 7 3
A find() 0 10 3
A isBlank() 0 3 1
A accumulateEmptyLine() 0 12 3
A getLineId() 0 18 4
A __construct() 0 8 1
A storeAccumulated() 0 19 3
A endCollect() 0 4 1
A collect() 0 15 4
A inject() 0 17 3
A accumulate() 0 3 1
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
class Comments
22
{
23
    protected $hasStored;
24
    protected $headComments;
25
    protected $accumulated;
26
    protected $lineIds;
27
    protected $stored;
28
    protected $endComments;
29
30
    public function __construct()
31
    {
32
        $this->hasStored = false;
33
        $this->headComments = false;
34
        $this->accumulated = [];
35
        $this->lineIds = [];
36
        $this->stored = [];
37
        $this->endComments = [];
38
    }
39
40
    /**
41
     * Collect all of the comments from a text file
42
     * (usually a yaml file).
43
     *
44
     * @param array $contentLines
45
     */
46
    public function collect(array $contentLines)
47
    {
48
        $contentLines = $this->removeTrailingBlankLines($contentLines);
49
50
        // Put look through the rest of the lines and store the comments as needed
51
        foreach ($contentLines as $line) {
52
            if ($this->isBlank($line)) {
53
                $this->accumulateEmptyLine();
54
            } elseif ($this->isComment($line)) {
55
                $this->accumulate($line);
56
            } else {
57
                $this->storeAccumulated($line);
58
            }
59
        }
60
        $this->endCollect();
61
    }
62
63
    /**
64
     * Description
65
     * @param array $contentLines
66
     * @return array of lines with comments re-intersperced
67
     */
68
    public function inject(array $contentLines)
69
    {
70
        $contentLines = $this->removeTrailingBlankLines($contentLines);
71
72
        // If there were any comments at the beginning of the
73
        // file, then put them back at the beginning.
74
        $result = $this->headComments === false ? [] : $this->headComments;
75
        foreach ($contentLines as $line) {
76
            $fetched = $this->find($line);
77
            $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

77
            $result = array_merge(/** @scrutinizer ignore-type */ $result, $fetched);
Loading history...
78
79
            $result[] = $line;
80
        }
81
        // Any comments found at the end of the file will stay at
82
        // the end of the file.
83
        $result = array_merge($result, $this->endComments);
84
        return $result;
85
    }
86
87
    /**
88
     * @param string $line
89
     * @return true if provided line is a comment
90
     */
91
    protected function isComment($line)
92
    {
93
        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...
94
    }
95
96
    /**
97
     * Stop collecting. Any accumulated comments will be
98
     * remembered so that they may be re-injected at the
99
     * end of the new file.
100
     */
101
    protected function endCollect()
102
    {
103
        $this->endComments = $this->accumulated;
104
        $this->accumulated = [];
105
    }
106
107
    protected function accumulateEmptyLine()
108
    {
109
        if ($this->hasStored) {
110
            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...
111
        }
112
113
        if ($this->headComments === false) {
114
            $this->headComments = [];
115
        }
116
117
        $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

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