Commit 34b288e5 authored by Phillip Webb's avatar Phillip Webb

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
parent 86a5c90d
...@@ -28,6 +28,7 @@ dependencies { ...@@ -28,6 +28,7 @@ dependencies {
testImplementation("org.assertj:assertj-core:3.11.1") testImplementation("org.assertj:assertj-core:3.11.1")
testImplementation("org.apache.logging.log4j:log4j-core:2.12.1") testImplementation("org.apache.logging.log4j:log4j-core:2.12.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.6.0") testImplementation("org.junit.jupiter:junit-jupiter:5.6.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
} }
checkstyle { checkstyle {
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; ...@@ -21,36 +21,36 @@ package org.springframework.boot.build.context.properties;
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
class AsciidocBuilder { class Asciidoc {
private final StringBuilder content; private final StringBuilder content;
AsciidocBuilder() { Asciidoc() {
this.content = new StringBuilder(); this.content = new StringBuilder();
} }
AsciidocBuilder appendKey(Object... items) { Asciidoc appendWithHardLineBreaks(Object... items) {
for (Object item : items) { for (Object item : items) {
appendln("`+", item, "+` +"); appendln("`+", item, "+` +");
} }
return this; return this;
} }
AsciidocBuilder newLine() { Asciidoc appendln(Object... items) {
return append(System.lineSeparator());
}
AsciidocBuilder appendln(Object... items) {
return append(items).newLine(); return append(items).newLine();
} }
AsciidocBuilder append(Object... items) { Asciidoc append(Object... items) {
for (Object item : items) { for (Object item : items) {
this.content.append(item); this.content.append(item);
} }
return this; return this;
} }
Asciidoc newLine() {
return append(System.lineSeparator());
}
@Override @Override
public String toString() { public String toString() {
return this.content.toString(); return this.content.toString();
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -18,35 +18,38 @@ package org.springframework.boot.build.context.properties; ...@@ -18,35 +18,38 @@ package org.springframework.boot.build.context.properties;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.stream.Stream;
/** /**
* Table entry regrouping a list of configuration properties sharing the same description. * Table row regrouping a list of configuration properties sharing the same description.
* *
* @author Brian Clozel * @author Brian Clozel
* @author Phillip Webb
*/ */
class CompoundConfigurationTableEntry extends ConfigurationTableEntry { class CompoundRow extends Row {
private final Set<String> configurationKeys; private final Set<String> propertyNames;
private final String description; private final String description;
CompoundConfigurationTableEntry(String key, String description) { CompoundRow(Snippet snippet, String prefix, String description) {
this.key = key; super(snippet, prefix);
this.description = description; this.description = description;
this.configurationKeys = new TreeSet<>(); this.propertyNames = new TreeSet<>();
} }
void addConfigurationKeys(ConfigurationProperty... properties) { void addProperty(ConfigurationProperty property) {
Stream.of(properties).map(ConfigurationProperty::getName).forEach(this.configurationKeys::add); this.propertyNames.add(property.getDisplayName());
} }
@Override @Override
void write(AsciidocBuilder builder) { void write(Asciidoc asciidoc) {
builder.append("|[[" + this.key + "]]<<" + this.key + ","); asciidoc.append("|");
this.configurationKeys.forEach(builder::appendKey); asciidoc.append("[[" + getAnchor() + "]]");
builder.appendln(">>"); asciidoc.append("<<" + getAnchor() + ",");
builder.newLine().appendln("|").appendln("|+++", this.description, "+++"); this.propertyNames.forEach(asciidoc::appendWithHardLineBreaks);
asciidoc.appendln(">>");
asciidoc.appendln("|+++", this.description, "+++");
asciidoc.appendln("|");
} }
} }
/*
* 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 @@ ...@@ -17,14 +17,13 @@
package org.springframework.boot.build.context.properties; package org.springframework.boot.build.context.properties;
import java.io.File; import java.io.File;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.stream.Stream;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
...@@ -33,35 +32,40 @@ import com.fasterxml.jackson.databind.ObjectMapper; ...@@ -33,35 +32,40 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* {@code META-INF/spring-configuration-metadata.json} files. * {@code META-INF/spring-configuration-metadata.json} files.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb
*/ */
final class ConfigurationProperties { 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") @SuppressWarnings("unchecked")
static Map<String, ConfigurationProperty> fromFiles(Iterable<File> files) { static ConfigurationProperties fromFiles(Iterable<File> files) {
List<ConfigurationProperty> configurationProperties = new ArrayList<>();
try { try {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
List<ConfigurationProperty> properties = new ArrayList<>();
for (File file : files) { for (File file : files) {
try (Reader reader = new FileReader(file)) { Map<String, Object> json = objectMapper.readValue(file, Map.class);
Map<String, Object> json = objectMapper.readValue(file, Map.class); for (Map<String, Object> property : (List<Map<String, Object>>) json.get("properties")) {
List<Map<String, Object>> properties = (List<Map<String, Object>>) json.get("properties"); properties.add(ConfigurationProperty.fromJsonProperties(property));
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));
}
} }
} }
return configurationProperties.stream() return new ConfigurationProperties(properties);
.collect(Collectors.toMap(ConfigurationProperty::getName, Function.identity()));
} }
catch (IOException ex) { catch (IOException ex) {
throw new RuntimeException("Failed to load configuration metadata", ex); throw new RuntimeException("Failed to load configuration metadata", ex);
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,12 +16,14 @@ ...@@ -16,12 +16,14 @@
package org.springframework.boot.build.context.properties; package org.springframework.boot.build.context.properties;
import java.util.Map;
/** /**
* A configuration property. * A configuration property.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class ConfigurationProperty { class ConfigurationProperty {
private final String name; private final String name;
...@@ -45,23 +47,27 @@ public class ConfigurationProperty { ...@@ -45,23 +47,27 @@ public class ConfigurationProperty {
this.deprecated = deprecated; this.deprecated = deprecated;
} }
public String getName() { String getName() {
return this.name; return this.name;
} }
public String getType() { String getDisplayName() {
return (getType() != null && getType().startsWith("java.util.Map")) ? getName() + ".*" : getName();
}
String getType() {
return this.type; return this.type;
} }
public Object getDefaultValue() { Object getDefaultValue() {
return this.defaultValue; return this.defaultValue;
} }
public String getDescription() { String getDescription() {
return this.description; return this.description;
} }
public boolean isDeprecated() { boolean isDeprecated() {
return this.deprecated; return this.deprecated;
} }
...@@ -70,4 +76,13 @@ public class ConfigurationProperty { ...@@ -70,4 +76,13 @@ public class ConfigurationProperty {
return "ConfigurationProperty [name=" + this.name + ", type=" + this.type + "]"; 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; ...@@ -28,12 +28,13 @@ import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskAction; 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. * {@link Task} used to document auto-configuration classes.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb
*/ */
public class DocumentConfigurationProperties extends DefaultTask { public class DocumentConfigurationProperties extends DefaultTask {
...@@ -62,42 +63,159 @@ public class DocumentConfigurationProperties extends DefaultTask { ...@@ -62,42 +63,159 @@ public class DocumentConfigurationProperties extends DefaultTask {
@TaskAction @TaskAction
void documentConfigurationProperties() throws IOException { void documentConfigurationProperties() throws IOException {
Builder builder = DocumentOptions.builder(); Snippets snippets = new Snippets(this.configurationPropertyMetadata);
builder.addSection("core") snippets.add("application-properties.core", "Core Properties", this::corePrefixes);
.withKeyPrefixes("debug", "trace", "logging", "spring.aop", "spring.application", snippets.add("application-properties.cache", "Cache Properties", this::cachePrefixes);
"spring.autoconfigure", "spring.banner", "spring.beaninfo", "spring.codec", "spring.config", snippets.add("application-properties.mail", "Mail Properties", this::mailPrefixes);
"spring.info", "spring.jmx", "spring.lifecycle", "spring.main", "spring.messages", "spring.pid", snippets.add("application-properties.json", "JSON Properties", this::jsonPrefixes);
"spring.profiles", "spring.quartz", "spring.reactor", "spring.task", snippets.add("application-properties.data", "Data Properties", this::dataPrefixes);
"spring.mandatory-file-encoding", "info", "spring.output.ansi.enabled") snippets.add("application-properties.transaction", "Transaction Properties", this::transactionPrefixes);
.addSection("mail").withKeyPrefixes("spring.mail", "spring.sendgrid").addSection("cache") snippets.add("application-properties.data-migration", "Data Migration Properties", this::dataMigrationPrefixes);
.withKeyPrefixes("spring.cache").addSection("server").withKeyPrefixes("server").addSection("web") snippets.add("application-properties.integration", "Integration Properties", this::integrationPrefixes);
.withKeyPrefixes("spring.hateoas", "spring.http", "spring.servlet", "spring.jersey", "spring.mvc", snippets.add("application-properties.web", "Web Properties", this::webPrefixes);
"spring.netty", "spring.resources", "spring.session", "spring.web", "spring.webflux") snippets.add("application-properties.templating", "Templating Properties", this::templatePrefixes);
.addSection("json").withKeyPrefixes("spring.jackson", "spring.gson").addSection("rsocket") snippets.add("application-properties.server", "Server Properties", this::serverPrefixes);
.withKeyPrefixes("spring.rsocket").addSection("templating") snippets.add("application-properties.security", "Security Properties", this::securityPrefixes);
.withKeyPrefixes("spring.freemarker", "spring.groovy", "spring.mustache", "spring.thymeleaf") snippets.add("application-properties.rsocket", "RSocket Properties", this::rsocketPrefixes);
.addOverride("spring.groovy.template.configuration", "See GroovyMarkupConfigurer") snippets.add("application-properties.actuator", "Actuator Properties", this::actuatorPrefixes);
.addSection("security").withKeyPrefixes("spring.security").addSection("data-migration") snippets.add("application-properties.devtools", "Devtools Properties", this::devtoolsPrefixes);
.withKeyPrefixes("spring.flyway", "spring.liquibase", "spring.sql.init").addSection("data") snippets.add("application-properties.testing", "Testing Properties", this::testingPrefixes);
.withKeyPrefixes("spring.couchbase", "spring.elasticsearch", "spring.h2", "spring.influx", snippets.writeTo(this.outputDir.toPath());
"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", private void corePrefixes(Config config) {
"Oracle UCP specific settings bound to an instance of Oracle UCP's PoolDataSource") config.accept("debug");
.addOverride("spring.datasource.dbcp2", config.accept("trace");
"Commons DBCP2 specific settings bound to an instance of DBCP2's BasicDataSource") config.accept("logging");
.addOverride("spring.datasource.tomcat", config.accept("spring.aop");
"Tomcat datasource specific settings bound to an instance of Tomcat JDBC's DataSource") config.accept("spring.application");
.addOverride("spring.datasource.hikari", config.accept("spring.autoconfigure");
"Hikari specific settings bound to an instance of Hikari's HikariDataSource") config.accept("spring.banner");
.addSection("transaction").withKeyPrefixes("spring.jta", "spring.transaction").addSection("integration") config.accept("spring.beaninfo");
.withKeyPrefixes("spring.activemq", "spring.artemis", "spring.batch", "spring.integration", config.accept("spring.codec");
"spring.jms", "spring.kafka", "spring.rabbitmq", "spring.hazelcast", "spring.webservices") config.accept("spring.config");
.addSection("actuator").withKeyPrefixes("management").addSection("devtools") config.accept("spring.info");
.withKeyPrefixes("spring.devtools").addSection("testing").withKeyPrefixes("spring.test"); config.accept("spring.jmx");
DocumentOptions options = builder.build(); config.accept("spring.lifecycle");
new ConfigurationMetadataDocumentWriter().writeDocument(this.outputDir.toPath(), options, config.accept("spring.main");
this.configurationPropertyMetadata); 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");
} }
} }
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -17,19 +17,21 @@ ...@@ -17,19 +17,21 @@
package org.springframework.boot.build.context.properties; 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 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() { private final String id;
return this.key;
}
abstract void write(AsciidocBuilder builder); protected Row(Snippet snippet, String id) {
this.snippet = snippet;
this.id = id;
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
...@@ -39,18 +41,24 @@ abstract class ConfigurationTableEntry implements Comparable<ConfigurationTableE ...@@ -39,18 +41,24 @@ abstract class ConfigurationTableEntry implements Comparable<ConfigurationTableE
if (obj == null || getClass() != obj.getClass()) { if (obj == null || getClass() != obj.getClass()) {
return false; return false;
} }
ConfigurationTableEntry other = (ConfigurationTableEntry) obj; Row other = (Row) obj;
return this.key.equals(other.key); return this.id.equals(other.id);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return this.key.hashCode(); return this.id.hashCode();
} }
@Override @Override
public int compareTo(ConfigurationTableEntry other) { public int compareTo(Row other) {
return this.key.compareTo(other.getKey()); return this.id.compareTo(other.id);
} }
String getAnchor() {
return this.snippet.getAnchor() + "." + this.id;
}
abstract void write(Asciidoc asciidoc);
} }
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -20,24 +20,22 @@ import java.util.Arrays; ...@@ -20,24 +20,22 @@ import java.util.Arrays;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* Table entry containing a single configuration property. * Table row containing a single configuration property.
* *
* @author Brian Clozel * @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 description;
private final String defaultValue; private final String defaultValue;
private final String anchor; SingleRow(Snippet snippet, ConfigurationProperty property) {
super(snippet, property.getName());
SingleConfigurationTableEntry(ConfigurationProperty property) { this.displayName = property.getDisplayName();
this.key = property.getName();
this.anchor = this.key;
if (property.getType() != null && property.getType().startsWith("java.util.Map")) {
this.key += ".*";
}
this.description = property.getDescription(); this.description = property.getDescription();
this.defaultValue = getDefaultValue(property.getDefaultValue()); this.defaultValue = getDefaultValue(property.getDefaultValue());
} }
...@@ -54,31 +52,32 @@ class SingleConfigurationTableEntry extends ConfigurationTableEntry { ...@@ -54,31 +52,32 @@ class SingleConfigurationTableEntry extends ConfigurationTableEntry {
} }
@Override @Override
void write(AsciidocBuilder builder) { void write(Asciidoc asciidoc) {
builder.appendln("|[[" + this.anchor + "]]<<" + this.anchor + ",`+", this.key, "+`>>"); asciidoc.append("|");
writeDefaultValue(builder); asciidoc.append("[[" + getAnchor() + "]]");
writeDescription(builder); asciidoc.appendln("<<" + getAnchor() + ",`+", this.displayName, "+`>>");
builder.appendln(); writeDescription(asciidoc);
writeDefaultValue(asciidoc);
} }
private void writeDefaultValue(AsciidocBuilder builder) { private void writeDescription(Asciidoc builder) {
String defaultValue = (this.defaultValue != null) ? this.defaultValue : ""; if (this.description == null || this.description.isEmpty()) {
if (defaultValue.isEmpty()) {
builder.appendln("|"); builder.appendln("|");
} }
else { else {
defaultValue = defaultValue.replace("\\", "\\\\").replace("|", "\\|"); String cleanedDescription = this.description.replace("|", "\\|").replace("<", "&lt;").replace(">", "&gt;");
builder.appendln("|`+", defaultValue, "+`"); builder.appendln("|+++", cleanedDescription, "+++");
} }
} }
private void writeDescription(AsciidocBuilder builder) { private void writeDefaultValue(Asciidoc builder) {
if (this.description == null || this.description.isEmpty()) { String defaultValue = (this.defaultValue != null) ? this.defaultValue : "";
builder.append("|"); if (defaultValue.isEmpty()) {
builder.appendln("|");
} }
else { else {
String cleanedDescription = this.description.replace("|", "\\|").replace("<", "&lt;").replace(">", "&gt;"); defaultValue = defaultValue.replace("\\", "\\\\").replace("|", "\\|");
builder.append("|+++", cleanedDescription, "+++"); builder.appendln("|`+", defaultValue, "+`");
} }
} }
......
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2021-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,82 +16,86 @@ ...@@ -16,82 +16,86 @@
package org.springframework.boot.build.context.properties; package org.springframework.boot.build.context.properties;
import java.util.Arrays; import java.util.LinkedHashMap;
import java.util.HashMap; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/** /**
* Options for generating documentation for configuration properties. * A configuration properties snippet.
* *
* @author Brian Clozel * @author Brian Clozed
* @since 2.0.0 * @author Phillip Webb
*/ */
public final class DocumentOptions { class Snippet {
private final Map<String, List<String>> metadataSections; private final String anchor;
private final String title;
private final Set<String> prefixes;
private final Map<String, String> overrides; private final Map<String, String> overrides;
private DocumentOptions(Map<String, List<String>> metadataSections, Map<String, String> overrides) { Snippet(String anchor, String title, Consumer<Config> config) {
this.metadataSections = metadataSections; 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; this.overrides = overrides;
} }
Map<String, List<String>> getMetadataSections() { String getAnchor() {
return this.metadataSections; return this.anchor;
} }
Map<String, String> getOverrides() { String getTitle() {
return this.overrides; return this.title;
} }
static Builder builder() { void forEachPrefix(Consumer<String> action) {
return new Builder(); this.prefixes.forEach(action);
} }
/** void forEachOverride(BiConsumer<String, String> action) {
* Builder for DocumentOptions. this.overrides.forEach(action);
*/
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. * Callback to configure the snippet.
*/ */
public static class SectionSpec { interface Config {
private final String name; /**
* Accept the given prefix using the meta-data description.
private final Builder builder; * @param prefix the prefix to accept
*/
SectionSpec(Builder builder, String name) { void accept(String prefix);
this.builder = builder;
this.name = name; /**
} * Accept the given prefix with a defined description.
* @param prefix the prefix to accept
Builder withKeyPrefixes(String... keyPrefixes) { * @param description the description to use
this.builder.metadataSections.put(this.name, Arrays.asList(keyPrefixes)); */
return this.builder; void accept(String prefix, String description);
}
} }
......
/*
* 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 @@ ...@@ -16,7 +16,6 @@
package org.springframework.boot.build.context.properties; package org.springframework.boot.build.context.properties;
import java.util.Arrays;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
...@@ -25,36 +24,24 @@ import java.util.TreeSet; ...@@ -25,36 +24,24 @@ import java.util.TreeSet;
* *
* @author Brian Clozel * @author Brian Clozel
*/ */
class ConfigurationTable { class Table {
private final String id; private final Set<Row> rows = new TreeSet<>();
private final Set<ConfigurationTableEntry> entries; void addRow(Row row) {
this.rows.add(row);
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));
} }
String toAsciidocTable() { void write(Asciidoc asciidoc) {
AsciidocBuilder builder = new AsciidocBuilder(); asciidoc.appendln("[cols=\"4,3,3\", options=\"header\"]");
builder.appendln("[cols=\"4,3,3\", options=\"header\"]"); asciidoc.appendln("|===");
builder.appendln("|==="); asciidoc.appendln("|Name|Description|Default Value");
builder.appendln("|Key|Default Value|Description"); asciidoc.appendln();
builder.appendln(); this.rows.forEach((entry) -> {
this.entries.forEach((entry) -> { entry.write(asciidoc);
entry.write(builder); asciidoc.appendln();
builder.appendln();
}); });
return builder.appendln("|===").toString(); asciidoc.appendln("|===");
} }
} }
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -21,27 +21,27 @@ import org.junit.jupiter.api.Test; ...@@ -21,27 +21,27 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link CompoundConfigurationTableEntry}. * Tests for {@link CompoundRow}.
* *
* @author Brian Clozel * @author Brian Clozel
*/ */
public class CompoundConfigurationTableEntryTests { public class CompoundRowTests {
private static final String NEWLINE = System.lineSeparator(); private static final String NEWLINE = System.lineSeparator();
private static final Snippet SNIPPET = new Snippet("my", "title", null);
@Test @Test
void simpleProperty() { void simpleProperty() {
ConfigurationProperty firstProp = new ConfigurationProperty("spring.test.first", "java.lang.String"); CompoundRow row = new CompoundRow(SNIPPET, "spring.test", "This is a description.");
ConfigurationProperty secondProp = new ConfigurationProperty("spring.test.second", "java.lang.String"); row.addProperty(new ConfigurationProperty("spring.test.first", "java.lang.String"));
ConfigurationProperty thirdProp = new ConfigurationProperty("spring.test.third", "java.lang.String"); row.addProperty(new ConfigurationProperty("spring.test.second", "java.lang.String"));
CompoundConfigurationTableEntry entry = new CompoundConfigurationTableEntry("spring.test", row.addProperty(new ConfigurationProperty("spring.test.third", "java.lang.String"));
"This is a description."); Asciidoc asciidoc = new Asciidoc();
entry.addConfigurationKeys(firstProp, secondProp, thirdProp); row.write(asciidoc);
AsciidocBuilder builder = new AsciidocBuilder(); assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test]]<<my.spring.test,`+spring.test.first+` +"
entry.write(builder); + NEWLINE + "`+spring.test.second+` +" + NEWLINE + "`+spring.test.third+` +" + NEWLINE + ">>" + NEWLINE
assertThat(builder.toString()).isEqualTo("|[[spring.test]]<<spring.test,`+spring.test.first+` +" + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|" + NEWLINE);
+ "`+spring.test.second+` +" + NEWLINE + "`+spring.test.third+` +" + NEWLINE + ">>" + NEWLINE + NEWLINE
+ "|" + NEWLINE + "|+++This is a description.+++" + NEWLINE);
} }
} }
...@@ -18,7 +18,6 @@ package org.springframework.boot.build.context.properties; ...@@ -18,7 +18,6 @@ package org.springframework.boot.build.context.properties;
import java.io.File; import java.io.File;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
...@@ -33,7 +32,7 @@ class ConfigurationPropertiesTests { ...@@ -33,7 +32,7 @@ class ConfigurationPropertiesTests {
@Test @Test
void whenJsonHasAnIntegerDefaultValueThenItRemainsAnIntegerWhenRead() { void whenJsonHasAnIntegerDefaultValueThenItRemainsAnIntegerWhenRead() {
Map<String, ConfigurationProperty> properties = ConfigurationProperties ConfigurationProperties properties = ConfigurationProperties
.fromFiles(Arrays.asList(new File("src/test/resources/spring-configuration-metadata.json"))); .fromFiles(Arrays.asList(new File("src/test/resources/spring-configuration-metadata.json")));
assertThat(properties.get("example.counter").getDefaultValue()).isEqualTo(0); assertThat(properties.get("example.counter").getDefaultValue()).isEqualTo(0);
} }
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -21,78 +21,81 @@ import org.junit.jupiter.api.Test; ...@@ -21,78 +21,81 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link SingleConfigurationTableEntry}. * Tests for {@link SingleRow}.
* *
* @author Brian Clozel * @author Brian Clozel
*/ */
public class SingleConfigurationTableEntryTests { public class SingleRowTests {
private static final String NEWLINE = System.lineSeparator(); private static final String NEWLINE = System.lineSeparator();
private static final Snippet SNIPPET = new Snippet("my", "title", null);
@Test @Test
void simpleProperty() { void simpleProperty() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", "something", ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", "something",
"This is a description.", false); "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property); SingleRow row = new SingleRow(SNIPPET, property);
AsciidocBuilder builder = new AsciidocBuilder(); Asciidoc asciidoc = new Asciidoc();
entry.write(builder); row.write(asciidoc);
assertThat(builder.toString()).isEqualTo("|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>" assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|`+something+`" + NEWLINE + "|+++This is a description.+++" + NEWLINE); + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+something+`" + NEWLINE);
} }
@Test @Test
void noDefaultValue() { void noDefaultValue() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null, ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null,
"This is a description.", false); "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property); SingleRow row = new SingleRow(SNIPPET, property);
AsciidocBuilder builder = new AsciidocBuilder(); Asciidoc asciidoc = new Asciidoc();
entry.write(builder); row.write(asciidoc);
assertThat(builder.toString()).isEqualTo("|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>" assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|" + NEWLINE + "|+++This is a description.+++" + NEWLINE); + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|" + NEWLINE);
} }
@Test @Test
void defaultValueWithPipes() { void defaultValueWithPipes() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String",
"first|second", "This is a description.", false); "first|second", "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property); SingleRow row = new SingleRow(SNIPPET, property);
AsciidocBuilder builder = new AsciidocBuilder(); Asciidoc asciidoc = new Asciidoc();
entry.write(builder); row.write(asciidoc);
assertThat(builder.toString()).isEqualTo("|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>" assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|`+first\\|second+`" + NEWLINE + "|+++This is a description.+++" + NEWLINE); + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+first\\|second+`" + NEWLINE);
} }
@Test @Test
void defaultValueWithBackslash() { void defaultValueWithBackslash() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String",
"first\\second", "This is a description.", false); "first\\second", "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property); SingleRow row = new SingleRow(SNIPPET, property);
AsciidocBuilder builder = new AsciidocBuilder(); Asciidoc asciidoc = new Asciidoc();
entry.write(builder); row.write(asciidoc);
assertThat(builder.toString()).isEqualTo("|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>" assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|`+first\\\\second+`" + NEWLINE + "|+++This is a description.+++" + NEWLINE); + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+first\\\\second+`" + NEWLINE);
} }
@Test @Test
void descriptionWithPipe() { void descriptionWithPipe() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null, ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null,
"This is a description with a | pipe.", false); "This is a description with a | pipe.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property); SingleRow row = new SingleRow(SNIPPET, property);
AsciidocBuilder builder = new AsciidocBuilder(); Asciidoc asciidoc = new Asciidoc();
entry.write(builder); row.write(asciidoc);
assertThat(builder.toString()).isEqualTo("|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>" assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop+`>>"
+ NEWLINE + "|" + NEWLINE + "|+++This is a description with a \\| pipe.+++" + NEWLINE); + NEWLINE + "|+++This is a description with a \\| pipe.+++" + NEWLINE + "|" + NEWLINE);
} }
@Test @Test
void mapProperty() { void mapProperty() {
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", ConfigurationProperty property = new ConfigurationProperty("spring.test.prop",
"java.util.Map<java.lang.String,java.lang.String>", null, "This is a description.", false); "java.util.Map<java.lang.String,java.lang.String>", null, "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property); SingleRow row = new SingleRow(SNIPPET, property);
AsciidocBuilder builder = new AsciidocBuilder(); Asciidoc asciidoc = new Asciidoc();
entry.write(builder); row.write(asciidoc);
assertThat(builder.toString()).isEqualTo("|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop.*+`>>" assertThat(asciidoc.toString())
+ NEWLINE + "|" + NEWLINE + "|+++This is a description.+++" + NEWLINE); .isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop.*+`>>" + NEWLINE
+ "|+++This is a description.+++" + NEWLINE + "|" + NEWLINE);
} }
@Test @Test
...@@ -100,12 +103,12 @@ public class SingleConfigurationTableEntryTests { ...@@ -100,12 +103,12 @@ public class SingleConfigurationTableEntryTests {
String[] defaultValue = new String[] { "first", "second", "third" }; String[] defaultValue = new String[] { "first", "second", "third" };
ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", ConfigurationProperty property = new ConfigurationProperty("spring.test.prop",
"java.util.List<java.lang.String>", defaultValue, "This is a description.", false); "java.util.List<java.lang.String>", defaultValue, "This is a description.", false);
SingleConfigurationTableEntry entry = new SingleConfigurationTableEntry(property); SingleRow row = new SingleRow(SNIPPET, property);
AsciidocBuilder builder = new AsciidocBuilder(); Asciidoc asciidoc = new Asciidoc();
entry.write(builder); row.write(asciidoc);
assertThat(builder.toString()).isEqualTo( assertThat(asciidoc.toString()).isEqualTo("|[[my.spring.test.prop]]<<my.spring.test.prop,`+spring.test.prop+`>>"
"|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>" + NEWLINE + "|`+first," + NEWLINE + NEWLINE + "|+++This is a description.+++" + NEWLINE + "|`+first," + NEWLINE + "second," + NEWLINE
+ "second," + NEWLINE + "third+`" + NEWLINE + "|+++This is a description.+++" + NEWLINE); + "third+`" + NEWLINE);
} }
} }
...@@ -21,29 +21,38 @@ import org.junit.jupiter.api.Test; ...@@ -21,29 +21,38 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link ConfigurationTable}. * Tests for {@link Table}.
* *
* @author Brian Clozel * @author Brian Clozel
*/ */
public class ConfigurationTableTests { public class TableTests {
private static final String NEWLINE = System.lineSeparator(); private static final String NEWLINE = System.lineSeparator();
private static final Snippet SNIPPET = new Snippet("my", "title", null);
@Test @Test
void simpleTable() { void simpleTable() {
ConfigurationTable table = new ConfigurationTable("test"); Table table = new Table();
ConfigurationProperty first = new ConfigurationProperty("spring.test.prop", "java.lang.String", "something", table.addRow(new SingleRow(SNIPPET, new ConfigurationProperty("spring.test.prop", "java.lang.String",
"This is a description.", false); "something", "This is a description.", false)));
ConfigurationProperty second = new ConfigurationProperty("spring.test.other", "java.lang.String", "other value", table.addRow(new SingleRow(SNIPPET, new ConfigurationProperty("spring.test.other", "java.lang.String",
"This is another description.", false); "other value", "This is another description.", false)));
table.addEntry(new SingleConfigurationTableEntry(first)); Asciidoc asciidoc = new Asciidoc();
table.addEntry(new SingleConfigurationTableEntry(second)); table.write(asciidoc);
assertThat(table.toAsciidocTable()).isEqualTo("[cols=\"4,3,3\", options=\"header\"]" + NEWLINE + "|===" // @formatter:off
+ NEWLINE + "|Key|Default Value|Description" + NEWLINE + NEWLINE assertThat(asciidoc.toString()).isEqualTo(
+ "|[[spring.test.other]]<<spring.test.other,`+spring.test.other+`>>" + NEWLINE + "|`+other value+`" "[cols=\"4,3,3\", options=\"header\"]" + NEWLINE +
+ NEWLINE + "|+++This is another description.+++" + NEWLINE + NEWLINE "|===" + NEWLINE +
+ "|[[spring.test.prop]]<<spring.test.prop,`+spring.test.prop+`>>" + NEWLINE + "|`+something+`" "|Name|Description|Default Value" + NEWLINE + NEWLINE +
+ NEWLINE + "|+++This is a description.+++" + NEWLINE + 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. ...@@ -218,7 +218,7 @@ task documentVersionProperties(type: org.springframework.boot.build.constraints.
task documentConfigurationProperties(type: org.springframework.boot.build.context.properties.DocumentConfigurationProperties) { task documentConfigurationProperties(type: org.springframework.boot.build.context.properties.DocumentConfigurationProperties) {
configurationPropertyMetadata = configurations.configurationProperties 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) { tasks.withType(org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask) {
......
...@@ -762,3 +762,19 @@ executable-jar-alternatives=executable-jar.alternatives ...@@ -762,3 +762,19 @@ executable-jar-alternatives=executable-jar.alternatives
dependency-versions=dependency-versions dependency-versions=dependency-versions
dependency-versions-coordinates=dependency-versions.coordinates dependency-versions-coordinates=dependency-versions.coordinates
dependency-versions-properties=dependency-versions.properties 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
[appendix] [appendix]
[[application-properties]] [[application-properties]]
= Common Application properties = Common Application Properties
include::attributes.adoc[] include::attributes.adoc[]
......
[[application-properties.actuator]]
== Actuator Properties [[actuator-properties]]
include::documented-application-properties/actuator.adoc[]
[[application-properties.cache]]
== Cache Properties [[cache-properties]]
include::documented-application-properties/cache.adoc[]
[[application-properties.core]]
== Core Properties [[core-properties]]
include::documented-application-properties/core.adoc[]
[[application-properties.data-migration]]
== Data Migration Properties [[data-migration-properties]]
include::documented-application-properties/data-migration.adoc[]
[[application-properties.data]]
== Data Properties [[data-properties]]
include::documented-application-properties/data.adoc[]
[[application-properties.devtools]]
== Devtools Properties [[devtools-properties]]
include::documented-application-properties/devtools.adoc[]
[[application-properties.integration]]
== Integration Properties [[integration-properties]]
include::documented-application-properties/integration.adoc[]
[[application-properties.json]]
== JSON Properties [[json-properties]]
include::documented-application-properties/json.adoc[]
[[application-properties.mail]]
== Mail Properties [[mail-properties]]
include::documented-application-properties/mail.adoc[]
[[application-properties.rsocket]]
== RSocket Properties [[rsocket-properties]]
include::documented-application-properties/rsocket.adoc[]
[[application-properties.security]]
== Security Properties [[security-properties]]
include::documented-application-properties/security.adoc[]
[[application-properties.server]]
== Server Properties [[server-properties]]
include::documented-application-properties/server.adoc[]
[[application-properties.templating]]
== Templating Properties [[templating-properties]]
include::documented-application-properties/templating.adoc[]
[[application-properties.testing]]
== Testing Properties [[testing-properties]]
include::documented-application-properties/testing.adoc[]
[[application-properties.transaction]]
== Transaction Properties [[transaction-properties]]
include::documented-application-properties/transaction.adoc[]
[[application-properties.web]]
== Web Properties [[web-properties]]
include::documented-application-properties/web.adoc[]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment