Passed
Pull Request — master (#137)
by Romain
03:18
created

UpdateChangelog::formatLogs()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 46
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 28
nc 4
nop 1
dl 0
loc 46
rs 9.472
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * Copyright (C) 2018
5
 * Nathan Boiron <[email protected]>
6
 * Romain Canon <[email protected]>
7
 *
8
 * This file is part of the TYPO3 NotiZ project.
9
 * It is free software; you can redistribute it and/or modify it
10
 * under the terms of the GNU General Public License, either
11
 * version 3 of the License, or any later version.
12
 *
13
 * For the full copyright and license information, see:
14
 * http://www.gnu.org/licenses/gpl-3.0.html
15
 */
16
17
/**
18
 * Run the following command in shell:
19
 *
20
 * $ php Build/update-changelog.php x.y.z
21
 *
22
 * Then update the `CHANGELOG.md` file, do modifications if needed, then commit.
23
 */
24
class UpdateChangelog
25
{
26
    const CHANGELOG_FILE = 'CHANGELOG.md';
27
28
    protected $version;
29
    protected $currentDate;
30
    protected $lastGitTag;
31
32
    /**
33
     * Must have the new version number as parameter.
34
     *
35
     * @param string $version
36
     */
37
    public function __construct($version)
38
    {
39
        $this->version = $version;
40
41
        // Format "02 February 2018"
42
        $this->currentDate = date('d F Y');
43
44
        // Fetches last tag that was added in git
45
        $this->lastGitTag = trim(shell_exec('git describe --tags --abbrev=0'));
46
    }
47
48
    /**
49
     * Will update the changelog file with the latest commits, ordered as
50
     * follow:
51
     *
52
     * - New features
53
     * - Bugs fixed
54
     * - Important/breaking changes
55
     * - Other less important commits
56
     */
57
    public function run()
58
    {
59
        $features = $this->getLogs('FEATURE');
60
        $bugfix = $this->getLogs('BUGFIX');
61
        $important = $this->getLogs('!!!');
62
        $others = $this->getInvertedLogs('FEATURE', 'BUGFIX', '!!!');
63
64
        $currentChangelog = file_get_contents(self::CHANGELOG_FILE);
65
66
        $changelog = $this->getChangelog($features, $bugfix, $important, $others);
67
        $changelog = preg_replace('/\n/', "\n$changelog", $currentChangelog, 1);
68
69
        file_put_contents(self::CHANGELOG_FILE, $changelog);
70
    }
71
72
    /**
73
     * @param string $features
74
     * @param string $bugfix
75
     * @param string $important
76
     * @param string $others
77
     * @return string
78
     */
79
    protected function getChangelog($features, $bugfix, $important, $others)
80
    {
81
        $changelog = "
82
## v$this->version - $this->currentDate";
83
84
        if ($features) {
85
            $changelog .= "
86
87
### New features
88
$features";
89
        }
90
91
        if ($bugfix) {
92
            $changelog .= "
93
### Bugs fixed
94
$bugfix";
95
        }
96
97
        if ($important) {
98
            $changelog .= "
99
### Important
100
101
**⚠ Please pay attention to the changes below as they might break your TYPO3 installation:** 
102
$important";
103
        }
104
105
        if ($others) {
106
            $changelog .= "
107
### Others
108
$others";
109
        }
110
111
        return $changelog;
112
    }
113
114
    /**
115
     * @param string[] $types
116
     * @return string
117
     */
118
    protected function getLogs(...$types)
119
    {
120
        $command = $this->getLogsCommand(...$types);
121
122
        return $this->formatLogs($command);
123
    }
124
125
    /**
126
     * @param string[] $types
127
     * @return string
128
     */
129
    protected function getInvertedLogs(...$types)
130
    {
131
        $command = $this->getLogsCommand(...$types);
132
        $command .= ' --invert-grep';
133
134
        return $this->formatLogs($command);
135
    }
136
137
    /**
138
     * @param string[] $types
139
     * @return string
140
     */
141
    protected function getLogsCommand(...$types)
142
    {
143
        $command = "git log HEAD...$this->lastGitTag" .
144
            ' --date=format:"%d %b %Y"';
145
146
        foreach ($types as $type) {
147
            $command .= ' --grep="^\[' . $type . '\]"';
148
        }
149
150
        return $command;
151
    }
152
153
    /**
154
     * @param $command
155
     * @return string
156
     */
157
    protected function formatLogs($command)
158
    {
159
        $title = $this->getGitLog($command, '%s');
160
        $revisionShort = $this->getGitLog($command, '%h');
161
        $revision = $this->getGitLog($command, '%H');
162
        $body = $this->getGitLog($command, '%b');
163
        $author = $this->getGitLog($command, '%an');
164
        $authorEmail = $this->getGitLog($command, '%ae');
165
        $date = $this->getGitLog($command, '%ad');
166
167
        $count = count($title);
168
169
        if ($count === 0) {
170
            return '';
171
        }
172
173
        $result = '';
174
175
        for ($i = 0; $i < count($title); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
176
            $detailedTitle = $this->replaceCodeSections($title[$i]);
177
            $pullRequest = null;
178
179
            $detailedBody = preg_replace('/\n/', "\n> ", $body[$i]);
180
            $detailedBody = $this->addLinkToGitHubIssues($detailedBody);
181
182
            // Add a link to detected GitHub issues number.
183
            if (preg_match('/#([0-9]+)/', $title[$i], $pullRequestResult)) {
184
                $detailedTitle = preg_replace('/ *\(#([0-9]+)\)/', '', $detailedTitle);
185
                $detailedTitle = preg_replace('/ *#([0-9]+)/', '', $detailedTitle);
186
                $pullRequest = ' / [#' . $pullRequestResult[1] . '](https://github.com/CuyZ/NotiZ/issues/' . $pullRequestResult[1] . ')';
187
            }
188
189
            $result .= <<<HTML
190
191
<details>
192
<summary>$detailedTitle</summary>
193
194
> *by [$author[$i]](mailto:$authorEmail[$i])* on *$date[$i] / [$revisionShort[$i]](https://github.com/CuyZ/NotiZ/commit/$revision[$i])$pullRequest*
195
196
> $detailedBody
197
</details>
198
199
HTML;
200
        }
201
202
        return $result;
203
    }
204
205
    /**
206
     * @param string $command
207
     * @param string $format
208
     * @return array
209
     */
210
    protected function getGitLog($command, $format)
211
    {
212
        $result = shell_exec($command . '  --pretty=tformat:"' . $format . '>>>NEXT<<<"');
213
        $result = explode('>>>NEXT<<<', $result);
214
        $result = array_map('trim', $result);
215
        array_pop($result);
216
        $result = array_map([$this, 'sanitizeLog'], $result);
217
218
        return $result;
219
    }
220
221
    /**
222
     * @param string $text
223
     * @return string
224
     */
225
    protected function sanitizeLog($text)
226
    {
227
        // Replace redundant line breaks.
228
        $text = preg_replace('/\n\n\n+/m', "\n\n", $text);
229
230
        // Removes the commit prefix.
231
        $text = preg_replace('/^\[!!!\](.*)$/', '$1', $text);
232
        $text = preg_replace('/^\[[^ \]]+\] (.*)$/', '$1', $text);
233
234
        return $text;
235
    }
236
237
    /**
238
     * Add a link to all detected GitHub issues number.
239
     *
240
     * @param string $text
241
     * @return string
242
     */
243
    protected function addLinkToGitHubIssues($text)
244
    {
245
        return preg_replace('/#([0-9]+)/', '[#$1](https:\/\/github.com\/CuyZ\/NotiZ\/issues\/$1)', $text);
246
    }
247
248
    /**
249
     * @param string $text
250
     * @return string
251
     */
252
    protected function replaceCodeSections($text)
253
    {
254
        return preg_replace('/`([^`]*)`/', '<code>$1</code>', $text);
255
    }
256
}
257
258
unset($argv[0]);
259
(new UpdateChangelog(...$argv))->run();
0 ignored issues
show
Bug introduced by
$argv is expanded, but the parameter $version of UpdateChangelog::__construct() does not expect variable arguments. ( Ignorable by Annotation )

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

259
(new UpdateChangelog(/** @scrutinizer ignore-type */ ...$argv))->run();
Loading history...
260