Better describe service capability

Make sure that the title and description of each capability is
configurable and expose that information in the meta-data.

This typically allows to customize the header of each capability in the
UI and the description in help usage such as the optimized text output
for command line clients.

See gh-89
This commit is contained in:
Stephane Nicoll
2015-03-04 11:59:36 +01:00
parent 67c4ef26c9
commit 526a2e7b59
13 changed files with 164 additions and 77 deletions

View File

@@ -93,7 +93,7 @@ class CommandLineHelpGenerator {
defaults['baseDir'] = 'no base dir'
defaults['dependencies'] = 'none'
Map parametersDescription = buildParametersDescription()
Map parametersDescription = buildParametersDescription(metadata)
String[][] parameterTable = new String[defaults.size() + 1][];
parameterTable[0] = ["Parameter", "Description", "Default value"]
defaults.sort().keySet().eachWithIndex { id, i ->
@@ -117,7 +117,7 @@ class CommandLineHelpGenerator {
Map defaults = metadata.defaults()
Map parametersDescription = buildParametersDescription()
Map parametersDescription = buildParametersDescription(metadata)
String[][] parameterTable = new String[defaults.size() + 1][];
parameterTable[0] = ["Id", "Description", "Default value"]
defaults.keySet().eachWithIndex { id, i ->
@@ -165,24 +165,16 @@ class CommandLineHelpGenerator {
TableGenerator.generate(typeTable)
}
protected Map buildParametersDescription() {
protected Map buildParametersDescription(InitializrMetadata metadata) {
Map result = [:]
result['groupId'] = 'project coordinates'
result['artifactId'] = 'project coordinates (infer archive name)'
result['version'] = 'project version'
result['name'] = 'project name (infer application name)'
result['description'] = 'project description'
result['packageName'] = 'root package'
metadata.properties.each { key, value ->
if (value.hasProperty('description') && value.hasProperty('id')) {
result[value.id] = value['description']
}
}
result['applicationName'] = 'application name'
result['dependencies'] = 'dependency identifiers (comma separated)'
result['type'] = 'project type'
result['packaging'] = 'project packaging'
result['language'] = 'programming language'
result['javaVersion'] = 'language level'
result['bootVersion'] = 'spring boot version'
result['baseDir'] = 'base directory to create in the archive'
result
}
private static String buildVersionRangeRepresentation(String range) {

View File

@@ -30,7 +30,8 @@ class DependenciesCapability extends ServiceCapability<List<DependencyGroup>> {
private final Map<String, Dependency> indexedDependencies = [:]
DependenciesCapability() {
super('dependencies', ServiceCapabilityType.HIERARCHICAL_MULTI_SELECT)
super('dependencies', ServiceCapabilityType.HIERARCHICAL_MULTI_SELECT,
'Project dependencies', 'dependency identifiers (comma-separated)')
}
/**

View File

@@ -30,23 +30,27 @@ class InitializrMetadata {
final TypeCapability types = new TypeCapability()
final SingleSelectCapability bootVersions = new SingleSelectCapability('bootVersion')
final SingleSelectCapability bootVersions =
new SingleSelectCapability('bootVersion', 'Spring Boot Version', 'spring boot version')
final SingleSelectCapability packagings = new SingleSelectCapability('packaging')
final SingleSelectCapability packagings =
new SingleSelectCapability('packaging', 'Packaging', 'project packaging')
final SingleSelectCapability javaVersions = new SingleSelectCapability('javaVersion')
final SingleSelectCapability javaVersions =
new SingleSelectCapability('javaVersion', 'Java Version', 'language level')
final SingleSelectCapability languages = new SingleSelectCapability('language')
final SingleSelectCapability languages =
new SingleSelectCapability('language', 'Language', 'programming language')
final TextCapability name = new TextCapability('name')
final TextCapability name = new TextCapability('name', 'Name', 'project name (infer application name)')
final TextCapability description = new TextCapability('description')
final TextCapability description = new TextCapability('description', 'Description', 'project description' )
final TextCapability groupId = new TextCapability('groupId')
final TextCapability groupId = new TextCapability('groupId', 'Group', 'project coordinates')
final TextCapability artifactId = new ArtifactIdCapability(name)
final TextCapability version = new TextCapability('version')
final TextCapability version = new TextCapability('version', 'Version', 'project version')
final TextCapability packageName = new PackageCapability(name)
@@ -130,7 +134,7 @@ class InitializrMetadata {
private final TextCapability nameCapability
ArtifactIdCapability(TextCapability nameCapability) {
super('artifactId')
super('artifactId', 'Artifact', 'project coordinates (infer archive name)')
this.nameCapability = nameCapability
}
@@ -145,7 +149,7 @@ class InitializrMetadata {
private final TextCapability nameCapability
PackageCapability(TextCapability nameCapability) {
super('packageName')
super('packageName', 'Package Name', 'root package')
this.nameCapability = nameCapability
}

View File

@@ -152,12 +152,12 @@ class InitializrMetadataBuilder {
metadata.packagings.merge(properties.packagings)
metadata.javaVersions.merge(properties.javaVersions)
metadata.languages.merge(properties.languages)
metadata.groupId.merge(properties.defaults.groupId)
metadata.artifactId.merge(properties.defaults.artifactId)
metadata.version.merge(properties.defaults.version)
metadata.name.merge(properties.defaults.name)
metadata.description.merge(properties.defaults.description)
metadata.packageName.merge(properties.defaults.packageName)
properties.groupId.apply(metadata.groupId)
properties.artifactId.apply(metadata.artifactId)
properties.version.apply(metadata.version)
properties.name.apply(metadata.name)
properties.description.apply(metadata.description)
properties.packageName.apply(metadata.packageName)
}
}

View File

@@ -16,7 +16,7 @@
package io.spring.initializr.metadata
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonIgnore
import org.springframework.boot.context.properties.ConfigurationProperties
@@ -27,32 +27,61 @@ import org.springframework.boot.context.properties.ConfigurationProperties
* @since 1.0
*/
@ConfigurationProperties(prefix = 'initializr', ignoreUnknownFields = false)
@JsonIgnoreProperties(["dependencies", "types", "packagings", "javaVersions", "languages", "bootVersions", "defaults"])
class InitializrProperties extends InitializrConfiguration {
@JsonIgnore
final List<DependencyGroup> dependencies = []
@JsonIgnore
final List<Type> types = []
@JsonIgnore
final List<DefaultMetadataElement> packagings = []
@JsonIgnore
final List<DefaultMetadataElement> javaVersions = []
@JsonIgnore
final List<DefaultMetadataElement> languages = []
@JsonIgnore
final List<DefaultMetadataElement> bootVersions = []
final Defaults defaults = new Defaults()
@JsonIgnore
final SimpleElement groupId = new SimpleElement(value: 'org.test')
static class Defaults {
@JsonIgnore
final SimpleElement artifactId = new SimpleElement()
String groupId = 'org.test'
String artifactId
String version = '0.0.1-SNAPSHOT'
String name = 'demo'
String description = 'Demo project for Spring Boot'
String packageName
@JsonIgnore
final SimpleElement version = new SimpleElement(value: '0.0.1-SNAPSHOT')
@JsonIgnore
final SimpleElement name = new SimpleElement(value: 'demo')
@JsonIgnore
final SimpleElement description = new SimpleElement(value: 'Demo project for Spring Boot')
@JsonIgnore
final SimpleElement packageName = new SimpleElement()
static class SimpleElement {
String title
String description
String value
void apply(TextCapability capability) {
if (title) {
capability.title = title
}
if (description) {
capability.description = description
}
if (value) {
capability.content = value
}
}
}
}

View File

@@ -36,6 +36,14 @@ abstract class ServiceCapability<T> {
final ServiceCapabilityType type
/**
* A title of the capability, used as a header text or label.
*/
String title
/**
* A description of the capability, used in help usage or UI tooltips.
*/
String description
protected ServiceCapability(String id, ServiceCapabilityType type) {
@@ -43,6 +51,13 @@ abstract class ServiceCapability<T> {
this.type = type
}
protected ServiceCapability(String id, ServiceCapabilityType type, String title, String description) {
this.id = id
this.type = type
this.title = title
this.description = description
}
/**
* Return the "content" of this capability. The structure of the content
* vastly depends on the {@link ServiceCapability type} of the capability.
@@ -64,6 +79,9 @@ abstract class ServiceCapability<T> {
Assert.notNull(other, "Other must not be null")
Assert.state(this.id.equals(other.id))
Assert.state(this.type.equals(other.type))
if (other.title) {
this.title = other.title
}
if (other.description) {
this.description = other.description
}

View File

@@ -31,7 +31,11 @@ class SingleSelectCapability extends ServiceCapability<List<DefaultMetadataEleme
@JsonCreator
SingleSelectCapability(@JsonProperty("id") String id) {
super(id, ServiceCapabilityType.SINGLE_SELECT)
this(id, null, null)
}
SingleSelectCapability(String id, String title, String description) {
super(id, ServiceCapabilityType.SINGLE_SELECT, title, description)
}
/**

View File

@@ -31,12 +31,11 @@ class TextCapability extends ServiceCapability<String> {
@JsonCreator
TextCapability(@JsonProperty("id") String id) {
super(id, ServiceCapabilityType.TEXT);
this(id, null, null)
}
TextCapability(String id, String content) {
this(id)
this.content = content
TextCapability(String id, String title, String description) {
super(id, ServiceCapabilityType.TEXT, title, description)
}
@Override

View File

@@ -27,7 +27,7 @@ class TypeCapability extends ServiceCapability<List<Type>> {
final List<Type> content = []
TypeCapability() {
super('type', ServiceCapabilityType.ACTION)
super('type', ServiceCapabilityType.ACTION, 'Type', 'project type')
}
/**

View File

@@ -31,37 +31,37 @@
<div class="col-sm-6">
<h3>Project metadata</h3>
<div class="form-group">
<label for="groupId" class="col-md-3 control-label">Group</label>
<label for="groupId" class="col-md-3 control-label">${groupId.title}</label>
<div class="col-md-8">
<input id="groupId" class="form-control" type="text" value="${groupId.content}" name="groupId">
</div>
</div>
<div class="form-group">
<label for="artifactId" class="col-md-3 control-label">Artifact</label>
<label for="artifactId" class="col-md-3 control-label">${artifactId.title}</label>
<div class="col-md-8">
<input id="artifactId" class="form-control" type="text" value="${artifactId.content}" name="artifactId">
</div>
</div>
<div class="form-group">
<label for="name" class="col-md-3 control-label">Name</label>
<label for="name" class="col-md-3 control-label">${name.title}</label>
<div class="col-md-8">
<input id="name" class="form-control" type="text" value="${name.content}" name="name">
</div>
</div>
<div class="form-group">
<label for="description" class="col-md-3 control-label">Description</label>
<label for="description" class="col-md-3 control-label">${description.title}</label>
<div class="col-md-8">
<input id="description" class="form-control" type="text" value="${description.content}" name="description">
</div>
</div>
<div class="form-group">
<label for="packageName" class="col-md-3 control-label">Package Name</label>
<label for="packageName" class="col-md-3 control-label">${packageName.title}</label>
<div class="col-md-8">
<input id="packageName" class="form-control" type="text" value="${packageName.content}" name="packageName">
</div>
</div>
<div class="form-group">
<label for="type" class="col-md-3 control-label">Type</label>
<label for="type" class="col-md-3 control-label">${types.title}</label>
<div class="col-md-8">
<select class="form-control" id="type" name="type">
<% types.content.each { %>
@@ -71,7 +71,7 @@
</div>
</div>
<div class="form-group">
<label for="packaging" class="col-md-3 control-label">Packaging</label>
<label for="packaging" class="col-md-3 control-label">${packagings.title}</label>
<div class="col-md-8">
<select class="form-control" id="packaging" name="packaging">
<% packagings.content.each { %>
@@ -81,7 +81,7 @@
</div>
</div>
<div class="form-group">
<label for="javaVersion" class="col-md-3 control-label">Java Version</label>
<label for="javaVersion" class="col-md-3 control-label">${javaVersions.title}</label>
<div class="col-md-8">
<select class="form-control" name="javaVersion" id="javaVersion">
<% javaVersions.content.each { %>
@@ -91,7 +91,7 @@
</div>
</div>
<div class="form-group">
<label for="language" class="col-md-3 control-label">Language</label>
<label for="language" class="col-md-3 control-label">${languages.title}</label>
<div class="col-md-8">
<select class="form-control" name="language" id="language">
<% languages.content.each { %>
@@ -101,7 +101,7 @@
</div>
</div>
<div class="form-group">
<label for="bootVersion" class="col-md-3 control-label">Spring Boot Version</label>
<label for="bootVersion" class="col-md-3 control-label">${bootVersions.title}</label>
<div class="col-md-8">
<select class="form-control" name="bootVersion" id="bootVersion">
<% bootVersions.content.each { %>
@@ -112,7 +112,7 @@
</div>
</div>
<div id="dependencies" class="col-sm-6">
<h3>Project dependencies</h3>
<h3>${dependencies.title}</h3>
<% dependencies.content.each { %>
<div class="form-group col-xs-6">
<h4>${it.name}</h4>

View File

@@ -26,24 +26,35 @@ import static org.junit.Assert.assertEquals
class TextCapabilityTests {
@Test
void mergeContent() {
TextCapability capability = new TextCapability('foo', '1234')
capability.merge(new TextCapability('foo', '4567'))
void mergeValue() {
TextCapability capability = new TextCapability('foo')
capability.content = '1234'
def another = new TextCapability('foo')
another.content = '4567'
capability.merge(another)
assertEquals 'foo', capability.id
assertEquals ServiceCapabilityType.TEXT, capability.type
assertEquals '4567', capability.content
}
@Test
void mergeDescription() {
TextCapability capability = new TextCapability('foo', '1234')
def other = new TextCapability('foo', '')
other.description = 'my description'
capability.merge(other)
void mergeTitle() {
TextCapability capability = new TextCapability('foo', 'Foo', 'my desc')
capability.merge(new TextCapability('foo', 'AnotherFoo', ''))
assertEquals 'foo', capability.id
assertEquals ServiceCapabilityType.TEXT, capability.type
assertEquals '1234', capability.content
assertEquals 'my description', capability.description
assertEquals 'AnotherFoo', capability.title
assertEquals 'my desc', capability.description
}
@Test
void mergeDescription() {
TextCapability capability = new TextCapability('foo', 'Foo', 'my desc')
capability.merge(new TextCapability('foo', '', 'another desc'))
assertEquals 'foo', capability.id
assertEquals ServiceCapabilityType.TEXT, capability.type
assertEquals 'Foo', capability.title
assertEquals 'another desc', capability.description
}
}

View File

@@ -1,8 +1,13 @@
initializr:
defaults:
groupId: org.foo
artifactId: foo-bar
version: 1.2.4-SNAPSHOT
name: FooBar
description: FooBar Project
packageName: org.foo.demo
groupId:
value: org.foo
artifactId:
value: foo-bar
version:
value: 1.2.4-SNAPSHOT
name:
value: FooBar
description:
value: FooBar Project
packageName:
value: org.foo.demo

View File

@@ -1,7 +1,9 @@
{
"artifactId": {
"content": "demo",
"description": "project coordinates (infer archive name)",
"id": "artifactId",
"title": "Artifact",
"type": "TEXT"
},
"bootVersions": {
@@ -22,7 +24,9 @@
"name": "1.0.2"
}
],
"description": "spring boot version",
"id": "bootVersion",
"title": "Spring Boot Version",
"type": "SINGLE_SELECT"
},
"configuration": {
@@ -130,17 +134,23 @@
"name": "Other"
}
],
"description": "dependency identifiers (comma-separated)",
"id": "dependencies",
"title": "Project dependencies",
"type": "HIERARCHICAL_MULTI_SELECT"
},
"description": {
"content": "Demo project for Spring Boot",
"description": "project description",
"id": "description",
"title": "Description",
"type": "TEXT"
},
"groupId": {
"content": "org.test",
"description": "project coordinates",
"id": "groupId",
"title": "Group",
"type": "TEXT"
},
"javaVersions": {
@@ -161,7 +171,9 @@
"name": "1.8"
}
],
"description": "language level",
"id": "javaVersion",
"title": "Java Version",
"type": "SINGLE_SELECT"
},
"languages": {
@@ -177,17 +189,23 @@
"name": "Java"
}
],
"description": "programming language",
"id": "language",
"title": "Language",
"type": "SINGLE_SELECT"
},
"name": {
"content": "demo",
"description": "project name (infer application name)",
"id": "name",
"title": "Name",
"type": "TEXT"
},
"packageName": {
"content": "demo",
"description": "root package",
"id": "packageName",
"title": "Package Name",
"type": "TEXT"
},
"packagings": {
@@ -203,7 +221,9 @@
"name": "War"
}
],
"description": "project packaging",
"id": "packaging",
"title": "Packaging",
"type": "SINGLE_SELECT"
},
"types": {
@@ -257,12 +277,16 @@
}
}
],
"description": "project type",
"id": "type",
"title": "Type",
"type": "ACTION"
},
"version": {
"content": "0.0.1-SNAPSHOT",
"description": "project version",
"id": "version",
"title": "Version",
"type": "TEXT"
}
}