Message::getLinkToMethodInCommit()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 5
1
<?php
2
3
4
namespace TheCodingMachine\WashingMachine\Gitlab;
5
6
7
use TheCodingMachine\WashingMachine\Clover\Analysis\Difference;
8
use TheCodingMachine\WashingMachine\Clover\CoverageDetectorInterface;
9
use TheCodingMachine\WashingMachine\Clover\CrapMethodFetcherInterface;
10
use TheCodingMachine\WashingMachine\Clover\DiffService;
11
12
/**
13
 * A class to build the message sent to Gitlab.
14
 */
15
class Message
16
{
17
    const MAX_NB_LINES_PER_FILE = 50;
18
19
    private $msg = '';
20
21
    public function addCoverageMessage(CoverageDetectorInterface $coverageDetector, CoverageDetectorInterface $previousCoverageDetector)
22
    {
23
        $coverage = $coverageDetector->getCoveragePercentage();
24
        $previousCoverage = $previousCoverageDetector->getCoveragePercentage();
25
26
        $additionalText = '';
27
        $style = '';
28
        if ($coverage > $previousCoverage + 0.0001) {
29
            $additionalText = sprintf('(<em>+%.2f%%</em>)', ($coverage - $previousCoverage)*100);
30
            $style .= 'background-color: #00994c; color: white';
31
        } elseif ($coverage < $previousCoverage - 0.0001) {
32
            $additionalText = sprintf('(<em>-%.2f%%</em>)', ($previousCoverage - $coverage)*100);
33
            $style .= 'background-color: #ff6666; color: white';
34
        }
35
36
37
        // Note: there is a failure in the way Gitlab escapes HTML for the tables. Let's use this!.
38
        $this->msg .= sprintf('<table>
39
<tr>
40
<td>PHP&nbsp;code&nbsp;coverage:</td>
41
<td style="font-weight: bold">%.2f%%</td>
42
<td style="%s">%s</td>
43
<td width="99%%"></td>
44
</tr>
45
</table><br/>', $coverageDetector->getCoveragePercentage()*100, $style, $additionalText);
46
    }
47
48
49
    public function addDifferencesHtml(CrapMethodFetcherInterface $methodFetcher, CrapMethodFetcherInterface $previousMethodFetcher, DiffService $diffService, string $commitId, string $gitlabUrl, string $projectName)
50
    {
51
        $differences = $diffService->getMeaningfulDifferences($methodFetcher, $previousMethodFetcher);
52
53
        $this->msg .= $this->getDifferencesHtml($differences, $commitId, $gitlabUrl, $projectName);
54
    }
55
56
    public function addDifference(Difference $difference, string $commitId, string $gitlabUrl, string $projectName)
57
    {
58
        $this->msg .= $this->getDifferencesHtml([$difference], $commitId, $gitlabUrl, $projectName);
59
    }
60
61
62
    private function getDifferencesHtml(array $differences, string $commitId, string $gitlabUrl, string $projectName) : string
63
    {
64
        if (empty($differences)) {
65
            return "No meaningful differences in code complexity detected.\n";
66
        }
67
68
        $tableTemplate = '<table>
69
<tr>
70
<th></th>
71
<th>C.R.A.P.</th>
72
<th>Variation</th>
73
<th width="99%%"></th>
74
</tr>
75
%s
76
</table>';
77
        $tableRows = '';
78
        $crapScoreEmojiGenerator = EmojiGenerator::createCrapScoreEmojiGenerator();
79
        foreach ($differences as $difference) {
80
            $style = '';
81
            if (!$difference->isNew()) {
82
83
                if ($difference->getCrapDifference() < 0) {
84
                    $style = 'background-color: #00994c; color: white';
85
                } else {
86
                    $style = 'background-color: #ff6666; color: white';
87
                }
88
                $differenceCol = sprintf('%+d', $difference->getCrapDifference());
89
                $crapScoreEmoji = '';
90
            } else {
91
                $differenceCol = '<em>New</em>';
92
                // For new rows, let's display an emoji
93
                $crapScoreEmoji = ' '.$crapScoreEmojiGenerator->getEmoji($difference->getCrapScore());
94
            }
95
96
            if ($difference->getFile() !== null) {
97
                $link = $this->getLinkToMethodInCommit($gitlabUrl, $projectName, $commitId, $difference->getFile(), $difference->getLine());
98
                $fullLink = sprintf('<a href="%s">%s</a>', $link, $difference->getMethodShortName());
99
            } else {
100
                $fullLink = $difference->getMethodShortName();
101
            }
102
103
            $tableRows .= sprintf('<tr>
104
<td><code>%s</code></td>
105
<td style="text-align:center">%d%s</td>
106
<td style="text-align:center;%s">%s</td>
107
</tr>', $fullLink, $difference->getCrapScore(), $crapScoreEmoji, $style, $differenceCol);
108
        }
109
110
        return sprintf($tableTemplate, $tableRows);
111
    }
112
113
    private function getLinkToMethodInCommit(string $gitlabUrl, string $projectName, string $commit, string $filePath, int $line)
114
    {
115
        return rtrim($gitlabUrl, '/').'/'.$projectName.'/blob/'.$commit.'/'.ltrim($filePath, '/').'#L'.$line;
116
    }
117
118
    public function addFile(\SplFileInfo $file, string $gitlabUrl, string $projectName, string $buildId)
119
    {
120
        list($text, $isComplete) = $this->getFirstLines($file, self::MAX_NB_LINES_PER_FILE);
121
122
        $text = str_replace('```', '\\```', $text);
123
        $text = str_replace('~~~', '\\~~~', $text);
124
125
        $url = $this->getArtifactFileUrl($file->getFilename(), $gitlabUrl, $projectName, $buildId);
126
127
        $this->msg .= sprintf("\n<strong>[%s](%s)</strong>\n", $file->getFilename(), $url);
128
        $this->msg .= sprintf("```\n%s%s```\n", $text, $isComplete?'':"... (file truncated)\n");
129
130
        if (!$isComplete) {
131
            $this->msg .= sprintf("[Download complete file](%s)\n", $url);
132
        }
133
    }
134
135
    private function getArtifactFileUrl(string $fileName, string $gitlabUrl, string $projectName, int $buildId) : string
136
    {
137
        return $gitlabUrl.'/'.$projectName.'/builds/'.$buildId.'/artifacts/file/'.$fileName;
138
    }
139
140
    /**
141
     * Returns the first lines of a file
142
     *
143
     * @param \SplFileInfo $file
144
     * @param int $maxLines
145
     * @return array First element: the string, second element: whether the returned string represents the whole file or not.
146
     */
147
    private function getFirstLines(\SplFileInfo $file, int $maxLines) : array
148
    {
149
        // Let's get 50 lines at most.
150
        $cnt = $maxLines;
151
        $fileObject = $file->openFile();
152
        $str = '';
153
        while (!$fileObject->eof() && $cnt !== 0) {
154
            $str .= $fileObject->fgets();
155
            $cnt--;
156
        }
157
158
        return [$str, $cnt !== 0];
159
    }
160
161
    public function __toString()
162
    {
163
        return $this->msg;
164
    }
165
166
}