Completed
Push — master ( 6517a9...981046 )
by Andrii
01:55
created

History::isInitTag()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * Changelog keeper
4
 *
5
 * @link      https://github.com/hiqdev/chkipper
6
 * @package   chkipper
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2016, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\chkipper\history;
12
13
/**
14
 * History class.
15
 *
16
 * @property array $headers: header => header
17
 * @property array $hashes:  hash => hash
18
 * @property array $links:   link => href
19
 * @property array $tags:    tag name => tag object
20
 *
21
 * @author Andrii Vasyliev <[email protected]>
22
 */
23
class History
24
{
25
    public $lastTag = 'Under development';
26
27
    public $initTag = 'Development started';
28
29
    protected $_project;
30
    protected $_headers = [];
31
    protected $_hashes  = [];
32
    protected $_links   = [];
33
    protected $_tags    = [];
34
35 2
    public function isInitTag($tag)
36
    {
37 2
        return $tag === $this->initTag;
38
    }
39
40 2
    public function isLastTag($tag)
41
    {
42 2
        return $tag === $this->lastTag;
43
    }
44
45 1
    public function setProject($value)
46
    {
47 1
        $this->_project = $value;
48 1
    }
49
50 2
    public function getProject()
51
    {
52 2
        if ($this->_project === null) {
53 1
            $this->_project = $this->detectProject();
54 1
        }
55
56 2
        return $this->_project;
57
    }
58
59 1
    public function detectProject()
60
    {
61 1
        foreach ($this->getHeaders() as $line) {
62 1
            if (preg_match('/\b([a-z0-9._-]{2,}\/[a-z0-9._-]{2,})\b/i', $line, $m)) {
63 1
                return $m[1];
64
            }
65
        }
66
    }
67
68 2
    public function addHeader($str)
69
    {
70 2
        $this->_headers[$str] = $str;
71 2
    }
72
73 1
    public function addHeaders(array $headers)
74
    {
75 1
        foreach ($headers as $header) {
76 1
            $this->addHeader($header);
77 1
        }
78 1
    }
79
80 1
    public function setHeaders(array $headers)
81
    {
82 1
        $this->_headers = [];
83 1
        $this->addHeaders($headers);
84 1
    }
85
86 2
    public function getHeaders()
87
    {
88 2
        return $this->_headers;
89
    }
90
91 2
    public function hasLink($link)
92
    {
93 2
        return isset($this->_links[$link]);
94
    }
95
96
    public function removeLink($link)
97
    {
98
        unset($this->_links[$link]);
99
    }
100
101 3
    public function addLink($link, $href)
102
    {
103 3
        $this->_links[$link] = $href;
104 3
    }
105
106
    public function addLinks(array $links)
107
    {
108
        foreach ($links as $link => $href) {
109
            $this->addLink($link, $href);
110
        }
111
    }
112
113 1
    public function setLinks(array $links)
114
    {
115 1
        $this->_links = $links;
116 1
    }
117
118 2
    public function getLinks()
119
    {
120 2
        return $this->_links;
121
    }
122
123 2
    public function hasHash($hash)
124
    {
125 2
        return isset($this->_hashes[(string) $hash]);
126
    }
127
128 3
    public function addHash($hash)
129
    {
130 3
        $this->_hashes[(string) $hash] = $hash;
131 3
    }
132
133
    public function addHashes(array $hashes)
134
    {
135
        foreach ($hashes as $hash) {
136
            $this->addHash($hash);
137
        }
138
    }
139
140
    public function setHashes(array $hashes)
141
    {
142
        $this->_hashes = [];
143
        $this->addHashes($hashes);
144
    }
145
146 2
    public function getHashes()
147
    {
148 2
        return $this->_hashes;
149
    }
150
151 2
    public function getFirstTag()
152
    {
153 2
        return reset($this->_tags);
154
    }
155
156
    public function setFirstTag($name)
157
    {
158
        $this->getFirstTag()->setName($name);
159
    }
160
161 3
    public function countTags()
162
    {
163 3
        return count($this->_tags);
164
    }
165
166 3
    public function initTags()
167
    {
168 3
        if (!$this->countTags()) {
169 3
            $this->addTag(new Tag($this->lastTag));
170 3
        }
171 3
    }
172
173 2
    public function getTags()
174
    {
175 2
        return $this->_tags;
176
    }
177
178
    /**
179
     * Adds given tags to the history.
180
     * @param Tag[] $tags
181
     * @param boolean $prependNotes default is append
182
     */
183 1
    public function addTags(array $tags, $prependNotes = false)
184
    {
185 1
        foreach ($tags as $name => $tag) {
186 1
            $this->addTag($tag, $prependNotes);
187 1
        }
188 1
    }
189
190 1
    public function setTags(array $tags)
191
    {
192 1
        $this->_tags = [];
193 1
        $this->addTags($tags);
194 1
    }
195
196
    /**
197
     * Returns tag by name.
198
     * Creates if not exists.
199
     * Returns first tag when given empty name.
200
     * @param string|Tag $tag tag name or tag object
201
     * @return Tag
202
     */
203 3
    public function findTag($tag)
204
    {
205 3
        if (!$tag) {
206 1
            $tag = reset($this->_tags) ?: $this->lastTag;
207 1
        }
208 3
        $name = $tag instanceof Tag ? $tag->getName() : $tag;
209 3
        if (!$this->hasTag($name)) {
210 3
            $this->_tags[$name] = new Tag($name);
211 3
        }
212
213 3
        return $this->_tags[$name];
214
    }
215
216 3
    public function hasTag($tag)
217
    {
218 3
        return isset($this->_tags[$tag]);
219
    }
220
221
    public function removeTag($name)
222
    {
223
        foreach ($this->_tags as $k => $tag) {
224
            if ($tag->getName() === $name) {
225
                unset($this->_tags[$k]);
226
227
                return;
228
            }
229
        }
230
    }
231
232
    /**
233
     * Adds tag.
234
     * @param Tag $tag
235
     * @param boolean $prependNotes default is append
236
     * @return Tag the added tag
237
     */
238 3
    public function addTag(Tag $tag, $prependNotes = false)
239
    {
240 3
        return $this->findTag($tag->getName())->setDate($tag->getDate())->addNotes($tag->getNotes(), $prependNotes);
241
    }
242
243
    /**
244
     * Merges given history into the current.
245
     * @param History $history
246
     * @param boolean $prependNotes default is append
247
     */
248
    public function merge(History $history, $prependNotes = false)
249
    {
250
        $this->mergeTags($history->getTags(), $prependNotes);
251
        $this->addLinks($history->getLinks());
252
        $this->addHashes($history->getHashes());
253
    }
254
255
    /**
256
     * Merge given tags into the current history.
257
     * @param Tag[] $tags
258
     * @param boolean $prependNotes default is append
259
     */
260
    public function mergeTags(array $tags, $prependNotes = false)
261
    {
262
        foreach ($tags as $tag) {
263
            foreach ($tag->getNotes() as $note) {
264
                $note->removeCommits($this->getHashes());
265
            }
266
        }
267
        $this->addTags($tags, $prependNotes);
268
        //$olds = $this->getTags();
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
269
        //$this->_tags = $tags;
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
270
        //$this->addTags($$olds);
0 ignored issues
show
Unused Code Comprehensibility introduced by
88% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
271
    }
272
273
    /**
274
     * Normalizes the history.
275
     */
276 2
    public function normalize($options = [])
277
    {
278
        static $defaults = [
279
            'removeEmptyFirstTag' => [],
280
            'addInitTag'          => [],
281
            'setTagDates'         => [],
282
            'addCommitLinks'      => [],
283
            'addTagLinks'         => [],
284
            'removeCommitLinks'   => [],
285
            'prettifyUserLinks'   => [],
286 2
        ];
287 2
        $options = array_merge($defaults, $options);
288 2
        foreach ($options as $func => $args) {
289 2
            if (is_array($args)) {
290 2
                call_user_func_array([$this, $func], $args);
291 2
            }
292 2
        }
293 2
    }
294
295
    /**
296
     * Removes first tag if it is empty: has no notes and no commits.
297
     */
298 2
    public function removeEmptyFirstTag()
299
    {
300 2
        $tag = $this->getFirstTag();
301 2
        $notes = $tag->getNotes();
302 2
        if (count($notes) > 1) {
303
            return;
304
        }
305 2
        if (count($notes) > 0) {
306 2
            $note = reset($notes);
307 2
            if ($note->getNote() || count($note->getCommits()) > 0) {
308 2
                return;
309
            }
310
        }
311
        $this->removeTag($tag->getName());
312
    }
313
314
    /**
315
     * Adds init tag with oldest commit date.
316
     */
317 2
    public function addInitTag()
318
    {
319 2
        if (!$this->hasTag($this->initTag)) {
320 1
            $min = '';
321 1
            foreach ($this->getTags() as $tag) {
322 1
                foreach ($tag->getNotes() as $note) {
323 1 View Code Duplication
                    foreach ($note->getCommits() as $commit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
324 1
                        $date = $commit->getDate();
325 1
                        if (!$min || strcmp($date, $min) < 0) {
326 1
                            $min = $date;
327 1
                        }
328 1
                    }
329 1
                }
330 1
            }
331 1
            if ($min) {
332 1
                $this->addTag(new Tag($this->initTag, $min));
333 1
            }
334 1
        }
335 2
    }
336
337
    /**
338
     * Normalizes dates to all the tags.
339
     * Drops date for the last tag and sets for others.
340
     */
341 2
    public function setTagDates()
342
    {
343 2
        foreach ($this->getTags() as $tag) {
344 2
            if ($tag->getName() === $this->lastTag) {
345 2
                $tag->unsetDate();
346 2
            } elseif (!$tag->getDate()) {
347
                $tag->setDate($tag->findDate());
348
            }
349 2
        }
350 2
    }
351
352
    /**
353
     * Adds tag links.
354
     */
355 2
    public function addTagLinks()
356
    {
357 2
        $prev = null;
358 2
        foreach (array_keys($this->getTags()) as $tag) {
359 2
            if ($prev && ($this->isLastTag($prev) || !$this->hasLink($prev))) {
360 2
                $this->addLink($prev, $this->generateTagHref($prev, $tag));
361 2
            }
362 2
            $prev = $tag;
363 2
        }
364 2
    }
365
366 2
    public function generateTagHref($prev, $curr)
367
    {
368 2
        $project = $this->getProject();
369 2
        if ($this->isInitTag($curr)) {
370 2
            return "https://github.com/$project/releases/tag/$prev";
371
        }
372 1
        if ($this->isLastTag($prev)) {
373 1
            $prev = 'HEAD';
374 1
        }
375
376 1
        return "https://github.com/$project/compare/$curr...$prev";
377
    }
378
379
    /**
380
     * Adds links for commits not having ones.
381
     */
382 2
    public function addCommitLinks()
383
    {
384 2
        foreach ($this->getHashes() as $hash) {
385 2
            if (!$this->hasLink($hash)) {
386 1
                $this->addLink($hash, $this->generateHashHref($hash));
387 1
            }
388 2
        }
389 2
    }
390
391 1
    public function generateHashHref($hash)
392
    {
393 1
        $project = $this->getProject();
394
395 1
        return "https://github.com/$project/commit/$hash";
396
    }
397
398
    /**
399
     * Removes commit links that are not present in the history.
400
     */
401 2
    public function removeCommitLinks($all = false)
402
    {
403 2
        foreach ($this->getLinks() as $link => $href) {
404 2
            if (preg_match('/^[0-9a-f]{7}$/', $link)) {
405 2
                if ($all || !$this->hasHash($link)) {
406
                    $this->removeLink($link);
407
                }
408 2
            }
409 2
        }
410 2
    }
411
412
    /**
413
     * Converts user links to given links.
414
     * Usage: add 2 links to `history.md` like this:
415
     *
416
     * [@hiqsol]: https://github.com/hiqsol
417
     * [[email protected]]: https://github.com/hiqsol
418
     */
419 2
    public function prettifyUserLinks()
420
    {
421 2
        $users = [];
422 2
        $subs = [];
423 2
        foreach ($this->getLinks() as $link => $href) {
424 2
            if ($link[0] === '@') {
425 1
                $users[$href] = $link;
426 2
            } else if (isset($users[$href])) {
427
                $subs[$link] = $users[$href];
428
            }
429 2
        }
430 2
        if (!$subs) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $subs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
431 2
            return;
432
        }
433
        foreach ($this->getTags() as $tag) {
434
            foreach ($tag->getNotes() as $note) {
435
                foreach ($note->getCommits() as $commit) {
436
                    $author = $commit->getAuthor();
437
                    if (isset($subs[$author])) {
438
                        $commit->setAuthor($subs[$author]);
439
                    }
440
                }
441
            }
442
        }
443
    }
444
}
445