]> git.mxchange.org Git - friendica.git/blob - src/Console/DocBloxErrorChecker.php
88c08666cac9d2ed91094dbae8421f80cfabe8ca
[friendica.git] / src / Console / DocBloxErrorChecker.php
1 <?php
2
3 namespace Friendica\Console;
4
5 use Friendica\App;
6
7 /**
8  * When I installed docblox, I had the experience that it does not generate any output at all.
9  * This script may be used to find that kind of problems with the documentation build process.
10  * If docblox generates output, use another approach for debugging.
11  *
12  * Basically, docblox takes a list of files to build documentation from. This script assumes there is a file or set of files
13  * breaking the build when it is included in that list. It tries to calculate the smallest list containing these files.
14  * Unfortunatly, the original problem is NP-complete, so what the script does is a best guess only.
15  *
16  * So it starts with a list of all files in the project.
17  * If that list can't be build, it cuts it in two parts and tries both parts independently. If only one of them breaks,
18  * it takes that one and tries the same independently. If both break, it assumes this is the smallest set. This assumption
19  * is not necessarily true. Maybe the smallest set consists of two files and both of them were in different parts when
20  * the list was divided, but by now it is my best guess. To make this assumption better, the list is shuffled after every step.
21  *
22  * After that, the script tries to remove a file from the list. It tests if the list breaks and if so, it
23  * assumes that the file it removed belongs to the set of erroneous files.
24  * This is done for all files, so, in the end removing one file leads to a working doc build.
25  *
26  * @author Alexander Kampmann
27  * @author Hypolite Petovan <hypolite@mrpetovan.com>
28  */
29 class DocBloxErrorChecker extends \Asika\SimpleConsole\Console
30 {
31
32         protected $helpOptions = ['h', 'help', '?'];
33
34         /** @var App */
35         private $app;
36
37         public function __construct(App $app, array $argv = null)
38         {
39                 parent::__construct($argv);
40
41                 $this->app = $app;
42         }
43
44         protected function getHelp()
45         {
46                 $help = <<<HELP
47 console docbloxerrorchecker - Checks the file tree for docblox errors
48 Usage
49         bin/console docbloxerrorchecker [-h|--help|-?] [-v]
50
51 Options
52     -h|--help|-? Show help information
53     -v           Show more debug information.
54 HELP;
55                 return $help;
56         }
57
58         protected function doExecute()
59         {
60                 if ($this->getOption('v')) {
61                         $this->out('Class: ' . __CLASS__);
62                         $this->out('Arguments: ' . var_export($this->args, true));
63                         $this->out('Options: ' . var_export($this->options, true));
64                 }
65
66                 if (count($this->args) > 0) {
67                         throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
68                 }
69
70                 if (!$this->commandExists('docblox')) {
71                         throw new \RuntimeException('DocBlox isn\'t available.');
72                 }
73
74                 $dir = $this->app->getBasePath();
75
76                 //stack for dirs to search
77                 $dirstack = [];
78                 //list of source files
79                 $filelist = [];
80
81                 //loop over all files in $dir
82                 while ($dh = opendir($dir)) {
83                         while ($file = readdir($dh)) {
84                                 if (is_dir($dir . "/" . $file)) {
85                                         //add to directory stack
86                                         if (strpos($file, '.') !== 0) {
87                                                 array_push($dirstack, $dir . "/" . $file);
88                                                 $this->out('dir ' . $dir . '/' . $file);
89                                         }
90                                 } else {
91                                         //test if it is a source file and add to filelist
92                                         if (substr($file, strlen($file) - 4) == ".php") {
93                                                 array_push($filelist, $dir . "/" . $file);
94                                                 $this->out($dir . '/' . $file);
95                                         }
96                                 }
97                         }
98                         //look at the next dir
99                         $dir = array_pop($dirstack);
100                 }
101
102                 //check the entire set
103                 if ($this->runs($filelist)) {
104                         throw new \RuntimeException("I can not detect a problem.");
105                 }
106
107                 //check half of the set and discard if that half is okay
108                 $res = $filelist;
109                 $i = count($res);
110                 do {
111                         $this->out($i . '/' . count($filelist) . ' elements remaining.');
112                         $res = $this->reduce($res, count($res) / 2);
113                         shuffle($res);
114                         $i = count($res);
115                 } while (count($res) < $i);
116
117                 //check one file after another
118                 $needed = [];
119
120                 while (count($res) != 0) {
121                         $file = array_pop($res);
122
123                         if ($this->runs(array_merge($res, $needed))) {
124                                 $this->out('needs: ' . $file . ' and file count ' . count($needed));
125                                 array_push($needed, $file);
126                         }
127                 }
128
129                 $this->out('Smallest Set is: ' . $this->namesList($needed) . ' with ' . count($needed) . ' files. ');
130
131                 return 0;
132         }
133
134         private function commandExists($command)
135         {
136                 $prefix = strpos(strtolower(PHP_OS),'win') > -1 ? 'where' : 'which';
137                 exec("{$prefix} {$command}", $output, $returnVal);
138                 return $returnVal === 0;
139         }
140
141         /**
142          * This function generates a comma separated list of file names.
143          *
144          * @param array $fileset Set of file names
145          *
146          * @return string comma-separated list of the file names
147          */
148         private function namesList($fileset)
149         {
150                 return implode(',', $fileset);
151         }
152
153         /**
154          * This functions runs phpdoc on the provided list of files
155          *
156          * @param array $fileset Set of filenames
157          *
158          * @return bool true, if that set can be built
159          */
160         private function runs($fileset)
161         {
162                 $fsParam = $this->namesList($fileset);
163                 $this->exec('docblox -t phpdoc_out -f ' . $fsParam);
164                 if (file_exists("phpdoc_out/index.html")) {
165                         $this->out('Subset ' . $fsParam . ' is okay.');
166                         $this->exec('rm -r phpdoc_out');
167                         return true;
168                 } else {
169                         $this->out('Subset ' . $fsParam . ' failed.');
170                         return false;
171                 }
172         }
173
174         /**
175          * This functions cuts down a fileset by removing files until it finally works.
176          * it was meant to be recursive, but php's maximum stack size is to small. So it just simulates recursion.
177          *
178          * In that version, it does not necessarily generate the smallest set, because it may not alter the elements order enough.
179          *
180          * @param array $fileset set of filenames
181          * @param int $ps number of files in subsets
182          *
183          * @return array a part of $fileset, that crashes
184          */
185         private function reduce($fileset, $ps)
186         {
187                 //split array...
188                 $parts = array_chunk($fileset, $ps);
189                 //filter working subsets...
190                 $parts = array_filter($parts, [$this, 'runs']);
191                 //melt remaining parts together
192                 if (is_array($parts)) {
193                         return array_reduce($parts, "array_merge", []);
194                 }
195                 return [];
196         }
197
198 }