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