diff --git a/README.md b/README.md index fc1d5ce..c435f2d 100644 --- a/README.md +++ b/README.md @@ -643,6 +643,28 @@ Removes a compression flag. **Returns**: The current instance of the SevenZip class. +### `rename(array $renames): string` + +Renames files within an archive. Note that the rename operation is not natively supported for `bzip2` archives. + +**Parameters** + +- `$renames`: An associative array mapping old file names to new file names. + +**Returns**: The output of the 7-Zip command. + +**Throws** + +- `InvalidArgumentException`: If the archive path is not set or the renames array is empty. +- `RuntimeException`: If the format is not supported (e.g., `bzip2`). + +**Example** + +```php +$sevenZip->source('/path/to/archive.7z') + ->rename(['old_name.txt' => 'new_name.txt']); +``` + ### `reset(): SevenZip` Resets the property values to their original state. diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 04a5ea4..470db3b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,17 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Deprecated in PHP 8\.4\: Parameter \#3 \$previous \(Throwable\) is implicitly nullable via default value null\.$#' - identifier: parameter.implicitlyNullable - count: 1 - path: src/Exceptions/ExecutableNotFoundException.php - - - - message: '#^Deprecated in PHP 8\.4\: Parameter \#2 \$value \(string\) is implicitly nullable via default value null\.$#' - identifier: parameter.implicitlyNullable - count: 1 - path: src/SevenZip.php - - message: '#^Method Verseles\\SevenZip\\SevenZip\:\:addFlag\(\) should return \$this\(Verseles\\SevenZip\\SevenZip\) but returns Verseles\\SevenZip\\SevenZip\.$#' identifier: return.type diff --git a/src/Exceptions/ExecutableNotFoundException.php b/src/Exceptions/ExecutableNotFoundException.php index 3b1c30b..131bad2 100644 --- a/src/Exceptions/ExecutableNotFoundException.php +++ b/src/Exceptions/ExecutableNotFoundException.php @@ -6,7 +6,7 @@ class ExecutableNotFoundException extends \Exception { - public function __construct($message = 'Executable 7z not found.', $code = 0, Throwable $previous = null) + public function __construct($message = 'Executable 7z not found.', $code = 0, ?Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/src/SevenZip.php b/src/SevenZip.php index 67eecbb..e13e876 100644 --- a/src/SevenZip.php +++ b/src/SevenZip.php @@ -315,7 +315,7 @@ public function mmt(int|bool|string $threads = "on"): self */ public function addFlag( string $flag, - string $value = null, + ?string $value = null, bool $glued = false, ): self { if ($glued && $value !== null) { @@ -1086,6 +1086,59 @@ public function fileInfo(): array return $this->parseFileInfoOutput($output); } + /** + * Rename files in an archive. + * + * @param array $renames An associative array mapping old file names to new file names. + * + * @return string The output of the 7-Zip command. + * @throws \InvalidArgumentException If source path is not set or the renames array is empty. + * @throws \RuntimeException If the archive format is unsupported. + */ + public function rename(array $renames): string + { + if (!$this->getSourcePath()) { + throw new \InvalidArgumentException( + "Archive file path (source) must be set", + ); + } + + if (empty($renames)) { + throw new \InvalidArgumentException( + "Renames array cannot be empty", + ); + } + + $extension = strtolower(pathinfo($this->getSourcePath(), PATHINFO_EXTENSION)); + + if (in_array($this->getFormat(), ['bzip2', 'bz2']) || in_array($extension, ['bzip2', 'bz2'])) { + throw new \RuntimeException( + "Rename operation is not supported for bzip2 archives", + ); + } + + if ($this->getPassword()) { + $this->addFlag("p", $this->getPassword(), glued: true); + } + + $renameArgs = []; + foreach ($renames as $oldName => $newName) { + $renameArgs[] = $oldName; + $renameArgs[] = $newName; + } + + $command = [ + $this->sevenZipPath, + "rn", + ...$this->flagrize($this->getAlwaysFlags()), + ...$this->flagrize($this->getCustomFlags()), + $this->getSourcePath(), + ...$renameArgs, + ]; + + return $this->runCommand($command); + } + /** * Test the integrity of an archive. * diff --git a/tests/SevenZipTest.php b/tests/SevenZipTest.php index 824e221..c24a31f 100644 --- a/tests/SevenZipTest.php +++ b/tests/SevenZipTest.php @@ -274,9 +274,35 @@ public function testVerify(string $format): void $this->assertStringContainsString('Everything is Ok', $output); } - #[Covers('\Verseles\SevenZip\SevenZip::extract')] + #[Covers('\Verseles\SevenZip\SevenZip::rename')] #[DataProvider('compressAndExtractDataProvider')] #[Depends('testVerify')] + public function testRename(string $format): void + { + $archive = $this->testDir . '/target/archive.' . $format; + + if (in_array($format, ['bzip2', 'bz2'])) { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Rename operation is not supported for bzip2 archives'); + } + + $renames = [ + 'source/Avatart.svg' => 'source/RenamedAvatar.svg' + ]; + + // Do not set format explicitly so that we rely on source file extension for unsupported tests + $output = $this->sevenZip + ->source(path: $archive) + ->rename($renames); + + if (!in_array($format, ['bzip2', 'bz2'])) { + $this->assertStringContainsString('Everything is Ok', $output); + } + } + + #[Covers('\Verseles\SevenZip\SevenZip::extract')] + #[DataProvider('compressAndExtractDataProvider')] + #[Depends('testRename')] public function testExtract(string $format): void { $archive = $this->testDir . '/target/archive.' . $format; @@ -287,7 +313,11 @@ public function testExtract(string $format): void ->source(path: $archive) ->target(path: $target) ->extract(); - $this->assertFileExists(filename: $target . '/source/Avatart.svg'); + if (in_array($format, ['bzip2', 'bz2'])) { + $this->assertFileExists(filename: $target . '/source/Avatart.svg'); + } else { + $this->assertFileExists(filename: $target . '/source/RenamedAvatar.svg'); + } $this->assertFileExists(filename: $target . '/source/js_interop_usage.md'); unlink($archive);