3 namespace Friendica\Core\Console;
\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
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
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
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
24 * @author Alexander Kampmann
\r
25 * @author Hypolite Petovan <mrpetovan@gmail.com>
\r
27 class DocBloxErrorChecker extends \Asika\SimpleConsole\Console
\r
30 protected $helpOptions = ['h', 'help', '?'];
\r
32 protected function getHelp()
\r
35 console docbloxerrorchecker - Checks the file tree for docblox errors
\r
37 bin/console docbloxerrorchecker [-h|--help|-?] [-v]
\r
40 -h|--help|-? Show help information
\r
41 -v Show more debug information.
\r
46 protected function doExecute()
\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
54 if (count($this->args) > 0) {
\r
55 throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
\r
58 if (!$this->commandExists('docblox')) {
\r
59 throw new \RuntimeException('DocBlox isn\'t available.');
\r
62 //return from util folder to frindica base dir
\r
63 $dir = get_app()->get_basepath();
\r
65 //stack for dirs to search
\r
67 //list of source files
\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
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
87 //look at the next dir
\r
88 $dir = array_pop($dirstack);
\r
91 //check the entire set
\r
92 if ($this->runs($filelist)) {
\r
93 throw new \RuntimeException("I can not detect a problem.");
\r
96 //check half of the set and discard if that half is okay
\r
100 $this->out($i . '/' . count($filelist) . ' elements remaining.');
\r
101 $res = $this->reduce($res, count($res) / 2);
\r
104 } while (count($res) < $i);
\r
106 //check one file after another
\r
109 while (count($res) != 0) {
\r
110 $file = array_pop($res);
\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
118 $this->out('Smallest Set is: ' . $this->namesList($needed) . ' with ' . count($needed) . ' files. ');
\r
123 private function commandExists($command)
\r
125 $prefix = strpos(strtolower(PHP_OS),'win') > -1 ? 'where' : 'which';
\r
126 exec("{$prefix} {$command}", $output = [], $returnVal = 0);
\r
127 return $returnVal === 0;
\r
131 * This function generates a comma separated list of file names.
\r
135 * @param array $fileset Set of file names
\r
137 * @return string comma-separated list of the file names
\r
139 private function namesList($fileset)
\r
141 return implode(',', $fileset);
\r
145 * This functions runs phpdoc on the provided list of files
\r
148 * @param array $fileset Set of filenames
\r
150 * @return bool true, if that set can be built
\r
152 private function runs($fileset)
\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
161 $this->out('Subset ' . $fsParam . ' failed.');
\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
170 * In that version, it does not necessarily generate the smallest set, because it may not alter the elements order enough.
\r
174 * @param array $fileset set of filenames
\r
175 * @param int $ps number of files in subsets
\r
177 * @return array a part of $fileset, that crashes
\r
179 private function reduce($fileset, $ps)
\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