1<?php
2/**
3 * Version control report base class for PHP_CodeSniffer.
4 *
5 * PHP version 5
6 *
7 * @category  PHP
8 * @package   PHP_CodeSniffer
9 * @author    Ben Selby <benmatselby@gmail.com>
10 * @copyright 2009-2014 SQLI <www.sqli.com>
11 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
12 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13 * @link      http://pear.php.net/package/PHP_CodeSniffer
14 */
15
16/**
17 * Version control report base class for PHP_CodeSniffer.
18 *
19 * PHP version 5
20 *
21 * @category  PHP
22 * @package   PHP_CodeSniffer
23 * @author    Ben Selby <benmatselby@gmail.com>
24 * @copyright 2009-2014 SQLI <www.sqli.com>
25 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
26 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
27 * @version   Release: 1.2.2
28 * @link      http://pear.php.net/package/PHP_CodeSniffer
29 */
30abstract class PHP_CodeSniffer_Reports_VersionControl implements PHP_CodeSniffer_Report
31{
32
33    /**
34     * The name of the report we want in the output.
35     *
36     * @var string
37     */
38    protected $reportName = 'VERSION CONTROL';
39
40    /**
41     * A cache of author stats collected during the run.
42     *
43     * @var array
44     */
45    private $_authorCache = array();
46
47    /**
48     * A cache of blame stats collected during the run.
49     *
50     * @var array
51     */
52    private $_praiseCache = array();
53
54    /**
55     * A cache of source stats collected during the run.
56     *
57     * @var array
58     */
59    private $_sourceCache = array();
60
61
62    /**
63     * Generate a partial report for a single processed file.
64     *
65     * Function should return TRUE if it printed or stored data about the file
66     * and FALSE if it ignored the file. Returning TRUE indicates that the file and
67     * its data should be counted in the grand totals.
68     *
69     * @param array                $report      Prepared report data.
70     * @param PHP_CodeSniffer_File $phpcsFile   The file being reported on.
71     * @param boolean              $showSources Show sources?
72     * @param int                  $width       Maximum allowed line width.
73     *
74     * @return boolean
75     */
76    public function generateFileReport(
77        $report,
78        PHP_CodeSniffer_File $phpcsFile,
79        $showSources=false,
80        $width=80
81    ) {
82        $blames = $this->getBlameContent($report['filename']);
83
84        foreach ($report['messages'] as $line => $lineErrors) {
85            $author = 'Unknown';
86            if (isset($blames[($line - 1)]) === true) {
87                $blameAuthor = $this->getAuthor($blames[($line - 1)]);
88                if ($blameAuthor !== false) {
89                    $author = $blameAuthor;
90                }
91            }
92
93            if (isset($this->_authorCache[$author]) === false) {
94                $this->_authorCache[$author] = 0;
95                $this->_praiseCache[$author] = array(
96                                                'good' => 0,
97                                                'bad'  => 0,
98                                               );
99            }
100
101            $this->_praiseCache[$author]['bad']++;
102
103            foreach ($lineErrors as $column => $colErrors) {
104                foreach ($colErrors as $error) {
105                    $this->_authorCache[$author]++;
106
107                    if ($showSources === true) {
108                        $source = $error['source'];
109                        if (isset($this->_sourceCache[$author][$source]) === false) {
110                            $this->_sourceCache[$author][$source] = array(
111                                                                     'count'   => 1,
112                                                                     'fixable' => $error['fixable'],
113                                                                    );
114                        } else {
115                            $this->_sourceCache[$author][$source]['count']++;
116                        }
117                    }
118                }
119            }
120
121            unset($blames[($line - 1)]);
122        }//end foreach
123
124        // No go through and give the authors some credit for
125        // all the lines that do not have errors.
126        foreach ($blames as $line) {
127            $author = $this->getAuthor($line);
128            if ($author === false) {
129                $author = 'Unknown';
130            }
131
132            if (isset($this->_authorCache[$author]) === false) {
133                // This author doesn't have any errors.
134                if (PHP_CODESNIFFER_VERBOSITY === 0) {
135                    continue;
136                }
137
138                $this->_authorCache[$author] = 0;
139                $this->_praiseCache[$author] = array(
140                                                'good' => 0,
141                                                'bad'  => 0,
142                                               );
143            }
144
145            $this->_praiseCache[$author]['good']++;
146        }//end foreach
147
148        return true;
149
150    }//end generateFileReport()
151
152
153    /**
154     * Prints the author of all errors and warnings, as given by "version control blame".
155     *
156     * @param string  $cachedData    Any partial report data that was returned from
157     *                               generateFileReport during the run.
158     * @param int     $totalFiles    Total number of files processed during the run.
159     * @param int     $totalErrors   Total number of errors found during the run.
160     * @param int     $totalWarnings Total number of warnings found during the run.
161     * @param int     $totalFixable  Total number of problems that can be fixed.
162     * @param boolean $showSources   Show sources?
163     * @param int     $width         Maximum allowed line width.
164     * @param boolean $toScreen      Is the report being printed to screen?
165     *
166     * @return void
167     */
168    public function generate(
169        $cachedData,
170        $totalFiles,
171        $totalErrors,
172        $totalWarnings,
173        $totalFixable,
174        $showSources=false,
175        $width=80,
176        $toScreen=true
177    ) {
178        $errorsShown = ($totalErrors + $totalWarnings);
179        if ($errorsShown === 0) {
180            // Nothing to show.
181            return;
182        }
183
184        // Make sure the report width isn't too big.
185        $maxLength = 0;
186        foreach ($this->_authorCache as $author => $count) {
187            $maxLength = max($maxLength, strlen($author));
188            if ($showSources === true && isset($this->_sourceCache[$author]) === true) {
189                foreach ($this->_sourceCache[$author] as $source => $sourceData) {
190                    if ($source === 'count') {
191                        continue;
192                    }
193
194                    $maxLength = max($maxLength, (strlen($source) + 9));
195                }
196            }
197        }
198
199        $width = min($width, ($maxLength + 30));
200        $width = max($width, 70);
201        arsort($this->_authorCache);
202
203        echo PHP_EOL."\033[1m".'PHP CODE SNIFFER '.$this->reportName.' BLAME SUMMARY'."\033[0m".PHP_EOL;
204        echo str_repeat('-', $width).PHP_EOL."\033[1m";
205        if ($showSources === true) {
206            echo 'AUTHOR   SOURCE'.str_repeat(' ', ($width - 43)).'(Author %) (Overall %) COUNT'.PHP_EOL;
207            echo str_repeat('-', $width).PHP_EOL;
208        } else {
209            echo 'AUTHOR'.str_repeat(' ', ($width - 34)).'(Author %) (Overall %) COUNT'.PHP_EOL;
210            echo str_repeat('-', $width).PHP_EOL;
211        }
212
213        echo "\033[0m";
214
215        if ($showSources === true) {
216            $maxSniffWidth = ($width - 15);
217
218            if ($totalFixable > 0) {
219                $maxSniffWidth -= 4;
220            }
221        }
222
223        $fixableSources = 0;
224
225        foreach ($this->_authorCache as $author => $count) {
226            if ($this->_praiseCache[$author]['good'] === 0) {
227                $percent = 0;
228            } else {
229                $total   = ($this->_praiseCache[$author]['bad'] + $this->_praiseCache[$author]['good']);
230                $percent = round(($this->_praiseCache[$author]['bad'] / $total * 100), 2);
231            }
232
233            $overallPercent = '('.round((($count / $errorsShown) * 100), 2).')';
234            $authorPercent  = '('.$percent.')';
235            $line           = str_repeat(' ', (6 - strlen($count))).$count;
236            $line           = str_repeat(' ', (12 - strlen($overallPercent))).$overallPercent.$line;
237            $line           = str_repeat(' ', (11 - strlen($authorPercent))).$authorPercent.$line;
238            $line           = $author.str_repeat(' ', ($width - strlen($author) - strlen($line))).$line;
239
240            if ($showSources === true) {
241                $line = "\033[1m$line\033[0m";
242            }
243
244            echo $line.PHP_EOL;
245
246            if ($showSources === true && isset($this->_sourceCache[$author]) === true) {
247                $errors = $this->_sourceCache[$author];
248                asort($errors);
249                $errors = array_reverse($errors);
250
251                foreach ($errors as $source => $sourceData) {
252                    if ($source === 'count') {
253                        continue;
254                    }
255
256                    $count = $sourceData['count'];
257
258                    $srcLength = strlen($source);
259                    if ($srcLength > $maxSniffWidth) {
260                        $source = substr($source, 0, $maxSniffWidth);
261                    }
262
263                    $line = str_repeat(' ', (5 - strlen($count))).$count;
264
265                    echo '         ';
266                    if ($totalFixable > 0) {
267                        echo '[';
268                        if ($sourceData['fixable'] === true) {
269                            echo 'x';
270                            $fixableSources++;
271                        } else {
272                            echo ' ';
273                        }
274
275                        echo '] ';
276                    }
277
278                    echo $source;
279                    if ($totalFixable > 0) {
280                        echo str_repeat(' ', ($width - 18 - strlen($source)));
281                    } else {
282                        echo str_repeat(' ', ($width - 14 - strlen($source)));
283                    }
284
285                    echo $line.PHP_EOL;
286                }//end foreach
287            }//end if
288        }//end foreach
289
290        echo str_repeat('-', $width).PHP_EOL;
291        echo "\033[1m".'A TOTAL OF '.$errorsShown.' SNIFF VIOLATION';
292        if ($errorsShown !== 1) {
293            echo 'S';
294        }
295
296        echo ' WERE COMMITTED BY '.count($this->_authorCache).' AUTHOR';
297        if (count($this->_authorCache) !== 1) {
298            echo 'S';
299        }
300
301        echo "\033[0m";
302
303        if ($totalFixable > 0) {
304            if ($showSources === true) {
305                echo PHP_EOL.str_repeat('-', $width).PHP_EOL;
306                echo "\033[1mPHPCBF CAN FIX THE $fixableSources MARKED SOURCES AUTOMATICALLY ($totalFixable VIOLATIONS IN TOTAL)\033[0m";
307            } else {
308                echo PHP_EOL.str_repeat('-', $width).PHP_EOL;
309                echo "\033[1mPHPCBF CAN FIX $totalFixable OF THESE SNIFF VIOLATIONS AUTOMATICALLY\033[0m";
310            }
311        }
312
313        echo PHP_EOL.str_repeat('-', $width).PHP_EOL.PHP_EOL;
314
315        if ($toScreen === true && PHP_CODESNIFFER_INTERACTIVE === false) {
316            PHP_CodeSniffer_Reporting::printRunTime();
317        }
318
319    }//end generate()
320
321
322    /**
323     * Extract the author from a blame line.
324     *
325     * @param string $line Line to parse.
326     *
327     * @return mixed string or false if impossible to recover.
328     */
329    abstract protected function getAuthor($line);
330
331
332    /**
333     * Gets the blame output.
334     *
335     * @param string $filename File to blame.
336     *
337     * @return array
338     */
339    abstract protected function getBlameContent($filename);
340
341
342}//end class
343