-
Notifications
You must be signed in to change notification settings - Fork 87
#2219: Add UnpackCommandlet for extracting already supported archieve Formats #2334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
majesteSil
wants to merge
13
commits into
devonfw:main
Choose a base branch
from
majesteSil:feature/issue-2219-add-unpack-commandlet
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
5e5f9f2
feat-2219: Add UnpackCommandlet for extracting already supported arch…
majesteSil e0c7464
feat-2298: Update CHANGELOG and Help files for UnpackCommandlet
majesteSil ed3d39f
Merge branch 'main' into feature/issue-2219-add-unpack-commandlet
maybeec 417b2cb
feat-2298: Add detailed help entries for unpack commandlet in Help.pr…
majesteSil 6c41ecc
Merge remote-tracking branch 'origin/feature/issue-2219-add-unpack-co…
majesteSil 6f0661e
fix: Correct issue in CHANGELOG and update unpack commandlet help ent…
majesteSil 17bcdfc
#2219: Correct issue in CHANGELOG and update unpack commandlet help e…
majesteSil 6568894
Merge remote-tracking branch 'origin/feature/issue-2219-add-unpack-co…
majesteSil 2ceeb42
#2219 test: Add end-to-end test for unpack commandlet with positional…
majesteSil a0fa1df
#2219 review: Update unpack commandlet help entries and remove issue …
majesteSil 052374a
Merge branch 'main' into feature/issue-2219-add-unpack-commandlet
majesteSil 4ea9af6
#2219 fix:redo CHANGELOG.adoc modification for release
majesteSil 00d4ba2
Merge branch 'main' into feature/issue-2219-add-unpack-commandlet
majesteSil File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
cli/src/main/java/com/devonfw/tools/ide/commandlet/UnpackCommandlet.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| package com.devonfw.tools.ide.commandlet; | ||
|
|
||
| import java.nio.file.Path; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import com.devonfw.tools.ide.cli.CliException; | ||
| import com.devonfw.tools.ide.context.IdeContext; | ||
| import com.devonfw.tools.ide.property.PathProperty; | ||
| import com.devonfw.tools.ide.util.FilenameUtil; | ||
|
|
||
| /** | ||
| * {@link Commandlet} to extract an archive file to a target directory. | ||
| * <p> | ||
| * Supports ZIP, TAR, TAR.GZ, TAR.BZ2, 7Z, JAR archives (cross-platform), as well as MSI (Windows) and DMG/PKG (Mac). | ||
| * </p> | ||
| */ | ||
| public final class UnpackCommandlet extends Commandlet { | ||
|
|
||
| private static final Logger LOG = LoggerFactory.getLogger(UnpackCommandlet.class); | ||
|
|
||
| /** The archive file to extract. */ | ||
| public final PathProperty archive; | ||
|
|
||
| /** The target directory to extract into. If not specified, defaults to {@code <cwd>/<archive_name_without_extension>}. */ | ||
| public final PathProperty target; | ||
|
|
||
| /** | ||
| * The constructor. | ||
| * | ||
| * @param context the {@link IdeContext}. | ||
| */ | ||
| public UnpackCommandlet(IdeContext context) { | ||
|
|
||
| super(context); | ||
| addKeyword(getName()); | ||
|
|
||
| this.archive = add(new PathProperty("", true, "archive", true)); | ||
| this.target = add(new PathProperty("", false, "target", false)); | ||
| } | ||
|
|
||
| @Override | ||
| public String getName() { | ||
|
|
||
| return "unpack"; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isIdeRootRequired() { | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isIdeHomeRequired() { | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isWriteLogFile() { | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| protected void doRun() { | ||
|
|
||
| Path cwd = this.context.getCwd(); | ||
| if (cwd == null) { | ||
| throw new CliException("Missing current working directory!"); | ||
| } | ||
|
|
||
| Path archivePath = this.archive.getValue(); | ||
| if (!archivePath.isAbsolute()) { | ||
| archivePath = cwd.resolve(archivePath).normalize(); | ||
| } | ||
|
|
||
| Path targetDir = this.target.getValue(); | ||
| if (targetDir == null) { | ||
| // Derive default target from archive filename without extension | ||
| String targetName = FilenameUtil.getFilenameWithoutExtension(archivePath); | ||
| targetDir = cwd.resolve(targetName); | ||
| } | ||
| if (!targetDir.isAbsolute()) { | ||
| targetDir = cwd.resolve(targetDir).normalize(); | ||
| } | ||
|
|
||
| LOG.info("Extracting {} to {}", archivePath, targetDir); | ||
| this.context.getFileAccess().extract(archivePath, targetDir); | ||
| LOG.info("Extraction completed successfully."); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 159 additions & 0 deletions
159
cli/src/test/java/com/devonfw/tools/ide/commandlet/UnpackCommandletTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| package com.devonfw.tools.ide.commandlet; | ||
|
|
||
| import java.io.IOException; | ||
| import java.nio.file.Path; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import com.devonfw.tools.ide.cli.CliArguments; | ||
| import com.devonfw.tools.ide.context.AbstractIdeContextTest; | ||
| import com.devonfw.tools.ide.context.IdeTestContext; | ||
|
|
||
| /** | ||
| * Test of {@link UnpackCommandlet}. | ||
| */ | ||
| class UnpackCommandletTest extends AbstractIdeContextTest { | ||
|
|
||
| /** Base filename of the test archive without extension. */ | ||
| private static final String TEST_ARCHIVE_BASENAME = "executable_and_non_executable"; | ||
|
|
||
| /** Path to the test archive directory. */ | ||
| private static final Path TEST_ARCHIVE_DIR = Path.of("src/test/resources/com/devonfw/tools/ide/io"); | ||
|
|
||
| /** Test ZIP archive. */ | ||
| private static final Path TEST_ARCHIVE_ZIP = TEST_ARCHIVE_DIR.resolve(TEST_ARCHIVE_BASENAME + ".zip"); | ||
|
|
||
| /** Test TAR.GZ archive. */ | ||
| private static final Path TEST_ARCHIVE_TAR_GZ = TEST_ARCHIVE_DIR.resolve(TEST_ARCHIVE_BASENAME + ".tar.gz"); | ||
|
|
||
| /** Test 7Z archive. */ | ||
| private static final Path TEST_ARCHIVE_7Z = TEST_ARCHIVE_DIR.resolve(TEST_ARCHIVE_BASENAME + ".7z"); | ||
|
|
||
| /** | ||
| * Tests extraction of a ZIP archive to the default target directory derived from the archive filename. | ||
| */ | ||
| @Test | ||
| void testUnpackZipWithDefaultTarget() throws IOException { | ||
|
|
||
| IdeTestContext context = newContext(PROJECT_BASIC); | ||
|
|
||
| Path archive = TEST_ARCHIVE_ZIP.toAbsolutePath(); | ||
| UnpackCommandlet cmd = new UnpackCommandlet(context); | ||
| cmd.archive.setValue(archive); | ||
|
|
||
| cmd.run(); | ||
|
|
||
| Path expectedTarget = context.getCwd().resolve(TEST_ARCHIVE_BASENAME); | ||
| assertThat(expectedTarget).isDirectory(); | ||
| assertThat(expectedTarget.resolve("executableFile.txt")).isRegularFile(); | ||
| assertThat(expectedTarget.resolve("nonExecutableFile.txt")).isRegularFile(); | ||
| } | ||
|
|
||
| /** | ||
| * Tests extraction of a ZIP archive to an explicit target directory via --target. | ||
| */ | ||
| @Test | ||
| void testUnpackZipWithExplicitTarget() throws IOException { | ||
|
|
||
| IdeTestContext context = newContext(PROJECT_BASIC); | ||
|
|
||
| Path testDir = context.getWorkspacePath().resolve("unpack-test"); | ||
| context.getFileAccess().mkdirs(testDir); | ||
| context.setCwd(testDir, context.getWorkspaceName(), context.getIdeHome()); | ||
|
|
||
| Path archive = TEST_ARCHIVE_ZIP.toAbsolutePath(); | ||
| Path target = testDir.resolve("my-extraction"); | ||
|
|
||
| UnpackCommandlet cmd = new UnpackCommandlet(context); | ||
| cmd.archive.setValue(archive); | ||
| cmd.target.setValue(target); | ||
|
|
||
| cmd.run(); | ||
|
|
||
| assertThat(target).isDirectory(); | ||
| assertThat(target.resolve("executableFile.txt")).isRegularFile(); | ||
| assertThat(target.resolve("nonExecutableFile.txt")).isRegularFile(); | ||
| } | ||
|
|
||
| /** | ||
| * Tests that extracting a non-existing archive fails with an appropriate error. | ||
| */ | ||
| @Test | ||
| void testUnpackNonExistingArchiveFails() { | ||
|
|
||
| IdeTestContext context = newContext(PROJECT_BASIC); | ||
|
|
||
| UnpackCommandlet cmd = new UnpackCommandlet(context); | ||
| cmd.archive.setValue(Path.of("does_not_exist.zip")); | ||
|
|
||
| assertThatExceptionOfType(IllegalStateException.class) | ||
| .isThrownBy(cmd::run) | ||
| .withMessageContaining("does_not_exist.zip") | ||
| .withMessageContaining("Failed to extract"); | ||
| } | ||
|
|
||
| /** | ||
| * Tests extraction of a tar.gz archive with default target directory. | ||
| */ | ||
| @Test | ||
| void testUnpackTarGzWithDefaultTarget() throws IOException { | ||
|
|
||
| IdeTestContext context = newContext(PROJECT_BASIC); | ||
|
|
||
| Path archive = TEST_ARCHIVE_TAR_GZ.toAbsolutePath(); | ||
| UnpackCommandlet cmd = new UnpackCommandlet(context); | ||
| cmd.archive.setValue(archive); | ||
|
|
||
| cmd.run(); | ||
|
|
||
| Path expectedTarget = context.getCwd().resolve(TEST_ARCHIVE_BASENAME); | ||
| assertThat(expectedTarget).isDirectory(); | ||
| } | ||
|
|
||
| /** | ||
| * Tests extraction of a 7z archive with default target directory. | ||
| */ | ||
| @Test | ||
| void testUnpack7zWithDefaultTarget() throws IOException { | ||
|
|
||
| IdeTestContext context = newContext(PROJECT_BASIC); | ||
|
|
||
| Path archive = TEST_ARCHIVE_7Z.toAbsolutePath(); | ||
| UnpackCommandlet cmd = new UnpackCommandlet(context); | ||
| cmd.archive.setValue(archive); | ||
|
|
||
| cmd.run(); | ||
|
|
||
| Path expectedTarget = context.getCwd().resolve(TEST_ARCHIVE_BASENAME); | ||
| assertThat(expectedTarget).isDirectory(); | ||
| } | ||
|
|
||
| /** | ||
| * End-to-end test that a positional {@code target} argument on the command line ({@code ide unpack <archive> <target>}) | ||
| * is bound to the target property and the archive is extracted into the given directory. | ||
| * <p> | ||
| * The {@code target} property has no long option (it is a positional value argument); this guards that the positional | ||
| * binding works end-to-end, which the property-level tests above (which set the property directly) cannot verify. | ||
| * </p> | ||
| */ | ||
| @Test | ||
| void testUnpackWithPositionalTarget() throws IOException { | ||
|
|
||
| IdeTestContext context = newContext(PROJECT_BASIC); | ||
|
|
||
| Path archive = TEST_ARCHIVE_ZIP.toAbsolutePath(); | ||
| Path target = context.getCwd().resolve("e2e-unpack-target"); | ||
| CliArguments args = new CliArguments("unpack", archive.toString(), target.toString()); | ||
| args.next(); | ||
|
|
||
| int exitCode = context.run(args); | ||
|
|
||
| assertThat(exitCode).isEqualTo(0); | ||
| assertThat(context).logAtError().hasNoMessageContaining("Unknown command"); | ||
| assertThat(context).logAtError().hasNoMessageContaining("Invalid option"); | ||
| assertThat(context).logAtError().hasNoMessageContaining("No matching property"); | ||
| assertThat(target).isDirectory(); | ||
| assertThat(target.resolve("executableFile.txt")).isRegularFile(); | ||
| assertThat(target.resolve("nonExecutableFile.txt")).isRegularFile(); | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.