Init command takes the output as last argument

This commit moves the --output switch to a regular argument. This aligns
to other command, i.e. spring init my-project.zip would save the project
to "my-project.zip" in the current directory.

This commit also auto-detects the --extract option if the location ends
with a slash, i.e. spring init demo/ would extract the content of the
project in a demo directory that is local to the current directory.

Fixes gh-1802
This commit is contained in:
Stephane Nicoll 2014-11-03 11:52:00 +01:00
parent 96198e2999
commit 2e07f003c9
4 changed files with 84 additions and 30 deletions

View File

@ -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<Void> force;
private OptionSpec<String> 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<Object>(
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;
}

View File

@ -40,6 +40,8 @@ class ProjectGenerationRequest {
private String output;
private boolean extract;
private String bootVersion;
private List<String> dependencies = new ArrayList<String>();
@ -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;
}
/**

View File

@ -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 "

View File

@ -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();