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: }