Chris@14: Chris@14: * Chris@14: * For the full copyright and license information, please view the LICENSE Chris@14: * file that was distributed with this source code. Chris@14: */ Chris@14: Chris@14: namespace SebastianBergmann\CodeCoverage\Node; Chris@14: Chris@14: use SebastianBergmann\CodeCoverage\CodeCoverage; Chris@14: Chris@14: class Builder Chris@14: { Chris@14: /** Chris@14: * @param CodeCoverage $coverage Chris@14: * Chris@14: * @return Directory Chris@14: */ Chris@14: public function build(CodeCoverage $coverage) Chris@14: { Chris@14: $files = $coverage->getData(); Chris@14: $commonPath = $this->reducePaths($files); Chris@14: $root = new Directory( Chris@14: $commonPath, Chris@14: null Chris@14: ); Chris@14: Chris@14: $this->addItems( Chris@14: $root, Chris@14: $this->buildDirectoryStructure($files), Chris@14: $coverage->getTests(), Chris@14: $coverage->getCacheTokens() Chris@14: ); Chris@14: Chris@14: return $root; Chris@14: } Chris@14: Chris@14: /** Chris@14: * @param Directory $root Chris@14: * @param array $items Chris@14: * @param array $tests Chris@14: * @param bool $cacheTokens Chris@14: */ Chris@14: private function addItems(Directory $root, array $items, array $tests, $cacheTokens) Chris@14: { Chris@14: foreach ($items as $key => $value) { Chris@14: if (\substr($key, -2) == '/f') { Chris@14: $key = \substr($key, 0, -2); Chris@14: Chris@14: if (\file_exists($root->getPath() . DIRECTORY_SEPARATOR . $key)) { Chris@14: $root->addFile($key, $value, $tests, $cacheTokens); Chris@14: } Chris@14: } else { Chris@14: $child = $root->addDirectory($key); Chris@14: $this->addItems($child, $value, $tests, $cacheTokens); Chris@14: } Chris@14: } Chris@14: } Chris@14: Chris@14: /** Chris@14: * Builds an array representation of the directory structure. Chris@14: * Chris@14: * For instance, Chris@14: * Chris@14: * Chris@14: * Array Chris@14: * ( Chris@14: * [Money.php] => Array Chris@14: * ( Chris@14: * ... Chris@14: * ) Chris@14: * Chris@14: * [MoneyBag.php] => Array Chris@14: * ( Chris@14: * ... Chris@14: * ) Chris@14: * ) Chris@14: * Chris@14: * Chris@14: * is transformed into Chris@14: * Chris@14: * Chris@14: * Array Chris@14: * ( Chris@14: * [.] => Array Chris@14: * ( Chris@14: * [Money.php] => Array Chris@14: * ( Chris@14: * ... Chris@14: * ) Chris@14: * Chris@14: * [MoneyBag.php] => Array Chris@14: * ( Chris@14: * ... Chris@14: * ) Chris@14: * ) Chris@14: * ) Chris@14: * Chris@14: * Chris@14: * @param array $files Chris@14: * Chris@14: * @return array Chris@14: */ Chris@14: private function buildDirectoryStructure($files) Chris@14: { Chris@14: $result = []; Chris@14: Chris@14: foreach ($files as $path => $file) { Chris@14: $path = \explode('/', $path); Chris@14: $pointer = &$result; Chris@14: $max = \count($path); Chris@14: Chris@14: for ($i = 0; $i < $max; $i++) { Chris@14: if ($i == ($max - 1)) { Chris@14: $type = '/f'; Chris@14: } else { Chris@14: $type = ''; Chris@14: } Chris@14: Chris@14: $pointer = &$pointer[$path[$i] . $type]; Chris@14: } Chris@14: Chris@14: $pointer = $file; Chris@14: } Chris@14: Chris@14: return $result; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Reduces the paths by cutting the longest common start path. Chris@14: * Chris@14: * For instance, Chris@14: * Chris@14: * Chris@14: * Array Chris@14: * ( Chris@14: * [/home/sb/Money/Money.php] => Array Chris@14: * ( Chris@14: * ... Chris@14: * ) Chris@14: * Chris@14: * [/home/sb/Money/MoneyBag.php] => Array Chris@14: * ( Chris@14: * ... Chris@14: * ) Chris@14: * ) Chris@14: * Chris@14: * Chris@14: * is reduced to Chris@14: * Chris@14: * Chris@14: * Array Chris@14: * ( Chris@14: * [Money.php] => Array Chris@14: * ( Chris@14: * ... Chris@14: * ) Chris@14: * Chris@14: * [MoneyBag.php] => Array Chris@14: * ( Chris@14: * ... Chris@14: * ) Chris@14: * ) Chris@14: * Chris@14: * Chris@14: * @param array $files Chris@14: * Chris@14: * @return string Chris@14: */ Chris@14: private function reducePaths(&$files) Chris@14: { Chris@14: if (empty($files)) { Chris@14: return '.'; Chris@14: } Chris@14: Chris@14: $commonPath = ''; Chris@14: $paths = \array_keys($files); Chris@14: Chris@14: if (\count($files) == 1) { Chris@14: $commonPath = \dirname($paths[0]) . '/'; Chris@14: $files[\basename($paths[0])] = $files[$paths[0]]; Chris@14: Chris@14: unset($files[$paths[0]]); Chris@14: Chris@14: return $commonPath; Chris@14: } Chris@14: Chris@14: $max = \count($paths); Chris@14: Chris@14: for ($i = 0; $i < $max; $i++) { Chris@14: // strip phar:// prefixes Chris@14: if (\strpos($paths[$i], 'phar://') === 0) { Chris@14: $paths[$i] = \substr($paths[$i], 7); Chris@14: $paths[$i] = \strtr($paths[$i], '/', DIRECTORY_SEPARATOR); Chris@14: } Chris@14: $paths[$i] = \explode(DIRECTORY_SEPARATOR, $paths[$i]); Chris@14: Chris@14: if (empty($paths[$i][0])) { Chris@14: $paths[$i][0] = DIRECTORY_SEPARATOR; Chris@14: } Chris@14: } Chris@14: Chris@14: $done = false; Chris@14: $max = \count($paths); Chris@14: Chris@14: while (!$done) { Chris@14: for ($i = 0; $i < $max - 1; $i++) { Chris@14: if (!isset($paths[$i][0]) || Chris@14: !isset($paths[$i + 1][0]) || Chris@14: $paths[$i][0] != $paths[$i + 1][0]) { Chris@14: $done = true; Chris@14: Chris@14: break; Chris@14: } Chris@14: } Chris@14: Chris@14: if (!$done) { Chris@14: $commonPath .= $paths[0][0]; Chris@14: Chris@14: if ($paths[0][0] != DIRECTORY_SEPARATOR) { Chris@14: $commonPath .= DIRECTORY_SEPARATOR; Chris@14: } Chris@14: Chris@14: for ($i = 0; $i < $max; $i++) { Chris@14: \array_shift($paths[$i]); Chris@14: } Chris@14: } Chris@14: } Chris@14: Chris@14: $original = \array_keys($files); Chris@14: $max = \count($original); Chris@14: Chris@14: for ($i = 0; $i < $max; $i++) { Chris@14: $files[\implode('/', $paths[$i])] = $files[$original[$i]]; Chris@14: unset($files[$original[$i]]); Chris@14: } Chris@14: Chris@14: \ksort($files); Chris@14: Chris@14: return \substr($commonPath, 0, -1); Chris@14: } Chris@14: }