Add prefix to appendix property anchor links

Refactor property appendix generator code so that the complete section
is generated and anchors follow the expected naming.

Closes gh-26375
pull/26632/head
Phillip Webb 4 years ago
parent 86a5c90d20
commit 34b288e5fe

@ -28,6 +28,7 @@ dependencies {
testImplementation("org.assertj:assertj-core:3.11.1")
testImplementation("org.apache.logging.log4j:log4j-core:2.12.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.6.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
checkstyle {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,36 +21,36 @@ package org.springframework.boot.build.context.properties;
*
* @author Phillip Webb
*/
class AsciidocBuilder {
class Asciidoc {
private final StringBuilder content;
AsciidocBuilder() {
Asciidoc() {
this.content = new StringBuilder();
}
AsciidocBuilder appendKey(Object... items) {
Asciidoc appendWithHardLineBreaks(Object... items) {
for (Object item : items) {
appendln("`+", item, "+` +");
}
return this;
}
AsciidocBuilder newLine() {
return append(System.lineSeparator());
}
AsciidocBuilder appendln(Object... items) {
Asciidoc appendln(Object... items) {
return append(items).newLine();
}
AsciidocBuilder append(Object... items) {
Asciidoc append(Object... items) {
for (Object item : items) {
this.content.append(item);
}
return this;
}
Asciidoc newLine() {
return append(System.lineSeparator());
}
@Override
public String toString() {
return this.content.toString();

@ -1,52 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.context.properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Stream;
/**
* Table entry regrouping a list of configuration properties sharing the same description.
*
* @author Brian Clozel
*/
class CompoundConfigurationTableEntry extends ConfigurationTableEntry {
private final Set<String> configurationKeys;
private final String description;
CompoundConfigurationTableEntry(String key, String description) {
this.key = key;
this.description = description;
this.configurationKeys = new TreeSet<>();
}
void addConfigurationKeys(ConfigurationProperty... properties) {
Stream.of(properties).map(ConfigurationProperty::getName).forEach(this.configurationKeys::add);
}
@Override
void write(AsciidocBuilder builder) {
builder.append("|[[" + this.key + "]]<<" + this.key + ",");
this.configurationKeys.forEach(builder::appendKey);
builder.appendln(">>");
builder.newLine().appendln("|").appendln("|+++", this.description, "+++");
}
}

@ -0,0 +1,55 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.context.properties;
import java.util.Set;
import java.util.TreeSet;
/**
* Table row regrouping a list of configuration properties sharing the same description.
*
* @author Brian Clozel
* @author Phillip Webb
*/
class CompoundRow extends Row {
private final Set<String> propertyNames;
private final String description;
CompoundRow(Snippet snippet, String prefix, String description) {
super(snippet, prefix);
this.description = description;
this.propertyNames = new TreeSet<>();
}
void addProperty(ConfigurationProperty property) {
this.propertyNames.add(property.getDisplayName());
}
@Override
void write(Asciidoc asciidoc) {
asciidoc.append("|");
asciidoc.append("[[" + getAnchor() + "]]");
asciidoc.append("<<" + getAnchor() + ",");
this.propertyNames.forEach(asciidoc::appendWithHardLineBreaks);
asciidoc.appendln(">>");
asciidoc.appendln("|+++", this.description, "+++");
asciidoc.appendln("|");
}
}

@ -1,125 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.context.properties;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.gradle.api.file.FileCollection;
/**
* Write Asciidoc documents with configuration properties listings.
*
* @author Brian Clozel
* @since 2.0.0
*/
public class ConfigurationMetadataDocumentWriter {
public void writeDocument(Path outputDirectory, DocumentOptions options, FileCollection metadataFiles)
throws IOException {
assertValidOutputDirectory(outputDirectory);
if (!Files.exists(outputDirectory)) {
Files.createDirectory(outputDirectory);
}
List<ConfigurationTable> tables = createConfigTables(ConfigurationProperties.fromFiles(metadataFiles), options);
for (ConfigurationTable table : tables) {
writeConfigurationTable(table, outputDirectory);
}
}
private void assertValidOutputDirectory(Path outputDirPath) {
if (outputDirPath == null) {
throw new IllegalArgumentException("output path should not be null");
}
if (Files.exists(outputDirPath) && !Files.isDirectory(outputDirPath)) {
throw new IllegalArgumentException("output path already exists and is not a directory");
}
}
private List<ConfigurationTable> createConfigTables(Map<String, ConfigurationProperty> metadataProperties,
DocumentOptions options) {
List<ConfigurationTable> tables = new ArrayList<>();
List<String> unmappedKeys = metadataProperties.values().stream().filter((property) -> !property.isDeprecated())
.map(ConfigurationProperty::getName).collect(Collectors.toList());
Map<String, CompoundConfigurationTableEntry> overrides = getOverrides(metadataProperties, unmappedKeys,
options);
options.getMetadataSections().forEach((id, keyPrefixes) -> tables
.add(createConfigTable(metadataProperties, unmappedKeys, overrides, id, keyPrefixes)));
if (!unmappedKeys.isEmpty()) {
throw new IllegalStateException(
"The following keys were not written to the documentation: " + String.join(", ", unmappedKeys));
}
if (!overrides.isEmpty()) {
throw new IllegalStateException("The following keys were not written to the documentation: "
+ String.join(", ", overrides.keySet()));
}
return tables;
}
private Map<String, CompoundConfigurationTableEntry> getOverrides(
Map<String, ConfigurationProperty> metadataProperties, List<String> unmappedKeys, DocumentOptions options) {
Map<String, CompoundConfigurationTableEntry> overrides = new HashMap<>();
options.getOverrides().forEach((keyPrefix, description) -> {
CompoundConfigurationTableEntry entry = new CompoundConfigurationTableEntry(keyPrefix, description);
List<String> matchingKeys = unmappedKeys.stream().filter((key) -> key.startsWith(keyPrefix))
.collect(Collectors.toList());
for (String matchingKey : matchingKeys) {
entry.addConfigurationKeys(metadataProperties.get(matchingKey));
}
overrides.put(keyPrefix, entry);
unmappedKeys.removeAll(matchingKeys);
});
return overrides;
}
private ConfigurationTable createConfigTable(Map<String, ConfigurationProperty> metadataProperties,
List<String> unmappedKeys, Map<String, CompoundConfigurationTableEntry> overrides, String id,
List<String> keyPrefixes) {
ConfigurationTable table = new ConfigurationTable(id);
for (String keyPrefix : keyPrefixes) {
List<String> matchingOverrides = overrides.keySet().stream()
.filter((overrideKey) -> overrideKey.startsWith(keyPrefix)).collect(Collectors.toList());
matchingOverrides.forEach((match) -> table.addEntry(overrides.remove(match)));
}
List<String> matchingKeys = unmappedKeys.stream()
.filter((key) -> keyPrefixes.stream().anyMatch(key::startsWith)).collect(Collectors.toList());
for (String matchingKey : matchingKeys) {
ConfigurationProperty property = metadataProperties.get(matchingKey);
table.addEntry(new SingleConfigurationTableEntry(property));
}
unmappedKeys.removeAll(matchingKeys);
return table;
}
private void writeConfigurationTable(ConfigurationTable table, Path outputDirectory) throws IOException {
Path outputFilePath = outputDirectory.resolve(table.getId() + ".adoc");
Files.deleteIfExists(outputFilePath);
Files.createFile(outputFilePath);
try (OutputStream outputStream = Files.newOutputStream(outputFilePath)) {
outputStream.write(table.toAsciidocTable().getBytes(StandardCharsets.UTF_8));
}
}
}

@ -17,14 +17,13 @@
package org.springframework.boot.build.context.properties;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -33,35 +32,40 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* {@code META-INF/spring-configuration-metadata.json} files.
*
* @author Andy Wilkinson
* @author Phillip Webb
*/
final class ConfigurationProperties {
private ConfigurationProperties() {
private final Map<String, ConfigurationProperty> byName;
private ConfigurationProperties(List<ConfigurationProperty> properties) {
Map<String, ConfigurationProperty> byName = new LinkedHashMap<>();
for (ConfigurationProperty property : properties) {
byName.put(property.getName(), property);
}
this.byName = Collections.unmodifiableMap(byName);
}
ConfigurationProperty get(String propertyName) {
return this.byName.get(propertyName);
}
Stream<ConfigurationProperty> stream() {
return this.byName.values().stream();
}
@SuppressWarnings("unchecked")
static Map<String, ConfigurationProperty> fromFiles(Iterable<File> files) {
List<ConfigurationProperty> configurationProperties = new ArrayList<>();
static ConfigurationProperties fromFiles(Iterable<File> files) {
try {
ObjectMapper objectMapper = new ObjectMapper();
List<ConfigurationProperty> properties = new ArrayList<>();
for (File file : files) {
try (Reader reader = new FileReader(file)) {
Map<String, Object> json = objectMapper.readValue(file, Map.class);
List<Map<String, Object>> properties = (List<Map<String, Object>>) json.get("properties");
for (Map<String, Object> property : properties) {
String name = (String) property.get("name");
String type = (String) property.get("type");
Object defaultValue = property.get("defaultValue");
String description = (String) property.get("description");
boolean deprecated = property.containsKey("deprecated");
configurationProperties
.add(new ConfigurationProperty(name, type, defaultValue, description, deprecated));
}
Map<String, Object> json = objectMapper.readValue(file, Map.class);
for (Map<String, Object> property : (List<Map<String, Object>>) json.get("properties")) {
properties.add(ConfigurationProperty.fromJsonProperties(property));
}
}
return configurationProperties.stream()
.collect(Collectors.toMap(ConfigurationProperty::getName, Function.identity()));
return new ConfigurationProperties(properties);
}
catch (IOException ex) {
throw new RuntimeException("Failed to load configuration metadata", ex);

@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 the original author or authors.
* Copyright 2019-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,12 +16,14 @@
package org.springframework.boot.build.context.properties;
import java.util.Map;
/**
* A configuration property.
*
* @author Andy Wilkinson
*/
public class ConfigurationProperty {
class ConfigurationProperty {
private final String name;
@ -45,23 +47,27 @@ public class ConfigurationProperty {
this.deprecated = deprecated;
}
public String getName() {
String getName() {
return this.name;
}
public String getType() {
String getDisplayName() {
return (getType() != null && getType().startsWith("java.util.Map")) ? getName() + ".*" : getName();
}
String getType() {
return this.type;
}
public Object getDefaultValue() {
Object getDefaultValue() {
return this.defaultValue;
}
public String getDescription() {
String getDescription() {
return this.description;
}
public boolean isDeprecated() {
boolean isDeprecated() {
return this.deprecated;
}
@ -70,4 +76,13 @@ public class ConfigurationProperty {
return "ConfigurationProperty [name=" + this.name + ", type=" + this.type + "]";
}
static ConfigurationProperty fromJsonProperties(Map<String, Object> property) {
String name = (String) property.get("name");
String type = (String) property.get("type");
Object defaultValue = property.get("defaultValue");
String description = (String) property.get("description");
boolean deprecated = property.containsKey("deprecated");
return new ConfigurationProperty(name, type, defaultValue, description, deprecated);
}
}

@ -28,12 +28,13 @@ import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskAction;
import org.springframework.boot.build.context.properties.DocumentOptions.Builder;
import org.springframework.boot.build.context.properties.Snippet.Config;
/**
* {@link Task} used to document auto-configuration classes.
*
* @author Andy Wilkinson
* @author Phillip Webb
*/
public class DocumentConfigurationProperties extends DefaultTask {
@ -62,42 +63,159 @@ public class DocumentConfigurationProperties extends DefaultTask {
@TaskAction
void documentConfigurationProperties() throws IOException {
Builder builder = DocumentOptions.builder();
builder.addSection("core")
.withKeyPrefixes("debug", "trace", "logging", "spring.aop", "spring.application",
"spring.autoconfigure", "spring.banner", "spring.beaninfo", "spring.codec", "spring.config",
"spring.info", "spring.jmx", "spring.lifecycle", "spring.main", "spring.messages", "spring.pid",
"spring.profiles", "spring.quartz", "spring.reactor", "spring.task",
"spring.mandatory-file-encoding", "info", "spring.output.ansi.enabled")
.addSection("mail").withKeyPrefixes("spring.mail", "spring.sendgrid").addSection("cache")
.withKeyPrefixes("spring.cache").addSection("server").withKeyPrefixes("server").addSection("web")
.withKeyPrefixes("spring.hateoas", "spring.http", "spring.servlet", "spring.jersey", "spring.mvc",
"spring.netty", "spring.resources", "spring.session", "spring.web", "spring.webflux")
.addSection("json").withKeyPrefixes("spring.jackson", "spring.gson").addSection("rsocket")
.withKeyPrefixes("spring.rsocket").addSection("templating")
.withKeyPrefixes("spring.freemarker", "spring.groovy", "spring.mustache", "spring.thymeleaf")
.addOverride("spring.groovy.template.configuration", "See GroovyMarkupConfigurer")
.addSection("security").withKeyPrefixes("spring.security").addSection("data-migration")
.withKeyPrefixes("spring.flyway", "spring.liquibase", "spring.sql.init").addSection("data")
.withKeyPrefixes("spring.couchbase", "spring.elasticsearch", "spring.h2", "spring.influx",
"spring.ldap", "spring.mongodb", "spring.neo4j", "spring.redis", "spring.dao", "spring.data",
"spring.datasource", "spring.jooq", "spring.jdbc", "spring.jpa", "spring.r2dbc")
.addOverride("spring.datasource.oracleucp",
"Oracle UCP specific settings bound to an instance of Oracle UCP's PoolDataSource")
.addOverride("spring.datasource.dbcp2",
"Commons DBCP2 specific settings bound to an instance of DBCP2's BasicDataSource")
.addOverride("spring.datasource.tomcat",
"Tomcat datasource specific settings bound to an instance of Tomcat JDBC's DataSource")
.addOverride("spring.datasource.hikari",
"Hikari specific settings bound to an instance of Hikari's HikariDataSource")
.addSection("transaction").withKeyPrefixes("spring.jta", "spring.transaction").addSection("integration")
.withKeyPrefixes("spring.activemq", "spring.artemis", "spring.batch", "spring.integration",
"spring.jms", "spring.kafka", "spring.rabbitmq", "spring.hazelcast", "spring.webservices")
.addSection("actuator").withKeyPrefixes("management").addSection("devtools")
.withKeyPrefixes("spring.devtools").addSection("testing").withKeyPrefixes("spring.test");
DocumentOptions options = builder.build();
new ConfigurationMetadataDocumentWriter().writeDocument(this.outputDir.toPath(), options,
this.configurationPropertyMetadata);
Snippets snippets = new Snippets(this.configurationPropertyMetadata);
snippets.add("application-properties.core", "Core Properties", this::corePrefixes);
snippets.add("application-properties.cache", "Cache Properties", this::cachePrefixes);
snippets.add("application-properties.mail", "Mail Properties", this::mailPrefixes);
snippets.add("application-properties.json", "JSON Properties", this::jsonPrefixes);
snippets.add("application-properties.data", "Data Properties", this::dataPrefixes);
snippets.add("application-properties.transaction", "Transaction Properties", this::transactionPrefixes);
snippets.add("application-properties.data-migration", "Data Migration Properties", this::dataMigrationPrefixes);
snippets.add("application-properties.integration", "Integration Properties", this::integrationPrefixes);
snippets.add("application-properties.web", "Web Properties", this::webPrefixes);
snippets.add("application-properties.templating", "Templating Properties", this::templatePrefixes);
snippets.add("application-properties.server", "Server Properties", this::serverPrefixes);
snippets.add("application-properties.security", "Security Properties", this::securityPrefixes);
snippets.add("application-properties.rsocket", "RSocket Properties", this::rsocketPrefixes);
snippets.add("application-properties.actuator", "Actuator Properties", this::actuatorPrefixes);
snippets.add("application-properties.devtools", "Devtools Properties", this::devtoolsPrefixes);
snippets.add("application-properties.testing", "Testing Properties", this::testingPrefixes);
snippets.writeTo(this.outputDir.toPath());
}
private void corePrefixes(Config config) {
config.accept("debug");
config.accept("trace");
config.accept("logging");
config.accept("spring.aop");
config.accept("spring.application");
config.accept("spring.autoconfigure");
config.accept("spring.banner");
config.accept("spring.beaninfo");
config.accept("spring.codec");
config.accept("spring.config");
config.accept("spring.info");
config.accept("spring.jmx");
config.accept("spring.lifecycle");
config.accept("spring.main");
config.accept("spring.messages");
config.accept("spring.pid");
config.accept("spring.profiles");
config.accept("spring.quartz");
config.accept("spring.reactor");
config.accept("spring.task");
config.accept("spring.mandatory-file-encoding");
config.accept("info");
config.accept("spring.output.ansi.enabled");
}
private void cachePrefixes(Config config) {
config.accept("spring.cache");
}
private void mailPrefixes(Config config) {
config.accept("spring.mail");
config.accept("spring.sendgrid");
}
private void jsonPrefixes(Config config) {
config.accept("spring.jackson");
config.accept("spring.gson");
}
private void dataPrefixes(Config config) {
config.accept("spring.couchbase");
config.accept("spring.elasticsearch");
config.accept("spring.h2");
config.accept("spring.influx");
config.accept("spring.ldap");
config.accept("spring.mongodb");
config.accept("spring.neo4j");
config.accept("spring.redis");
config.accept("spring.dao");
config.accept("spring.data");
config.accept("spring.datasource");
config.accept("spring.jooq");
config.accept("spring.jdbc");
config.accept("spring.jpa");
config.accept("spring.r2dbc");
config.accept("spring.datasource.oracleucp",
"Oracle UCP specific settings bound to an instance of Oracle UCP's PoolDataSource");
config.accept("spring.datasource.dbcp2",
"Commons DBCP2 specific settings bound to an instance of DBCP2's BasicDataSource");
config.accept("spring.datasource.tomcat",
"Tomcat datasource specific settings bound to an instance of Tomcat JDBC's DataSource");
config.accept("spring.datasource.hikari",
"Hikari specific settings bound to an instance of Hikari's HikariDataSource");
}
private void transactionPrefixes(Config prefix) {
prefix.accept("spring.jta");
prefix.accept("spring.transaction");
}
private void dataMigrationPrefixes(Config prefix) {
prefix.accept("spring.flyway");
prefix.accept("spring.liquibase");
prefix.accept("spring.sql.init");
}
private void integrationPrefixes(Config prefix) {
prefix.accept("spring.activemq");
prefix.accept("spring.artemis");
prefix.accept("spring.batch");
prefix.accept("spring.integration");
prefix.accept("spring.jms");
prefix.accept("spring.kafka");
prefix.accept("spring.rabbitmq");
prefix.accept("spring.hazelcast");
prefix.accept("spring.webservices");
}
private void webPrefixes(Config prefix) {
prefix.accept("spring.hateoas");
prefix.accept("spring.http");
prefix.accept("spring.servlet");
prefix.accept("spring.jersey");
prefix.accept("spring.mvc");
prefix.accept("spring.netty");
prefix.accept("spring.resources");
prefix.accept("spring.session");
prefix.accept("spring.web");
prefix.accept("spring.webflux");
}
private void templatePrefixes(Config prefix) {
prefix.accept("spring.freemarker");
prefix.accept("spring.groovy");
prefix.accept("spring.mustache");
prefix.accept("spring.thymeleaf");
prefix.accept("spring.groovy.template.configuration", "See GroovyMarkupConfigurer");
}
private void serverPrefixes(Config prefix) {
prefix.accept("server");
}
private void securityPrefixes(Config prefix) {
prefix.accept("spring.security");
}
private void rsocketPrefixes(Config prefix) {
prefix.accept("spring.rsocket");
}
private void actuatorPrefixes(Config prefix) {
prefix.accept("management");
}
private void devtoolsPrefixes(Config prefix) {
prefix.accept("spring.devtools");
}
private void testingPrefixes(Config prefix) {
prefix.accept("spring.test");
}
}

@ -1,98 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.context.properties;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Options for generating documentation for configuration properties.
*
* @author Brian Clozel
* @since 2.0.0
*/
public final class DocumentOptions {
private final Map<String, List<String>> metadataSections;
private final Map<String, String> overrides;
private DocumentOptions(Map<String, List<String>> metadataSections, Map<String, String> overrides) {
this.metadataSections = metadataSections;
this.overrides = overrides;
}
Map<String, List<String>> getMetadataSections() {
return this.metadataSections;
}
Map<String, String> getOverrides() {
return this.overrides;
}
static Builder builder() {
return new Builder();
}
/**
* Builder for DocumentOptions.
*/
public static class Builder {
Map<String, List<String>> metadataSections = new HashMap<>();
Map<String, String> overrides = new HashMap<>();
SectionSpec addSection(String name) {
return new SectionSpec(this, name);
}
Builder addOverride(String keyPrefix, String description) {
this.overrides.put(keyPrefix, description);
return this;
}
DocumentOptions build() {
return new DocumentOptions(this.metadataSections, this.overrides);
}
}
/**
* Configuration for a documentation section listing properties for a specific theme.
*/
public static class SectionSpec {
private final String name;
private final Builder builder;
SectionSpec(Builder builder, String name) {
this.builder = builder;
this.name = name;
}
Builder withKeyPrefixes(String... keyPrefixes) {
this.builder.metadataSections.put(this.name, Arrays.asList(keyPrefixes));
return this.builder;
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,19 +17,21 @@
package org.springframework.boot.build.context.properties;
/**
* Abstract class for entries in {@link ConfigurationTable}.
* Abstract class for rows in {@link Table}.
*
* @author Brian Clozel
* @author Phillip Webb
*/
abstract class ConfigurationTableEntry implements Comparable<ConfigurationTableEntry> {
abstract class Row implements Comparable<Row> {
protected String key;
private final Snippet snippet;
String getKey() {
return this.key;
}
private final String id;
abstract void write(AsciidocBuilder builder);
protected Row(Snippet snippet, String id) {
this.snippet = snippet;
this.id = id;
}
@Override
public boolean equals(Object obj) {
@ -39,18 +41,24 @@ abstract class ConfigurationTableEntry implements Comparable<ConfigurationTableE
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ConfigurationTableEntry other = (ConfigurationTableEntry) obj;
return this.key.equals(other.key);
Row other = (Row) obj;
return this.id.equals(other.id);
}
@Override
public int hashCode() {
return this.key.hashCode();
return this.id.hashCode();
}
@Override
public int compareTo(ConfigurationTableEntry other) {
return this.key.compareTo(other.getKey());
public int compareTo(Row other) {
return this.id.compareTo(other.id);
}
String getAnchor() {
return this.snippet.getAnchor() + "." + this.id;
}
abstract void write(Asciidoc asciidoc);
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,24 +20,22 @@ import java.util.Arrays;
import java.util.stream.Collectors;
/**
* Table entry containing a single configuration property.
* Table row containing a single configuration property.
*
* @author Brian Clozel
* @author Phillip Webb
*/
class SingleConfigurationTableEntry extends ConfigurationTableEntry {
class SingleRow extends Row {
private final String displayName;
private final String description;
private final String defaultValue;
private final String anchor;
SingleConfigurationTableEntry(ConfigurationProperty property) {
this.key = property.getName();
this.anchor = this.key;
if (property.getType() != null && property.getType().startsWith("java.util.Map")) {
this.key += ".*";
}
SingleRow(Snippet snippet, ConfigurationProperty property) {
super(snippet, property.getName());
this.displayName = property.getDisplayName();
this.description = property.getDescription();
this.defaultValue = getDefaultValue(property.getDefaultValue());
}
@ -54,31 +52,32 @@ class SingleConfigurationTableEntry extends ConfigurationTableEntry {
}
@Override
void write(AsciidocBuilder builder) {
builder.appendln("|[[" + this.anchor + "]]<<" + this.anchor + ",`+", this.key, "+`>>");
writeDefaultValue(builder);
writeDescription(builder);
builder.appendln();
void write(Asciidoc asciidoc) {
asciidoc.append("|");
asciidoc.append("[[" + getAnchor() + "]]");
asciidoc.appendln("<<" + getAnchor() + ",`+", this.displayName, "+`>>");
writeDescription(asciidoc);
writeDefaultValue(asciidoc);
}
private void writeDefaultValue(AsciidocBuilder builder) {
String defaultValue = (this.defaultValue != null) ? this.defaultValue : "";
if (defaultValue.isEmpty()) {
private void writeDescription(Asciidoc builder) {
if (this.description == null || this.description.isEmpty()) {
builder.appendln("|");
}
else {
defaultValue = defaultValue.replace("\\", "\\\\").replace("|", "\\|");
builder.appendln("|`+", defaultValue, "+`");
String cleanedDescription = this.description.replace("|", "\\|").replace("<", "&lt;").replace(">", "&gt;");
builder.appendln("|+++", cleanedDescription, "+++");
}
}
private void writeDescription(AsciidocBuilder builder) {
if (this.description == null || this.description.isEmpty()) {
builder.append("|");
private void writeDefaultValue(Asciidoc builder) {
String defaultValue = (this.defaultValue != null) ? this.defaultValue : "";
if (defaultValue.isEmpty()) {
builder.appendln("|");
}
else {
String cleanedDescription = this.description.replace("|", "\\|").replace("<", "&lt;").replace(">", "&gt;");
builder.append("|+++", cleanedDescription, "+++");
defaultValue = defaultValue.replace("\\", "\\\\").replace("|", "\\|");
builder.appendln("|`+", defaultValue, "+`");
}
}

@ -0,0 +1,102 @@
/*
* Copyright 2021-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.context.properties;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* A configuration properties snippet.
*
* @author Brian Clozed
* @author Phillip Webb
*/
class Snippet {
private final String anchor;
private final String title;
private final Set<String> prefixes;
private final Map<String, String> overrides;
Snippet(String anchor, String title, Consumer<Config> config) {
Set<String> prefixes = new LinkedHashSet<>();
Map<String, String> overrides = new LinkedHashMap<>();
if (config != null) {
config.accept(new Config() {
@Override
public void accept(String prefix) {
prefixes.add(prefix);
}
@Override
public void accept(String prefix, String description) {
overrides.put(prefix, description);
}
});
}
this.anchor = anchor;
this.title = title;
this.prefixes = prefixes;
this.overrides = overrides;
}
String getAnchor() {
return this.anchor;
}
String getTitle() {
return this.title;
}
void forEachPrefix(Consumer<String> action) {
this.prefixes.forEach(action);
}
void forEachOverride(BiConsumer<String, String> action) {
this.overrides.forEach(action);
}
/**
* Callback to configure the snippet.
*/
interface Config {
/**
* Accept the given prefix using the meta-data description.
* @param prefix the prefix to accept
*/
void accept(String prefix);
/**
* Accept the given prefix with a defined description.
* @param prefix the prefix to accept
* @param description the description to use
*/
void accept(String prefix, String description);
}
}

@ -0,0 +1,129 @@
/*
* Copyright 2021-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.context.properties;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.gradle.api.file.FileCollection;
/**
* Configuration properties snippets.
*
* @author Brian Clozed
* @author Phillip Webb
*/
class Snippets {
private final ConfigurationProperties properties;
private final List<Snippet> snippets = new ArrayList<>();
Snippets(FileCollection configurationPropertyMetadata) {
this.properties = ConfigurationProperties.fromFiles(configurationPropertyMetadata);
}
void add(String anchor, String title, Consumer<Snippet.Config> config) {
this.snippets.add(new Snippet(anchor, title, config));
}
void writeTo(Path outputDirectory) throws IOException {
createDirectory(outputDirectory);
Set<String> remaining = this.properties.stream().filter((property) -> !property.isDeprecated())
.map(ConfigurationProperty::getName).collect(Collectors.toSet());
for (Snippet snippet : this.snippets) {
Set<String> written = writeSnippet(outputDirectory, snippet, remaining);
remaining.removeAll(written);
}
if (!remaining.isEmpty()) {
throw new IllegalStateException(
"The following keys were not written to the documentation: " + String.join(", ", remaining));
}
}
private Set<String> writeSnippet(Path outputDirectory, Snippet snippet, Set<String> remaining) throws IOException {
Table table = new Table();
Set<String> added = new HashSet<>();
snippet.forEachOverride((prefix, description) -> {
CompoundRow row = new CompoundRow(snippet, prefix, description);
remaining.stream().filter((candidate) -> candidate.startsWith(prefix)).forEach((name) -> {
if (added.add(name)) {
row.addProperty(this.properties.get(name));
}
});
table.addRow(row);
});
snippet.forEachPrefix((prefix) -> {
remaining.stream().filter((candidate) -> candidate.startsWith(prefix)).forEach((name) -> {
if (added.add(name)) {
table.addRow(new SingleRow(snippet, this.properties.get(name)));
}
});
});
Asciidoc asciidoc = getAsciidoc(snippet, table);
writeAsciidoc(outputDirectory, snippet, asciidoc);
return added;
}
private Asciidoc getAsciidoc(Snippet snippet, Table table) {
Asciidoc asciidoc = new Asciidoc();
asciidoc.appendln("[[" + snippet.getAnchor() + "]]");
asciidoc.appendln("== ", snippet.getTitle());
table.write(asciidoc);
return asciidoc;
}
private void writeAsciidoc(Path outputDirectory, Snippet snippet, Asciidoc asciidoc) throws IOException {
String[] parts = (snippet.getAnchor()).split("\\.");
Path path = outputDirectory;
for (int i = 0; i < parts.length; i++) {
String name = (i < parts.length - 1) ? parts[i] : parts[i] + ".adoc";
path = path.resolve(name);
}
createDirectory(path.getParent());
Files.deleteIfExists(path);
try (OutputStream outputStream = Files.newOutputStream(path)) {
outputStream.write(asciidoc.toString().getBytes(StandardCharsets.UTF_8));
}
}
private void createDirectory(Path path) throws IOException {
assertValidOutputDirectory(path);
if (!Files.exists(path)) {
Files.createDirectory(path);
}
}
private void assertValidOutputDirectory(Path path) {
if (path == null) {
throw new IllegalArgumentException("Directory path should not be null");
}
if (Files.exists(path) && !Files.isDirectory(path)) {
throw new IllegalArgumentException("Path already exists and is not a directory");
}
}
}

@ -16,7 +16,6 @@
package org.springframework.boot.build.context.properties;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
@ -25,36 +24,24 @@ import java.util.TreeSet;
*
* @author Brian Clozel
*/
class ConfigurationTable {
class Table {
private final String id;
private final Set<Row> rows = new TreeSet<>();
private final Set<ConfigurationTableEntry> entries;
ConfigurationTable(String id) {
this.id = id;
this.entries = new TreeSet<>();
}
String getId() {
return this.id;
}
void addEntry(ConfigurationTableEntry... entries) {
this.entries.addAll(Arrays.asList(entries));
void addRow(Row row) {
this.rows.add(row);
}
String toAsciidocTable() {
AsciidocBuilder builder = new AsciidocBuilder();
builder.appendln("[cols=\"4,3,3\", options=\"header\"]");
builder.appendln("|===");
builder.appendln("|Key|Default Value|Description");
builder.appendln();
this.entries.forEach((entry) -> {
entry.write(builder);
builder.appendln();
void write(Asciidoc asciidoc) {
asciidoc.appendln("[cols=\"4,3,3\", options=\"header\"]");
asciidoc.appendln("|===");
asciidoc.appendln("|Name|Description|Default Value");
asciidoc.appendln();
this.rows.forEach((entry) -> {
entry.write(asciidoc);
asciidoc.appendln();
});
return builder.appendln("|===").toString();
asciidoc.appendln("|===");
}
}

@ -1,47 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.context.properties;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CompoundConfigurationTableEntry}.
*
* @author Brian Clozel
*/
public class CompoundConfigurationTableEntryTests {
private static final String NEWLINE = System.lineSeparator();
@Test
void simpleProperty() {
ConfigurationProperty firstProp = new ConfigurationProperty("spring.test.first", "java.lang.String");
ConfigurationProperty secondProp = new ConfigurationProperty("spring.test.second", "java.lang.String");
ConfigurationProperty thirdProp = new ConfigurationProperty("spring.test.third", "java.lang.String");
CompoundConfigurationTableEntry entry = new CompoundConfigurationTableEntry("spring.test",
"This is a description.");
entry.addConfigurationKeys(firstProp, secondProp, thirdProp);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
assertThat(builder.toString()).isEqualTo("|[[spring.test]]<<spring.test,`+spring.test.first+` +" + NEWLINE
+ "`+spring.test.second+` +" + NEWLINE + "`+spring.test.third+` +" + NEWLINE + ">>" + NEWLINE + NEWLINE
+ "|" + NEWLINE + "|+++This is a description.+++" + NEWLINE);
}
}

@ -0,0 +1,47 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.context.properties;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CompoundRow}.
*
* @author Brian Clozel
*/
public class CompoundRowTests {
private static final String NEWLINE = System.lineSeparator();
private static final Snippet SNIPPET = new Snippet("my", "title", null);
@Test
void simpleProperty() {
CompoundRow row = new CompoundRow(SNIPPET, "spring.test", "This is a description.");
row.addProperty(new ConfigurationProperty("spring.test.first", "java.lang.String"));
row.addProperty(new ConfigurationProperty("spring.test.second", "java.lang.String"));
row.addProperty(new ConfigurationProperty("spring.test.third", "java.lang.String"));
Asciidoc asciidoc = new Asciidoc();
row.write(asciidoc);
assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test]]<<my.spring.test,`+spring.test.first+` +"
+ NEWLINE + "`+spring.test.second+` +" + NEWLINE + "`+spring.test.third+` +" + NEWLINE + ">>" + NEWLINE
+ "|+++This is a description.+++" + NEWLINE + "|" + NEWLINE);
}
}

@ -18,7 +18,6 @@ package org.springframework.boot.build.context.properties;
import java.io.File;
import java.util.Arrays;
import java.util.Map;
import org.junit.jupiter.api.Test;
@ -33,7 +32,7 @@ class ConfigurationPropertiesTests {
@Test
void whenJsonHasAnIntegerDefaultValueThenItRemainsAnIntegerWhenRead() {
Map<String, ConfigurationProperty> properties = ConfigurationProperties
ConfigurationProperties properties = ConfigurationProperties
.fromFiles(Arrays.asList(new File("src/test/resources/spring-configuration-metadata.json")));
assertThat(properties.get("example.counter").getDefaultValue()).isEqualTo(0);
}

@ -1,49 +0,0 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.context.properties;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigurationTable}.
*
* @author Brian Clozel
*/
public class ConfigurationTableTests {
private static final String NEWLINE = System.lineSeparator();
@Test
void simpleTable() {
ConfigurationTable table = new ConfigurationTable("test");
ConfigurationProperty first = new ConfigurationProperty("spring.test.prop", "java.lang.String", "something",
"This is a description.", false);
ConfigurationProperty second = new ConfigurationProperty("spring.test.other", "java.lang.String", "other value",
"This is another description.", false);
table.addEntry(new SingleConfigurationTableEntry(first));
table.addEntry(new SingleConfigurationTableEntry(second));
assertThat(table.toAsciidocTable()).isEqualTo("[cols=\"4,3,3\", options=\"header\"]" + NEWLINE + "|==="
+ NEWLINE + "|Key|Default Value|Description" + NEWLINE + NEWLINE
+ "|[[spring.test.other]]<<spring.test.other,`+spring.test.other+`>>" + NEWLINE + "|`+other value+`"
+ NEWLINE + "|+++This is another description.+++" + NEWLINE + NEWLINE
+ "|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>" + NEWLINE + "|`+something+`"
+ NEWLINE + "|+++This is a description.+++" + NEWLINE + NEWLINE + "|===" + NEWLINE);
}
}

@ -1,111 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.context.properties;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SingleConfigurationTableEntry}.
*
* @author Brian Clozel
*/
public class SingleConfigurationTableEntryTests {
private static final String NEWLINE = System.lineSeparator();
@Test
void simpleProperty() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", "something",
"This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
assertThat(builder.toString()).isEqualTo("|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|`+something+`" + NEWLINE + "|+++This is a description.+++" + NEWLINE);
}
@Test
void noDefaultValue() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null,
"This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
assertThat(builder.toString()).isEqualTo("|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|" + NEWLINE + "|+++This is a description.+++" + NEWLINE);
}
@Test
void defaultValueWithPipes() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String",
"first|second", "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
assertThat(builder.toString()).isEqualTo("|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|`+first\\|second+`" + NEWLINE + "|+++This is a description.+++" + NEWLINE);
}
@Test
void defaultValueWithBackslash() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String",
"first\\second", "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
assertThat(builder.toString()).isEqualTo("|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|`+first\\\\second+`" + NEWLINE + "|+++This is a description.+++" + NEWLINE);
}
@Test
void descriptionWithPipe() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null,
"This is a description with a | pipe.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
assertThat(builder.toString()).isEqualTo("|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|" + NEWLINE + "|+++This is a description with a \\| pipe.+++" + NEWLINE);
}
@Test
void mapProperty() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop",
"java.util.Map<java.lang.String,java.lang.String>", null, "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
assertThat(builder.toString()).isEqualTo("|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop.*+`>>"
+ NEWLINE + "|" + NEWLINE + "|+++This is a description.+++" + NEWLINE);
}
@Test
void listProperty() {
String[] defaultValue = new String[] { "first", "second", "third" };
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop",
"java.util.List<java.lang.String>", defaultValue, "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property);
AsciidocBuilder builder = new AsciidocBuilder();
entry.write(builder);
assertThat(builder.toString()).isEqualTo(
"|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>" + NEWLINE + "|`+first," + NEWLINE
+ "second," + NEWLINE + "third+`" + NEWLINE + "|+++This is a description.+++" + NEWLINE);
}
}

@ -0,0 +1,114 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.context.properties;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SingleRow}.
*
* @author Brian Clozel
*/
public class SingleRowTests {
private static final String NEWLINE = System.lineSeparator();
private static final Snippet SNIPPET = new Snippet("my", "title", null);
@Test
void simpleProperty() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", "something",
"This is a description.", false);
SingleRow row = new SingleRow(SNIPPET, property);
Asciidoc asciidoc = new Asciidoc();
row.write(asciidoc);
assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+something+`" + NEWLINE);
}
@Test
void noDefaultValue() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null,
"This is a description.", false);
SingleRow row = new SingleRow(SNIPPET, property);
Asciidoc asciidoc = new Asciidoc();
row.write(asciidoc);
assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|+++This is a description.+++" + NEWLINE + "|" + NEWLINE);
}
@Test
void defaultValueWithPipes() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String",
"first|second", "This is a description.", false);
SingleRow row = new SingleRow(SNIPPET, property);
Asciidoc asciidoc = new Asciidoc();
row.write(asciidoc);
assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+first\\|second+`" + NEWLINE);
}
@Test
void defaultValueWithBackslash() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String",
"first\\second", "This is a description.", false);
SingleRow row = new SingleRow(SNIPPET, property);
Asciidoc asciidoc = new Asciidoc();
row.write(asciidoc);
assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+first\\\\second+`" + NEWLINE);
}
@Test
void descriptionWithPipe() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null,
"This is a description with a | pipe.", false);
SingleRow row = new SingleRow(SNIPPET, property);
Asciidoc asciidoc = new Asciidoc();
row.write(asciidoc);
assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|+++This is a description with a \\| pipe.+++" + NEWLINE + "|" + NEWLINE);
}
@Test
void mapProperty() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop",
"java.util.Map<java.lang.String,java.lang.String>", null, "This is a description.", false);
SingleRow row = new SingleRow(SNIPPET, property);
Asciidoc asciidoc = new Asciidoc();
row.write(asciidoc);
assertThat(asciidoc.toString())
.isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop.*+`>>" + NEWLINE
+ "|+++This is a description.+++" + NEWLINE + "|" + NEWLINE);
}
@Test
void listProperty() {
String[] defaultValue = new String[] { "first", "second", "third" };
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop",
"java.util.List<java.lang.String>", defaultValue, "This is a description.", false);
SingleRow row = new SingleRow(SNIPPET, property);
Asciidoc asciidoc = new Asciidoc();
row.write(asciidoc);
assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+first," + NEWLINE + "second," + NEWLINE
+ "third+`" + NEWLINE);
}
}

@ -0,0 +1,58 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.context.properties;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link Table}.
*
* @author Brian Clozel
*/
public class TableTests {
private static final String NEWLINE = System.lineSeparator();
private static final Snippet SNIPPET = new Snippet("my", "title", null);
@Test
void simpleTable() {
Table table = new Table();
table.addRow(new SingleRow(SNIPPET, new ConfigurationProperty("spring.test.prop", "java.lang.String",
"something", "This is a description.", false)));
table.addRow(new SingleRow(SNIPPET, new ConfigurationProperty("spring.test.other", "java.lang.String",
"other value", "This is another description.", false)));
Asciidoc asciidoc = new Asciidoc();
table.write(asciidoc);
// @formatter:off
assertThat(asciidoc.toString()).isEqualTo(
"[cols=\"4,3,3\", options=\"header\"]" + NEWLINE +
"|===" + NEWLINE +
"|Name|Description|Default Value" + NEWLINE + NEWLINE +
"|[[my.spring.test.other]]<<my.spring.test.other,`+spring.test.other+`>>" + NEWLINE +
"|+++This is another description.+++" + NEWLINE +
"|`+other value+`" + NEWLINE + NEWLINE +
"|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop+`>>" + NEWLINE +
"|+++This is a description.+++" + NEWLINE +
"|`+something+`" + NEWLINE + NEWLINE +
"|===" + NEWLINE);
// @formatter:on
}
}

@ -218,7 +218,7 @@ task documentVersionProperties(type: org.springframework.boot.build.constraints.
task documentConfigurationProperties(type: org.springframework.boot.build.context.properties.DocumentConfigurationProperties) {
configurationPropertyMetadata = configurations.configurationProperties
outputDir = file("${buildDir}/docs/generated/application-properties/documented-application-properties/")
outputDir = file("${buildDir}/docs/generated/")
}
tasks.withType(org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask) {

@ -762,3 +762,19 @@ executable-jar-alternatives=executable-jar.alternatives
dependency-versions=dependency-versions
dependency-versions-coordinates=dependency-versions.coordinates
dependency-versions-properties=dependency-versions.properties
core-properties=application-properties.core
cache-properties=application-properties.cache
mail-properties=application-properties.mail
json-properties=application-properties.json
data-properties=application-properties.data
transaction-properties=application-properties.transaction
data-migration-properties=application-properties.data-migration
integration-properties=application-properties.integration
web-properties=application-properties.web
templating-properties=application-properties.templating
server-properties=application-properties.server
security-properties=application-properties.security
rsocket-properties=application-properties.rsocket
actuator-properties=application-properties.actuator
devtools-properties=application-properties.devtools
testing-properties=application-properties.testing

@ -1,6 +1,6 @@
[appendix]
[[application-properties]]
= Common Application properties
= Common Application Properties
include::attributes.adoc[]

@ -1,3 +0,0 @@
[[application-properties.actuator]]
== Actuator Properties [[actuator-properties]]
include::documented-application-properties/actuator.adoc[]

@ -1,3 +0,0 @@
[[application-properties.cache]]
== Cache Properties [[cache-properties]]
include::documented-application-properties/cache.adoc[]

@ -1,3 +0,0 @@
[[application-properties.core]]
== Core Properties [[core-properties]]
include::documented-application-properties/core.adoc[]

@ -1,3 +0,0 @@
[[application-properties.data-migration]]
== Data Migration Properties [[data-migration-properties]]
include::documented-application-properties/data-migration.adoc[]

@ -1,3 +0,0 @@
[[application-properties.data]]
== Data Properties [[data-properties]]
include::documented-application-properties/data.adoc[]

@ -1,3 +0,0 @@
[[application-properties.devtools]]
== Devtools Properties [[devtools-properties]]
include::documented-application-properties/devtools.adoc[]

@ -1,3 +0,0 @@
[[application-properties.integration]]
== Integration Properties [[integration-properties]]
include::documented-application-properties/integration.adoc[]

@ -1,3 +0,0 @@
[[application-properties.json]]
== JSON Properties [[json-properties]]
include::documented-application-properties/json.adoc[]

@ -1,3 +0,0 @@
[[application-properties.mail]]
== Mail Properties [[mail-properties]]
include::documented-application-properties/mail.adoc[]

@ -1,3 +0,0 @@
[[application-properties.rsocket]]
== RSocket Properties [[rsocket-properties]]
include::documented-application-properties/rsocket.adoc[]

@ -1,3 +0,0 @@
[[application-properties.security]]
== Security Properties [[security-properties]]
include::documented-application-properties/security.adoc[]

@ -1,3 +0,0 @@
[[application-properties.server]]
== Server Properties [[server-properties]]
include::documented-application-properties/server.adoc[]

@ -1,3 +0,0 @@
[[application-properties.templating]]
== Templating Properties [[templating-properties]]
include::documented-application-properties/templating.adoc[]

@ -1,3 +0,0 @@
[[application-properties.testing]]
== Testing Properties [[testing-properties]]
include::documented-application-properties/testing.adoc[]

@ -1,3 +0,0 @@
[[application-properties.transaction]]
== Transaction Properties [[transaction-properties]]
include::documented-application-properties/transaction.adoc[]

@ -1,3 +0,0 @@
[[application-properties.web]]
== Web Properties [[web-properties]]
include::documented-application-properties/web.adoc[]
Loading…
Cancel
Save