diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommand.java index add75306fd..4f4ca9e175 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitCommand.java @@ -17,7 +17,9 @@ package org.springframework.boot.cli.command.init; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import joptsimple.OptionSet; import joptsimple.OptionSpec; @@ -27,6 +29,7 @@ import org.springframework.boot.cli.command.OptionParsingCommand; import org.springframework.boot.cli.command.options.OptionHandler; import org.springframework.boot.cli.command.status.ExitStatus; import org.springframework.boot.cli.util.Log; +import org.springframework.util.Assert; /** * {@link Command} that initializes a project using Spring initializr. @@ -45,6 +48,11 @@ public class InitCommand extends OptionParsingCommand { + "Initialzr (start.spring.io)", handler); } + @Override + public String getUsageHelp() { + return "[options] [location]"; + } + static class InitOptionHandler extends OptionHandler { private final ServiceCapabilitiesReportGenerator serviceCapabilitiesReport; @@ -73,8 +81,6 @@ public class InitCommand extends OptionParsingCommand { private OptionSpec force; - private OptionSpec output; - InitOptionHandler(InitializrService initializrService) { this.serviceCapabilitiesReport = new ServiceCapabilitiesReportGenerator( initializrService); @@ -123,14 +129,9 @@ public class InitCommand extends OptionParsingCommand { private void otherOptions() { this.extract = option(Arrays.asList("extract", "x"), - "Extract the project archive"); + "Extract the project archive. Inferred if a location is specified and ends with /"); this.force = option(Arrays.asList("force", "f"), "Force overwrite of existing files"); - this.output = option( - Arrays.asList("output", "o"), - "Location of the generated project. Can be an absolute or a " - + "relative reference and should refer to a directory when " - + "--extract is used").withRequiredArg(); } @Override @@ -160,12 +161,17 @@ public class InitCommand extends OptionParsingCommand { protected void generateProject(OptionSet options) throws IOException { ProjectGenerationRequest request = createProjectGenerationRequest(options); - this.projectGenerator.generateProject(request, options.has(this.force), - options.has(this.extract), options.valueOf(this.output)); + this.projectGenerator.generateProject(request, options.has(this.force)); } protected ProjectGenerationRequest createProjectGenerationRequest( OptionSet options) { + + List nonOptionArguments = new ArrayList( + options.nonOptionArguments()); + Assert.isTrue(nonOptionArguments.size() <= 1, + "Only the target location may be specified"); + ProjectGenerationRequest request = new ProjectGenerationRequest(); request.setServiceUrl(options.valueOf(this.target)); if (options.has(this.bootVersion)) { @@ -188,8 +194,10 @@ public class InitCommand extends OptionParsingCommand { if (options.has(this.type)) { request.setType(options.valueOf(this.type)); } - if (options.has(this.output)) { - request.setOutput(options.valueOf(this.output)); + request.setExtract(options.has(this.extract)); + if (nonOptionArguments.size() == 1) { + String output = (String) nonOptionArguments.get(0); + request.setOutput(output); } return request; } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationRequest.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationRequest.java index 1a61d0af1c..b2d7cfaf26 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationRequest.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerationRequest.java @@ -40,6 +40,8 @@ class ProjectGenerationRequest { private String output; + private boolean extract; + private String bootVersion; private List dependencies = new ArrayList(); @@ -76,7 +78,25 @@ class ProjectGenerationRequest { } public void setOutput(String output) { - this.output = output; + if (output != null && output.endsWith("/")) { + this.output = output.substring(0, output.length() - 1); + this.extract = true; + } else { + this.output = output; + } + } + + /** + * Specify if the project archive should be extract in the output location. If + * the {@link #getOutput() output} ends with "/", the project is extracted + * automatically. + */ + public boolean isExtract() { + return extract; + } + + public void setExtract(boolean extract) { + this.extract = extract; } /** diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerator.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerator.java index 9fa35b9717..fb2259b35c 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerator.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/ProjectGenerator.java @@ -41,24 +41,23 @@ public class ProjectGenerator { this.initializrService = initializrService; } - public void generateProject(ProjectGenerationRequest request, boolean force, - boolean extract, String output) throws IOException { + public void generateProject(ProjectGenerationRequest request, boolean force) throws IOException { ProjectGenerationResponse response = this.initializrService.generate(request); - if (extract) { + String fileName = (request.getOutput() != null ? request.getOutput() : response.getFileName()); + if (request.isExtract()) { if (isZipArchive(response)) { - extractProject(response, output, force); + extractProject(response, request.getOutput(), force); return; } else { Log.info("Could not extract '" + response.getContentType() + "'"); + fileName = response.getFileName(); // Use value from the server since we can't extract it } } - String fileName = response.getFileName(); - fileName = (fileName != null ? fileName : output); if (fileName == null) { throw new ReportableException( "Could not save the project, the server did not set a preferred " - + "file name. Use --output to specify the output location " + + "file name and no location was set. Specify the output location " + "for the project."); } writeProject(response, fileName, force); @@ -102,7 +101,7 @@ public class ProjectGenerator { throw new ReportableException(file.isDirectory() ? "Directory" : "File" + " '" + file.getName() + "' already exists. Use --force if you want to overwrite or " - + "--output to specify an alternate location."); + + "specify an alternate location."); } if (!entry.isDirectory()) { FileCopyUtils.copy(StreamUtils.nonClosing(zipStream), @@ -123,7 +122,7 @@ public class ProjectGenerator { if (!overwrite) { throw new ReportableException("File '" + outputFile.getName() + "' already exists. Use --force if you want to " - + "overwrite or --output to specify an alternate location."); + + "overwrite or specify an alternate location."); } if (!outputFile.delete()) { throw new ReportableException("Failed to delete existing file " diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitCommandTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitCommandTests.java index 75dc737d86..d4d9fd6c18 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitCommandTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitCommandTests.java @@ -95,7 +95,21 @@ public class InitCommandTests extends AbstractHttpClientMockTests { "application/zip", "demo.zip", archive); mockSuccessfulProjectGeneration(request); assertEquals(ExitStatus.OK, - this.command.run("--extract", "--output=" + folder.getAbsolutePath())); + this.command.run("--extract", folder.getAbsolutePath())); + File archiveFile = new File(folder, "test.txt"); + assertTrue("Archive not extracted properly " + folder.getAbsolutePath() + + " not found", archiveFile.exists()); + } + + @Test + public void generateProjectAndExtractWithConvention() throws Exception { + File folder = this.temporaryFolder.newFolder(); + byte[] archive = createFakeZipArchive("test.txt", "Fake content"); + MockHttpProjectGenerationRequest request = new MockHttpProjectGenerationRequest( + "application/zip", "demo.zip", archive); + mockSuccessfulProjectGeneration(request); + assertEquals(ExitStatus.OK, + this.command.run(folder.getAbsolutePath()+ "/")); File archiveFile = new File(folder, "test.txt"); assertTrue("Archive not extracted properly " + folder.getAbsolutePath() + " not found", archiveFile.exists()); @@ -113,7 +127,7 @@ public class InitCommandTests extends AbstractHttpClientMockTests { "application/foobar", fileName, archive); mockSuccessfulProjectGeneration(request); assertEquals(ExitStatus.OK, - this.command.run("--extract", "--output=" + folder.getAbsolutePath())); + this.command.run("--extract", folder.getAbsolutePath())); assertTrue("file should have been saved instead", file.exists()); } finally { @@ -133,7 +147,7 @@ public class InitCommandTests extends AbstractHttpClientMockTests { null, fileName, archive); mockSuccessfulProjectGeneration(request); assertEquals(ExitStatus.OK, - this.command.run("--extract", "--output=" + folder.getAbsolutePath())); + this.command.run("--extract", folder.getAbsolutePath())); assertTrue("file should have been saved instead", file.exists()); } finally { @@ -175,7 +189,7 @@ public class InitCommandTests extends AbstractHttpClientMockTests { "application/zip", "demo.zip", archive); mockSuccessfulProjectGeneration(request); assertEquals(ExitStatus.ERROR, - this.command.run("--extract", "--output=" + folder.getAbsolutePath())); + this.command.run("--extract", folder.getAbsolutePath())); assertEquals("File should not have changed", fileLength, conflict.length()); } @@ -192,8 +206,7 @@ public class InitCommandTests extends AbstractHttpClientMockTests { mockSuccessfulProjectGeneration(request); assertEquals( ExitStatus.OK, - this.command.run("--force", "--extract", - "--output=" + folder.getAbsolutePath())); + this.command.run("--force", "--extract", folder.getAbsolutePath())); assertTrue("File should have changed", fileLength != conflict.length()); } @@ -245,12 +258,26 @@ public class InitCommandTests extends AbstractHttpClientMockTests { } @Test - public void parseOutput() throws Exception { + public void parseLocation() throws Exception { this.handler.disableProjectGeneration(); - this.command.run("--output=foobar.zip"); + this.command.run("foobar.zip"); assertEquals("foobar.zip", this.handler.lastRequest.getOutput()); } + @Test + public void parseLocationWithSlash() throws Exception { + this.handler.disableProjectGeneration(); + this.command.run("foobar/"); + assertEquals("foobar", this.handler.lastRequest.getOutput()); + assertTrue(this.handler.lastRequest.isExtract()); + } + + @Test + public void parseMoreThanOneArg() throws Exception { + this.handler.disableProjectGeneration(); + assertEquals(ExitStatus.ERROR, this.command.run("foobar", "barfoo")); + } + private byte[] createFakeZipArchive(String fileName, String content) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream();