diff --git a/fixtures/info/tree-phar.phar b/fixtures/info/tree-phar.phar index cd4b5356f..db8a76325 100644 Binary files a/fixtures/info/tree-phar.phar and b/fixtures/info/tree-phar.phar differ diff --git a/src/Console/Command/Build.php b/src/Console/Command/Build.php index ab3495374..4970b39d4 100644 --- a/src/Console/Command/Build.php +++ b/src/Console/Command/Build.php @@ -17,8 +17,8 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use const E_USER_DEPRECATED; use function trigger_error; +use const E_USER_DEPRECATED; /** * @deprecated diff --git a/src/Console/Command/Compile.php b/src/Console/Command/Compile.php index 077c9e509..75ee79e11 100644 --- a/src/Console/Command/Compile.php +++ b/src/Console/Command/Compile.php @@ -47,6 +47,7 @@ use function count; use function decoct; use function explode; +use function filesize; use function function_exists; use function get_class; use function implode; @@ -56,7 +57,7 @@ use function KevinGH\Box\FileSystem\make_path_relative; use function KevinGH\Box\FileSystem\remove; use function KevinGH\Box\FileSystem\rename; -use function KevinGH\Box\formatted_filesize; +use function KevinGH\Box\format_size; use function KevinGH\Box\get_phar_compression_algorithms; use function posix_setrlimit; use function putenv; @@ -828,7 +829,9 @@ private function logEndBuilding(BuildLogger $logger, SymfonyStyle $io, Box $box, sprintf( 'PHAR: %s (%s)', $box->count() > 1 ? $box->count().' files' : $box->count().' file', - formatted_filesize($path) + format_size( + filesize($path) + ) ) .PHP_EOL .'You can inspect the generated PHAR with the "info" command.' diff --git a/src/Console/Command/Info.php b/src/Console/Command/Info.php index 2e5d17f5e..5e9f267a5 100644 --- a/src/Console/Command/Info.php +++ b/src/Console/Command/Info.php @@ -14,6 +14,7 @@ namespace KevinGH\Box\Console\Command; +use Assert\Assertion; use DateTimeImmutable; use DirectoryIterator; use Phar; @@ -34,11 +35,12 @@ use function array_sum; use function count; use function end; +use function filesize; use function is_array; use function iterator_to_array; use function KevinGH\Box\FileSystem\copy; use function KevinGH\Box\FileSystem\remove; -use function KevinGH\Box\formatted_filesize; +use function KevinGH\Box\format_size; use function key; use function realpath; use function sprintf; @@ -56,6 +58,7 @@ final class Info extends Command private const LIST_OPT = 'list'; private const METADATA_OPT = 'metadata'; private const MODE_OPT = 'mode'; + private const DEPTH_OPT = 'depth'; /** * The list of recognized compression algorithms. @@ -121,10 +124,17 @@ protected function configure(): void $this->addOption( self::MODE_OPT, 'm', - InputOption::VALUE_OPTIONAL, + InputOption::VALUE_REQUIRED, 'The listing mode. (default: indent, options: indent, flat)', 'indent' ); + $this->addOption( + self::DEPTH_OPT, + 'd', + InputOption::VALUE_REQUIRED, + 'The depth of the tree displayed', + -1 + ); } /** @@ -168,6 +178,10 @@ public function execute(InputInterface $input, OutputInterface $output): int public function showInfo(string $file, string $originalFile, InputInterface $input, OutputInterface $output, SymfonyStyle $io): int { + $depth = (int) $input->getOption(self::DEPTH_OPT); + + Assertion::greaterOrEqualThan($depth, -1, 'Expected the depth to be a positive integer or -1, got "%d"'); + try { try { $phar = new Phar($file); @@ -178,11 +192,16 @@ public function showInfo(string $file, string $originalFile, InputInterface $inp return $this->showPharInfo( $phar, $input->getOption(self::LIST_OPT), + $depth, 'indent' === $input->getOption(self::MODE_OPT), $output, $io ); } catch (Throwable $throwable) { + if ($output->isDebug()) { + throw $throwable; + } + $io->error( sprintf( 'Could not read the file "%s".', @@ -214,8 +233,14 @@ private function showGlobalInfo(OutputInterface $output, SymfonyStyle $io): int /** * @param Phar|PharData $phar */ - private function showPharInfo($phar, bool $content, bool $indent, OutputInterface $output, SymfonyStyle $io): int - { + private function showPharInfo( + $phar, + bool $content, + int $depth, + bool $indent, + OutputInterface $output, + SymfonyStyle $io + ): int { $signature = $phar->getSignature(); $this->showPharGlobalInfo($phar, $io, $signature); @@ -226,6 +251,8 @@ private function showPharInfo($phar, bool $content, bool $indent, OutputInterfac $this->renderContents( $output, $phar, + 0, + $depth, $indent ? 0 : false, $root, $phar, @@ -320,7 +347,9 @@ private function showPharGlobalInfo($phar, SymfonyStyle $io, $signature): void sprintf( 'Contents:%s (%s)', 1 === $totalCount ? ' 1 file' : " $totalCount files", - formatted_filesize($phar->getPath()) + format_size( + filesize($phar->getPath()) + ) ) ); } @@ -361,11 +390,17 @@ private function render(OutputInterface $output, array $attributes): void private function renderContents( OutputInterface $output, iterable $list, + int $depth, + int $maxDepth, $indent, string $base, $phar, string $root ): void { + if (-1 !== $maxDepth && $depth > $maxDepth) { + return; + } + foreach ($list as $item) { $item = $phar[str_replace($root, '', $item->getPathname())]; @@ -386,22 +421,33 @@ private function renderContents( $output->writeln("$path"); } } else { - $compression = ' [NONE]'; + $compression = '[NONE]'; foreach (self::FILE_ALGORITHMS as $code => $name) { if ($item->isCompressed($code)) { - $compression = " [$name]"; + $compression = "[$name]"; break; } } - $output->writeln($path.$compression); + $fileSize = format_size($item->getCompressedSize()); + + $output->writeln( + sprintf( + '%s %s - %s', + $path, + $compression, + $fileSize + ) + ); } if ($item->isDir()) { $this->renderContents( $output, new DirectoryIterator($item->getPathname()), + $depth + 1, + $maxDepth, (false === $indent) ? $indent : $indent + 2, $base, $phar, diff --git a/src/PhpSettingsHandler.php b/src/PhpSettingsHandler.php index 50dce21be..ec03729df 100644 --- a/src/PhpSettingsHandler.php +++ b/src/PhpSettingsHandler.php @@ -17,13 +17,13 @@ use Composer\XdebugHandler\Process; use Composer\XdebugHandler\XdebugHandler; use Psr\Log\LoggerInterface; -use const PHP_EOL; use function function_exists; use function getenv; use function ini_get; use function KevinGH\Box\FileSystem\append_to_file; use function sprintf; use function trim; +use const PHP_EOL; /** * @private diff --git a/src/functions.php b/src/functions.php index 699a6e845..94e55771b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -56,12 +56,11 @@ function get_phar_compression_algorithm_extension(int $algorithm): ?string /** * @private */ -function formatted_filesize(string $path): string +// TODO: add more tests for this +function format_size(int $size): string { - Assertion::file($path); - - $size = filesize($path); $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + $power = $size > 0 ? (int) floor(log($size, 1024)) : 0; return sprintf( diff --git a/tests/BoxTest.php b/tests/BoxTest.php index b1b6b0b8b..fc0c77636 100644 --- a/tests/BoxTest.php +++ b/tests/BoxTest.php @@ -16,7 +16,6 @@ use Exception; use InvalidArgumentException; -use function iterator_to_array; use KevinGH\Box\Compactor\FakeCompactor; use KevinGH\Box\Console\DisplayNormalizer; use KevinGH\Box\Test\FileSystemTestCase; @@ -35,6 +34,7 @@ use function extension_loaded; use function file_put_contents; use function in_array; +use function iterator_to_array; use function KevinGH\Box\FileSystem\canonicalize; use function KevinGH\Box\FileSystem\dump_file; use function KevinGH\Box\FileSystem\make_tmp_dir; diff --git a/tests/ConfigurationFileTest.php b/tests/ConfigurationFileTest.php index ff1742ca2..7388f233e 100644 --- a/tests/ConfigurationFileTest.php +++ b/tests/ConfigurationFileTest.php @@ -17,12 +17,12 @@ use Generator; use InvalidArgumentException; use KevinGH\Box\Json\JsonValidationException; -use const DIRECTORY_SEPARATOR; use function file_put_contents; use function KevinGH\Box\FileSystem\dump_file; use function KevinGH\Box\FileSystem\make_path_absolute; use function KevinGH\Box\FileSystem\rename; use function KevinGH\Box\FileSystem\symlink; +use const DIRECTORY_SEPARATOR; /** * @covers \KevinGH\Box\Configuration diff --git a/tests/ConfigurationTest.php b/tests/ConfigurationTest.php index 56324f319..8c5c99a63 100644 --- a/tests/ConfigurationTest.php +++ b/tests/ConfigurationTest.php @@ -25,11 +25,11 @@ use Phar; use Seld\JsonLint\ParsingException; use stdClass; -use const DIRECTORY_SEPARATOR; use function file_put_contents; use function KevinGH\Box\FileSystem\dump_file; use function KevinGH\Box\FileSystem\remove; use function KevinGH\Box\FileSystem\rename; +use const DIRECTORY_SEPARATOR; /** * @covers \KevinGH\Box\Configuration diff --git a/tests/ConfigurationTestCase.php b/tests/ConfigurationTestCase.php index 38493adf3..6cda8df1c 100644 --- a/tests/ConfigurationTestCase.php +++ b/tests/ConfigurationTestCase.php @@ -17,10 +17,10 @@ use KevinGH\Box\Console\ConfigurationHelper; use KevinGH\Box\Test\FileSystemTestCase; use stdClass; -use const DIRECTORY_SEPARATOR; use function file_put_contents; use function KevinGH\Box\FileSystem\make_path_absolute; use function natcasesort; +use const DIRECTORY_SEPARATOR; abstract class ConfigurationTestCase extends FileSystemTestCase { diff --git a/tests/Console/Command/CompileTest.php b/tests/Console/Command/CompileTest.php index 73b0ae477..a540db913 100644 --- a/tests/Console/Command/CompileTest.php +++ b/tests/Console/Command/CompileTest.php @@ -14,7 +14,6 @@ namespace KevinGH\Box\Console\Command; -use const DIRECTORY_SEPARATOR; use DirectoryIterator; use Generator; use InvalidArgumentException; diff --git a/tests/Console/Command/InfoTest.php b/tests/Console/Command/InfoTest.php index 8dd2daa12..d9175bc5c 100644 --- a/tests/Console/Command/InfoTest.php +++ b/tests/Console/Command/InfoTest.php @@ -14,18 +14,24 @@ namespace KevinGH\Box\Console\Command; +use InvalidArgumentException; use KevinGH\Box\Console\Application; use KevinGH\Box\Console\DisplayNormalizer; use Phar; use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Tester\CommandTester; +use UnexpectedValueException; use function preg_replace; use function realpath; +///** +// * @covers \KevinGH\Box\Console\Command\Info +// * +// * @runTestsInSeparateProcesses +// */ /** - * @covers \KevinGH\Box\Console\Command\Info - * - * @runTestsInSeparateProcesses + * @coversNothing */ class InfoTest extends TestCase { @@ -178,6 +184,25 @@ public function test_it_cannot_provide_info_about_an_invalid_phar_without_extens $this->assertSame(1, $this->commandTester->getStatusCode()); } + public function test_it_displays_the_error_in_debug_verbosity(): void + { + $file = self::FIXTURES.'/foo'; + + try { + $this->commandTester->execute( + [ + 'command' => 'info', + 'phar' => $file, + ], + ['verbosity' => OutputInterface::VERBOSITY_DEBUG] + ); + + $this->fail('Expected exception to be thrown.'); + } catch (UnexpectedValueException $exception) { + $this->assertStringStartsWith('Cannot create phar', $exception->getMessage()); + } + } + public function test_it_provides_info_about_a_targz_phar(): void { $pharPath = self::FIXTURES.'/simple-phar.tar.gz'; @@ -287,8 +312,8 @@ public function test_it_provides_a_phar_info_with_the_tree_of_the_content(): voi API Version: $version Archive Compression: - - BZ2 (50.00%) - - None (50.00%) + - BZ2 (33.33%) + - None (66.67%) Signature: {$signature['hash_type']} Signature Hash: {$signature['hash']} @@ -298,10 +323,13 @@ public function test_it_provides_a_phar_info_with_the_tree_of_the_content(): voi 'test' => 123, ) -Contents: 2 files (6.71KB) +Contents: 3 files (6.75KB) a/ - bar.php [BZ2] -foo.php [NONE] + bar.php [BZ2] - 60.00B +b/ + beta/ + bar.php [NONE] - 0.00B +foo.php [NONE] - 19.00B OUTPUT; @@ -331,8 +359,176 @@ public function test_it_provides_a_phar_info_with_the_flat_tree_of_the_content() API Version: $version Archive Compression: - - BZ2 (50.00%) - - None (50.00%) + - BZ2 (33.33%) + - None (66.67%) + +Signature: {$signature['hash_type']} +Signature Hash: {$signature['hash']} + +Metadata: +array ( + 'test' => 123, +) + +Contents: 3 files (6.75KB) +a/bar.php [BZ2] - 60.00B +b/beta/bar.php [NONE] - 0.00B +foo.php [NONE] - 19.00B + +OUTPUT; + + $this->assertSame($expected, DisplayNormalizer::removeTrailingSpaces($this->commandTester->getDisplay(true))); + $this->assertSame(0, $this->commandTester->getStatusCode()); + } + + public function test_it_can_limit_the_tree_depth(): void + { + $pharPath = self::FIXTURES.'/tree-phar.phar'; + $phar = new Phar($pharPath); + + $version = $phar->getVersion(); + $signature = $phar->getSignature(); + + $this->commandTester->execute( + [ + 'command' => 'info', + 'phar' => $pharPath, + '--list' => true, + '--metadata' => true, + '--depth' => 0, + ] + ); + + $expected = << 123, +) + +Contents: 3 files (6.75KB) +a/ +b/ +foo.php [NONE] - 19.00B + +OUTPUT; + + $this->assertSame($expected, DisplayNormalizer::removeTrailingSpaces($this->commandTester->getDisplay(true))); + $this->assertSame(0, $this->commandTester->getStatusCode()); + + $this->commandTester->execute( + [ + 'command' => 'info', + 'phar' => $pharPath, + '--list' => true, + '--metadata' => true, + '--depth' => 1, + ] + ); + + $expected = << 123, +) + +Contents: 3 files (6.75KB) +a/ + bar.php [BZ2] - 60.00B +b/ + beta/ +foo.php [NONE] - 19.00B + +OUTPUT; + + $this->assertSame($expected, DisplayNormalizer::removeTrailingSpaces($this->commandTester->getDisplay(true))); + $this->assertSame(0, $this->commandTester->getStatusCode()); + + $this->commandTester->execute( + [ + 'command' => 'info', + 'phar' => $pharPath, + '--list' => true, + '--metadata' => true, + '--depth' => -1, + ] + ); + + $expected = << 123, +) + +Contents: 3 files (6.75KB) +a/ + bar.php [BZ2] - 60.00B +b/ + beta/ + bar.php [NONE] - 0.00B +foo.php [NONE] - 19.00B + +OUTPUT; + + $this->assertSame($expected, DisplayNormalizer::removeTrailingSpaces($this->commandTester->getDisplay(true))); + $this->assertSame(0, $this->commandTester->getStatusCode()); + } + + public function test_it_can_limit_the_tree_depth_in_flat_mode(): void + { + $pharPath = self::FIXTURES.'/tree-phar.phar'; + $phar = new Phar($pharPath); + + $version = $phar->getVersion(); + $signature = $phar->getSignature(); + + $this->commandTester->execute( + [ + 'command' => 'info', + 'phar' => $pharPath, + '--list' => true, + '--metadata' => true, + '--depth' => 1, + '--mode' => 'flat', + ] + ); + + $expected = << 123, ) -Contents: 2 files (6.71KB) -a/bar.php [BZ2] -foo.php [NONE] +Contents: 3 files (6.75KB) +a/bar.php [BZ2] - 60.00B +foo.php [NONE] - 19.00B OUTPUT; $this->assertSame($expected, DisplayNormalizer::removeTrailingSpaces($this->commandTester->getDisplay(true))); $this->assertSame(0, $this->commandTester->getStatusCode()); } + + public function test_it_cannot_accept_an_invalid_depth(): void + { + $pharPath = self::FIXTURES.'/tree-phar.phar'; + $phar = new Phar($pharPath); + + try { + $this->commandTester->execute( + [ + 'command' => 'info', + 'phar' => $pharPath, + '--list' => true, + '--metadata' => true, + '--depth' => -10, + ] + ); + + $this->fail('Expected exception to be thrown.'); + } catch (InvalidArgumentException $exception) { + $this->assertSame( + 'Expected the depth to be a positive integer or -1, got "-10"', + $exception->getMessage() + ); + } + } }