diff --git a/initializr/src/main/groovy/io/spring/initializr/CommandLineHelpGenerator.groovy b/initializr/src/main/groovy/io/spring/initializr/CommandLineHelpGenerator.groovy index 8d06a941..c177bb39 100644 --- a/initializr/src/main/groovy/io/spring/initializr/CommandLineHelpGenerator.groovy +++ b/initializr/src/main/groovy/io/spring/initializr/CommandLineHelpGenerator.groovy @@ -16,6 +16,8 @@ package io.spring.initializr +import io.spring.initializr.support.VersionRange + import static io.spring.initializr.support.GroovyTemplate.template /** @@ -42,10 +44,9 @@ class CommandLineHelpGenerator { String generateGenericCapabilities(InitializrMetadata metadata, String serviceUrl) { def model = initializeModel(metadata, serviceUrl) model['hasExamples'] = false - doGenerateCapabilities(model) + template 'cli-capabilities.txt', model } - /** * Generate the capabilities of the service using "curl" as a plain text * document. @@ -54,7 +55,7 @@ class CommandLineHelpGenerator { def model = initializeModel(metadata, serviceUrl) model['examples'] = template 'curl-examples.txt', model model['hasExamples'] = true - doGenerateCapabilities(model) + template 'cli-capabilities.txt', model } /** @@ -65,43 +66,47 @@ class CommandLineHelpGenerator { def model = initializeModel(metadata, serviceUrl) model['examples'] = template 'httpie-examples.txt', model model['hasExamples'] = true - doGenerateCapabilities(model) - } - - private doGenerateCapabilities(def model) { template 'cli-capabilities.txt', model } + /** + * Generate the capabilities of the service using Spring Boot CLI as a plain + * text document. + */ + String generateSpringBootCliCapabilities(InitializrMetadata metadata, String serviceUrl) { + def model = initializeModel(metadata, serviceUrl) + model['hasExamples'] = false + template('boot-cli-capabilities.txt', model) + } private Map initializeModel(InitializrMetadata metadata, serviceUrl) { Map model = [:] model['logo'] = logo model['serviceUrl'] = serviceUrl - Map dependencies = [:] - new ArrayList(metadata.allDependencies).sort { a, b -> a.id <=> b.id }.each { - String description = it.name - if (it.description) { - description += ": $it.description" - } - if (it.versionRange) { - String range = it.versionRange.trim() - description += " - $range" - } - dependencies[it.id] = description + String[][] dependencyTable = new String[metadata.allDependencies.size() + 1][]; + dependencyTable[0] = ["Id", "Description", "Required version"] + new ArrayList(metadata.allDependencies).sort { a, b -> a.id <=> b.id } + .eachWithIndex { dep, i -> + String[] data = new String[3] + data[0] = dep.id + data[1] = dep.description ?: dep.name + data[2] = buildVersionRangeRepresentation(dep.versionRange) + dependencyTable[i + 1] = data } - model['dependencies'] = dependencies + model['dependencies'] = TableGenerator.generate(dependencyTable) - Map types = [:] - new ArrayList<>(metadata.types).sort { a, b -> a.id <=> b.id }.each { - String description = it.description - if (!description) { - description = it.name - } - types[it.id] = description + String[][] typeTable = new String[metadata.types.size() + 1][]; + typeTable[0] = ["Id", "Description", "Tags"] + new ArrayList<>(metadata.types).sort { a, b -> a.id <=> b.id }.eachWithIndex { type, i -> + String[] data = new String[3] + data[0] = (metadata.defaults.type.equals(type.id) ? type.id + " *" : type.id) + data[1] = type.description ?: type.name + data[2] = buildTagRepresentation(type) + typeTable[i + 1] = data; } - model['types'] = types + model['types'] = TableGenerator.generate(typeTable) Map defaults = [:] metadata.defaults.properties.sort().each { @@ -109,11 +114,117 @@ class CommandLineHelpGenerator { defaults[it.key] = it.value } } + String[][] parameterTable = new String[defaults.size() + 1][]; + parameterTable[0] = ["Id", "Default value"] + defaults.keySet().eachWithIndex { id, i -> + String[] data = new String[2] + data[0] = id + data[1] = metadata.defaults.properties[id] + parameterTable[i + 1] = data + } + model['parameters'] = TableGenerator.generate(parameterTable) + + defaults['applicationName'] = ProjectRequest.generateApplicationName(metadata.defaults.name, ProjectRequest.DEFAULT_APPLICATION_NAME) model['defaults'] = defaults + model } + private static String buildVersionRangeRepresentation(String range) { + if (!range) { + return null + } + VersionRange versionRange = VersionRange.parse(range) + if (versionRange.higherVersion == null) { + return ">= $range" + } else { + return range.trim() + } + } + + private static String buildTagRepresentation(InitializrMetadata.Type type) { + if (type.tags.isEmpty()) { + return ""; + } + type.tags.collect { key, value -> + "$key:$value" + }.join(",") + } + + private static class TableGenerator { + + static final String NEW_LINE = System.getProperty("line.separator") + + /** + * Generate a table description for the specified {@code content}. + *
+ * The {@code content} is a two-dimensional array holding the rows
+ * of the table. The first entry holds the header of the table.
+ */
+ public static String generate(String[][] content) {
+ StringBuilder sb = new StringBuilder()
+ int[] columnsLength = computeColumnsLength(content)
+ appendTableSeparation(sb, columnsLength)
+ appendRow(sb, content, columnsLength, 0) // Headers
+ appendTableSeparation(sb, columnsLength)
+ for (int i = 1; i < content.length; i++) {
+ appendRow(sb, content, columnsLength, i)
+ }
+ appendTableSeparation(sb, columnsLength)
+ sb.toString()
+ }
+
+
+ private static void appendRow(StringBuilder sb, String[][] content,
+ int[] columnsLength, int rowIndex) {
+ String[] row = content[rowIndex]
+ for (int i = 0; i < row.length; i++) {
+ sb.append("| ").append(fill(row[i], columnsLength[i])).append(" ")
+ }
+ sb.append("|")
+ sb.append(NEW_LINE)
+ }
+
+ private static void appendTableSeparation(StringBuilder sb, int[] headersLength) {
+ for (int headerLength : headersLength) {
+ sb.append("+").append("-".multiply(headerLength + 2))
+ }
+ sb.append("+")
+ sb.append(NEW_LINE)
+ }
+
+ private static String fill(String data, int columnSize) {
+ if (data == null) {
+ return " ".multiply(columnSize)
+ } else {
+ int i = columnSize - data.length()
+ return data + " ".multiply(i)
+ }
+ }
+
+ private static int[] computeColumnsLength(String[][] content) {
+ int count = content[0].length
+ int[] result = new int[count]
+ for (int i = 0; i < count; i++) {
+ result[i] = largest(content, i)
+ }
+ return result
+ }
+
+ private static int largest(String[][] content, int column) {
+ int max = 0
+ for (String[] rows : content) {
+ String s = rows[column]
+ if (s && s.length() > max) {
+ max = s.length()
+ }
+ }
+ return max
+ }
+
+ }
+
}
diff --git a/initializr/src/main/groovy/io/spring/initializr/InitializrMetadata.groovy b/initializr/src/main/groovy/io/spring/initializr/InitializrMetadata.groovy
index 1101dcb4..af7cedcc 100644
--- a/initializr/src/main/groovy/io/spring/initializr/InitializrMetadata.groovy
+++ b/initializr/src/main/groovy/io/spring/initializr/InitializrMetadata.groovy
@@ -248,6 +248,10 @@ class InitializrMetadata {
String versionRange
+ void setVersionRange(String versionRange) {
+ this.versionRange = versionRange ? versionRange.trim() : null
+ }
+
/**
* Specify if the dependency has its coordinates set, i.e. {@code groupId}
* and {@code artifactId}.
diff --git a/initializr/src/main/groovy/io/spring/initializr/support/VersionRange.groovy b/initializr/src/main/groovy/io/spring/initializr/support/VersionRange.groovy
index de5a4097..546c3b67 100644
--- a/initializr/src/main/groovy/io/spring/initializr/support/VersionRange.groovy
+++ b/initializr/src/main/groovy/io/spring/initializr/support/VersionRange.groovy
@@ -44,12 +44,19 @@ class VersionRange {
private static final String RANGE_REGEX = "(\\(|\\[)(.*),(.*)(\\)|\\])"
- private Version lowerVersion
- private boolean lowerInclusive
- private Version higherVersion
- private boolean higherInclusive
+ final Version lowerVersion
+ final boolean lowerInclusive
+ final Version higherVersion
+ final boolean higherInclusive
- /**
+ private VersionRange(Version lowerVersion, boolean lowerInclusive,
+ Version higherVersion, boolean higherInclusive) {
+ this.lowerVersion = lowerVersion
+ this.lowerInclusive = lowerInclusive
+ this.higherVersion = higherVersion
+ this.higherInclusive = higherInclusive
+ }
+/**
* Specify if the {@link Version} matches this range. Returns {@code true}
* if the version is contained within this range, {@code false} otherwise.
*/
@@ -85,14 +92,13 @@ class VersionRange {
if (!matcher.matches()) {
// Try to read it as simple string
Version version = Version.parse(text)
- return new VersionRange(lowerInclusive: true, lowerVersion: version)
+ return new VersionRange(version, true, null, true)
}
- VersionRange range = new VersionRange()
- range.lowerInclusive = matcher[0][1].equals('[')
- range.lowerVersion = Version.parse(matcher[0][2])
- range.higherVersion = Version.parse(matcher[0][3])
- range.higherInclusive = matcher[0][4].equals(']')
- range
+ boolean lowerInclusive = matcher[0][1].equals('[')
+ Version lowerVersion = Version.parse(matcher[0][2])
+ Version higherVersion = Version.parse(matcher[0][3])
+ boolean higherInclusive = matcher[0][4].equals(']')
+ new VersionRange(lowerVersion, lowerInclusive, higherVersion, higherInclusive)
}
}
diff --git a/initializr/src/main/groovy/io/spring/initializr/web/MainController.groovy b/initializr/src/main/groovy/io/spring/initializr/web/MainController.groovy
index 37b42a33..32eda51b 100644
--- a/initializr/src/main/groovy/io/spring/initializr/web/MainController.groovy
+++ b/initializr/src/main/groovy/io/spring/initializr/web/MainController.groovy
@@ -76,6 +76,9 @@ class MainController extends AbstractInitializrController {
if (userAgent.startsWith(WebConfig.HTTPIE_USER_AGENT_PREFIX)) {
return builder.body(commandLineHelpGenerator.generateHttpieCapabilities(metadata, appUrl))
}
+ if (userAgent.startsWith(WebConfig.SPRING_BOOT_CLI_AGENT_PREFIX)) {
+ return builder.body(commandLineHelpGenerator.generateSpringBootCliCapabilities(metadata, appUrl))
+ }
}
builder.body(commandLineHelpGenerator.generateGenericCapabilities(metadata, appUrl))
}
diff --git a/initializr/src/main/groovy/io/spring/initializr/web/WebConfig.groovy b/initializr/src/main/groovy/io/spring/initializr/web/WebConfig.groovy
index e9d6aec4..3379958a 100644
--- a/initializr/src/main/groovy/io/spring/initializr/web/WebConfig.groovy
+++ b/initializr/src/main/groovy/io/spring/initializr/web/WebConfig.groovy
@@ -39,6 +39,8 @@ class WebConfig extends WebMvcConfigurerAdapter {
static final String HTTPIE_USER_AGENT_PREFIX = 'HTTPie'
+ static final String SPRING_BOOT_CLI_AGENT_PREFIX = 'SpringBootCli'
+
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentTypeStrategy(new CommandLineContentNegotiationStrategy())
diff --git a/initializr/src/main/resources/templates/boot-cli-capabilities.txt b/initializr/src/main/resources/templates/boot-cli-capabilities.txt
new file mode 100644
index 00000000..2cb5b791
--- /dev/null
+++ b/initializr/src/main/resources/templates/boot-cli-capabilities.txt
@@ -0,0 +1,11 @@
+${logo}
+:: Service capabilities :: ${serviceUrl}
+
+Supported dependencies
+${dependencies}
+
+Project types (* denotes the default)
+${types}
+
+Parameters
+${parameters}
diff --git a/initializr/src/main/resources/templates/cli-capabilities.txt b/initializr/src/main/resources/templates/cli-capabilities.txt
index 967e0f16..fd05456f 100644
--- a/initializr/src/main/resources/templates/cli-capabilities.txt
+++ b/initializr/src/main/resources/templates/cli-capabilities.txt
@@ -9,15 +9,13 @@ The services uses a HAL based hypermedia format to expose a set of
resources to interact with. If you access this root resource
requesting application/json as media type the response will contain
the following links:
-<% types.each {key, value -> %>
-* ${key} - ${value} <% } %>
+${types}
The URI templates take a set of parameters to customize the result of
a request to the linked resource.
-
+-----------------+------------------------------------------+-----------------
| Parameter | Description | Default
-|-----------------+------------------------------------------+-----------------
++-----------------+------------------------------------------+-----------------
| groupId | project coordinates | ${defaults.groupId}
| artifactId | project coordinates (infer archive name) | ${defaults.artifactId}
| version | project version | ${defaults.version}
@@ -36,13 +34,9 @@ a request to the linked resource.
The following section has a list of supported identifiers for the comma separated
list of "dependencies".
-<% dependencies.each {key, value -> %>
-${key} - ${value} <% } %>
-
-
+${dependencies}
<% if (hasExamples) { %>
Examples:
${examples}
-
<% } %>
diff --git a/initializr/src/test/groovy/io/spring/initializr/CommandLineHelpGeneratorTest.groovy b/initializr/src/test/groovy/io/spring/initializr/CommandLineHelpGeneratorTest.groovy
index 560b2917..da41963d 100644
--- a/initializr/src/test/groovy/io/spring/initializr/CommandLineHelpGeneratorTest.groovy
+++ b/initializr/src/test/groovy/io/spring/initializr/CommandLineHelpGeneratorTest.groovy
@@ -36,8 +36,8 @@ class CommandLineHelpGeneratorTest {
createDependency('id-b', 'depB'),
createDependency('id-a', 'depA', 'and some description')).validateAndGet()
String content = generator.generateGenericCapabilities(metadata, "https://fake-service")
- assertThat content, containsString('id-a - depA: and some description')
- assertThat content, containsString('id-b - depB')
+ assertThat content, containsString('id-a | and some description |')
+ assertThat content, containsString('id-b | depB')
assertThat content, containsString("https://fake-service")
assertThat content, not(containsString('Examples:'))
assertThat content, not(containsString('curl'))
@@ -49,7 +49,9 @@ class CommandLineHelpGeneratorTest {
.addType(new InitializrMetadata.Type(id: 'foo', name: 'foo-name', description: 'foo-desc'))
.validateAndGet()
String content = generator.generateGenericCapabilities(metadata, "https://fake-service")
- assertThat content, containsString('foo - foo-desc')
+ assertThat content, containsString('| foo')
+ assertThat content, containsString('| foo-desc')
+
}
@Test
@@ -58,8 +60,8 @@ class CommandLineHelpGeneratorTest {
createDependency('id-b', 'depB'),
createDependency('id-a', 'depA', 'and some description')).validateAndGet()
String content = generator.generateCurlCapabilities(metadata, "https://fake-service")
- assertThat content, containsString('id-a - depA: and some description')
- assertThat content, containsString('id-b - depB')
+ assertThat content, containsString('id-a | and some description |')
+ assertThat content, containsString('id-b | depB')
assertThat content, containsString("https://fake-service")
assertThat content, containsString('Examples:')
assertThat content, containsString('curl')
@@ -71,14 +73,40 @@ class CommandLineHelpGeneratorTest {
createDependency('id-b', 'depB'),
createDependency('id-a', 'depA', 'and some description')).validateAndGet()
String content = generator.generateHttpieCapabilities(metadata, "https://fake-service")
- assertThat content, containsString('id-a - depA: and some description')
- assertThat content, containsString('id-b - depB')
+ assertThat content, containsString('id-a | and some description |')
+ assertThat content, containsString('id-b | depB')
assertThat content, containsString("https://fake-service")
assertThat content, containsString('Examples:')
assertThat content, not(containsString('curl'))
assertThat content, containsString("http https://fake-service")
}
+ @Test
+ void generateSpringBootCliCapabilities() {
+ def metadata = InitializrMetadataBuilder.withDefaults().addDependencyGroup("test",
+ createDependency('id-b', 'depB'),
+ createDependency('id-a', 'depA', 'and some description')).validateAndGet()
+ String content = generator.generateSpringBootCliCapabilities(metadata, "https://fake-service")
+ assertThat content, containsString('id-a | and some description |')
+ assertThat content, containsString('id-b | depB')
+ assertThat content, containsString("https://fake-service")
+ assertThat content, not(containsString('Examples:'))
+ assertThat content, not(containsString('curl'))
+ }
+
+ @Test
+ void generateCapabilitiesWithVersionRange() {
+ InitializrMetadata.Dependency first = new InitializrMetadata.Dependency(
+ id: 'first', description: 'first desc', versionRange: '1.2.0.RELEASE')
+ InitializrMetadata.Dependency second = new InitializrMetadata.Dependency(
+ id: 'second', description: 'second desc', versionRange: ' [1.2.0.RELEASE,1.3.0.M1) ')
+ def metadata = InitializrMetadataBuilder.withDefaults().addDependencyGroup("test", first, second).validateAndGet()
+ String content = generator.generateSpringBootCliCapabilities(metadata, "https://fake-service")
+ assertThat content, containsString('| first | first desc | >= 1.2.0.RELEASE |')
+ assertThat content, containsString('| second | second desc | [1.2.0.RELEASE,1.3.0.M1) |')
+
+ }
+
private static def createDependency(String id, String name) {
createDependency(id, name, null)
}
diff --git a/initializr/src/test/groovy/io/spring/initializr/web/MainControllerIntegrationTests.groovy b/initializr/src/test/groovy/io/spring/initializr/web/MainControllerIntegrationTests.groovy
index 14ca28dd..cc4597ab 100644
--- a/initializr/src/test/groovy/io/spring/initializr/web/MainControllerIntegrationTests.groovy
+++ b/initializr/src/test/groovy/io/spring/initializr/web/MainControllerIntegrationTests.groovy
@@ -252,6 +252,19 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
validateGenericHelpContent(response)
}
+ @Test
+ void springBootCliReceivesJsonByDefault() {
+ ResponseEntity