diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java b/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java index a9799634d4e52..5ab400889ac41 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java @@ -25,7 +25,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeSet; @@ -66,9 +65,9 @@ import org.jboss.jdeparser.JType; import org.jboss.jdeparser.JTypes; -import io.quarkus.annotation.processor.generate_doc.ConfigDocItem; import io.quarkus.annotation.processor.generate_doc.ConfigDocItemScanner; import io.quarkus.annotation.processor.generate_doc.ConfigDocWriter; +import io.quarkus.annotation.processor.generate_doc.ScannedConfigDocsItemHolder; public class ExtensionAnnotationProcessor extends AbstractProcessor { @@ -236,9 +235,12 @@ public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) } try { - final Map> extensionConfigurationItems = configDocItemScanner + final ScannedConfigDocsItemHolder scannedConfigDocsItemHolder = configDocItemScanner .scanExtensionsConfigurationItems(javaDocProperties); - configDocWriter.writeExtensionConfigDocumentation(extensionConfigurationItems); + + configDocWriter.writeExtensionConfigDocumentation(scannedConfigDocsItemHolder.getAllConfigItemsPerExtension(), + true); // generate extension doc with search engine activate + configDocWriter.writeExtensionConfigDocumentation(scannedConfigDocsItemHolder.getConfigGroupConfigItems(), false); // generate config group docs with search engine deactivated } catch (IOException e) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to generate extension doc: " + e); return; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java new file mode 100644 index 0000000000000..20d68d9350fb9 --- /dev/null +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java @@ -0,0 +1,284 @@ +package io.quarkus.annotation.processor.generate_doc; + +import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.getJavaDocSiteLink; +import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.getKnownGenericType; +import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.hyphenate; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; + +import io.quarkus.annotation.processor.Constants; + +class ConfigDoItemFinder { + + private static final String NAMED_MAP_CONFIG_ITEM_FORMAT = ".\"%s\""; + private final JavaDocParser javaDocParser = new JavaDocParser(); + private final ScannedConfigDocsItemHolder holder = new ScannedConfigDocsItemHolder(); + + private final Set configRoots; + private final Map configGroups; + private final Properties javaDocProperties; + + public ConfigDoItemFinder(Set configRoots, Map configGroups, + Properties javaDocProperties) { + this.configRoots = configRoots; + this.configGroups = configGroups; + this.javaDocProperties = javaDocProperties; + } + + /** + * Find configuration items from current encountered configuration roots + */ + ScannedConfigDocsItemHolder findInMemoryConfigurationItems() { + for (ConfigRootInfo configRootInfo : configRoots) { + final TypeElement element = configRootInfo.getClazz(); + /** + * Config sections will start at level 2 i.e the section title will be prefixed with + * ('='* (N + 1)) where N is section level. + */ + final int sectionLevel = 2; + final List configDocItems = recursivelyFindConfigItems(element, configRootInfo.getName(), + configRootInfo.getConfigPhase(), false, sectionLevel); + holder.addToAllConfigItems(configRootInfo.getClazz().getQualifiedName().toString(), configDocItems); + } + + return holder; + } + + /** + * Recursively find config item found in a config root given as {@link Element} + * + * @param element - root element + * @param parentName - root name + * @param configPhase - configuration phase see {@link ConfigPhase} + * @param withinAMap - indicates if a a key is within a map or is a map configuration key + * @param sectionLevel - section sectionLevel + */ + private List recursivelyFindConfigItems(Element element, String parentName, + ConfigPhase configPhase, boolean withinAMap, int sectionLevel) { + List configDocItems = new ArrayList<>(); + for (Element enclosedElement : element.getEnclosedElements()) { + if (!enclosedElement.getKind().isField()) { + continue; + } + + boolean isStaticField = enclosedElement + .getModifiers() + .stream() + .anyMatch(Modifier.STATIC::equals); + + if (isStaticField) { + continue; + } + + String name = Constants.EMPTY; + String defaultValue = Constants.NO_DEFAULT; + String defaultValueDoc = Constants.EMPTY; + final TypeMirror typeMirror = enclosedElement.asType(); + String type = typeMirror.toString(); + List acceptedValues = null; + Element configGroup = configGroups.get(type); + ConfigDocSection configSection = null; + boolean isConfigGroup = configGroup != null; + final TypeElement clazz = (TypeElement) element; + final String fieldName = enclosedElement.getSimpleName().toString(); + final String javaDocKey = clazz.getQualifiedName().toString() + Constants.DOT + fieldName; + final List annotationMirrors = enclosedElement.getAnnotationMirrors(); + final String rawJavaDoc = javaDocProperties.getProperty(javaDocKey); + + String hyphenatedFieldName = hyphenate(fieldName); + String configDocMapKey = hyphenatedFieldName; + + for (AnnotationMirror annotationMirror : annotationMirrors) { + String annotationName = annotationMirror.getAnnotationType().toString(); + if (annotationName.equals(Constants.ANNOTATION_CONFIG_ITEM) + || annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_MAP_KEY)) { + for (Map.Entry entry : annotationMirror + .getElementValues().entrySet()) { + final String key = entry.getKey().toString(); + final String value = entry.getValue().getValue().toString(); + if (annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_MAP_KEY) && "value()".equals(key)) { + configDocMapKey = value; + } else if (annotationName.equals(Constants.ANNOTATION_CONFIG_ITEM)) { + if ("name()".equals(key)) { + switch (value) { + case Constants.HYPHENATED_ELEMENT_NAME: + name = parentName + Constants.DOT + hyphenatedFieldName; + break; + case Constants.PARENT: + name = parentName; + break; + default: + name = parentName + Constants.DOT + value; + } + } else if ("defaultValue()".equals(key)) { + defaultValue = value; + } else if ("defaultValueDocumentation()".equals(key)) { + defaultValueDoc = value; + } + } + } + } else if (annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_SECTION)) { + final JavaDocParser.SectionHolder sectionHolder = javaDocParser.parseConfigSection(rawJavaDoc, + sectionLevel); + + configSection = new ConfigDocSection(); + configSection.setWithinAMap(withinAMap); + configSection.setConfigPhase(configPhase); + configSection.setSectionDetails(sectionHolder.details); + configSection.setSectionDetailsTitle(sectionHolder.title); + configSection.setName(parentName + Constants.DOT + hyphenatedFieldName); + } + } + + if (name.isEmpty()) { + name = parentName + Constants.DOT + hyphenatedFieldName; + } + + if (Constants.NO_DEFAULT.equals(defaultValue)) { + defaultValue = Constants.EMPTY; + } + if (Constants.EMPTY.equals(defaultValue)) { + defaultValue = defaultValueDoc; + } + + if (isConfigGroup) { + List groupConfigItems = recordConfigItemsFromConfigGroup(configPhase, name, configGroup, + configSection, withinAMap, sectionLevel); + configDocItems.addAll(groupConfigItems); + } else { + final ConfigDocKey configDocKey = new ConfigDocKey(); + configDocKey.setWithinAMap(withinAMap); + boolean optional = false; + boolean list = false; + if (!typeMirror.getKind().isPrimitive()) { + DeclaredType declaredType = (DeclaredType) typeMirror; + TypeElement typeElement = (TypeElement) declaredType.asElement(); + Name qualifiedName = typeElement.getQualifiedName(); + optional = qualifiedName.toString().startsWith("java.util.Optional"); + list = qualifiedName.contentEquals("java.util.List"); + + List typeArguments = declaredType.getTypeArguments(); + if (!typeArguments.isEmpty()) { + // FIXME: this is super dodgy: we should check the type!! + if (typeArguments.size() == 2) { + final String mapKey = String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey); + type = typeArguments.get(1).toString(); + configGroup = configGroups.get(type); + name += mapKey; + + if (configGroup != null) { + name += String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey); + List groupConfigItems = recordConfigItemsFromConfigGroup(configPhase, name, + configGroup, configSection, true, sectionLevel); + configDocItems.addAll(groupConfigItems); + continue; + } else { + configDocKey.setWithinAMap(true); + } + } else { + // FIXME: this is for Optional and List + TypeMirror realTypeMirror = typeArguments.get(0); + type = simpleTypeToString(realTypeMirror); + + if (isEnumType(realTypeMirror)) { + acceptedValues = extractEnumValues(realTypeMirror); + } + } + } else { + type = simpleTypeToString(declaredType); + if (isEnumType(declaredType)) { + acceptedValues = extractEnumValues(declaredType); + } + } + } + + final String configDescription = javaDocParser.parseConfigDescription(rawJavaDoc); + + configDocKey.setKey(name); + configDocKey.setType(type); + configDocKey.setConfigPhase(configPhase); + configDocKey.setDefaultValue(defaultValue); + configDocKey.setOptional(optional); + configDocKey.setList(list); + configDocKey.setConfigDoc(configDescription); + configDocKey.setAcceptedValues(acceptedValues); + configDocKey.setJavaDocSiteLink(getJavaDocSiteLink(type)); + ConfigDocItem configDocItem = new ConfigDocItem(); + configDocItem.setConfigDocKey(configDocKey); + configDocItems.add(configDocItem); + } + } + + return configDocItems; + } + + private List recordConfigItemsFromConfigGroup(ConfigPhase configPhase, String name, Element configGroup, + ConfigDocSection configSection, boolean withinAMap, int sectionLevel) { + final List groupConfigItems; + final List configDocItems = new ArrayList<>(); + + if (configSection != null) { + final ConfigDocItem configDocItem = new ConfigDocItem(); + configDocItem.setConfigDocSection(configSection); + configDocItems.add(configDocItem); + groupConfigItems = recursivelyFindConfigItems(configGroup, name, configPhase, withinAMap, + sectionLevel + 1); + configSection.addConfigDocItems(groupConfigItems); + } else { + groupConfigItems = recursivelyFindConfigItems(configGroup, name, configPhase, withinAMap, sectionLevel); + configDocItems.addAll(groupConfigItems); + } + + String configGroupName = configGroup.asType().toString(); + List previousConfigGroupConfigItems = holder.getConfigGroupConfigItems().get(configGroupName); + if (previousConfigGroupConfigItems == null) { + holder.addConfigGroupItems(configGroupName, groupConfigItems); + } else { + previousConfigGroupConfigItems.addAll(configDocItems); + } + + return configDocItems; + } + + private String simpleTypeToString(TypeMirror typeMirror) { + if (typeMirror.getKind().isPrimitive()) { + return typeMirror.toString(); + } + + final String knownGenericType = getKnownGenericType((DeclaredType) typeMirror); + return knownGenericType != null ? knownGenericType : typeMirror.toString(); + } + + private List extractEnumValues(TypeMirror realTypeMirror) { + Element declaredTypeElement = ((DeclaredType) realTypeMirror).asElement(); + List acceptedValues = new ArrayList<>(); + + for (Element field : declaredTypeElement.getEnclosedElements()) { + if (field.getKind() == ElementKind.ENUM_CONSTANT) { + acceptedValues.add(DocGeneratorUtil.hyphenateEnumValue(field.getSimpleName().toString())); + } + } + + return acceptedValues; + } + + private boolean isEnumType(TypeMirror realTypeMirror) { + return realTypeMirror instanceof DeclaredType + && ((DeclaredType) realTypeMirror).asElement().getKind() == ElementKind.ENUM; + } +} diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java index cfb849962e13e..6e1a07b104e32 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocItemScanner.java @@ -1,8 +1,7 @@ package io.quarkus.annotation.processor.generate_doc; +import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeConfigGroupDocFileName; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeExtensionDocFileName; -import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.getJavaDocSiteLink; -import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.getKnownGenericType; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.hyphenate; import java.io.BufferedReader; @@ -10,7 +9,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -21,15 +19,9 @@ import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.Name; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -38,10 +30,8 @@ final public class ConfigDocItemScanner { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private static final String NAMED_MAP_CONFIG_ITEM_FORMAT = ".\"%s\""; private static final String IO_QUARKUS_TEST_EXTENSION_PACKAGE = "io.quarkus.extest."; - private final JavaDocParser javaDocParser = new JavaDocParser(); private final Set configRoots = new HashSet<>(); private final Set processorClassMembers = new HashSet<>(); private final Map configGroups = new HashMap<>(); @@ -109,14 +99,20 @@ public void addConfigRoot(final PackageElement pkg, TypeElement clazz) { } /** - * Return a Map structure which contains extension name as key and generated doc value. + * Return a data structure which contains two maps of config items. + * 1. A map of all extensions config items accessible via + * {@link ScannedConfigDocsItemHolder#getAllConfigItemsPerExtension()} + * 2. a map of all config groups config items accessible via {@link ScannedConfigDocsItemHolder#getConfigGroupConfigItems()} */ - public Map> scanExtensionsConfigurationItems(Properties javaDocProperties) + public ScannedConfigDocsItemHolder scanExtensionsConfigurationItems(Properties javaDocProperties) throws IOException { - final Map> inMemoryConfigurationItems = findInMemoryConfigurationItems(javaDocProperties); + ConfigDoItemFinder configDoItemFinder = new ConfigDoItemFinder(configRoots, configGroups, javaDocProperties); + ScannedConfigDocsItemHolder inMemoryScannedItemsHolder = configDoItemFinder.findInMemoryConfigurationItems(); - if (!inMemoryConfigurationItems.isEmpty()) { + Map> inMemoryConfigItemsForConfigRoot = inMemoryScannedItemsHolder + .getAllConfigItemsPerExtension(); + if (!inMemoryConfigItemsForConfigRoot.isEmpty()) { if (!Constants.GENERATED_DOCS_DIR.exists()) { Constants.GENERATED_DOCS_DIR.mkdirs(); } @@ -134,8 +130,8 @@ public Map> scanExtensionsConfigurationItems(Propert } } - if (!inMemoryConfigurationItems.isEmpty()) { - for (Map.Entry> entry : inMemoryConfigurationItems.entrySet()) { + if (!inMemoryConfigItemsForConfigRoot.isEmpty()) { + for (Map.Entry> entry : inMemoryConfigItemsForConfigRoot.entrySet()) { String serializableConfigRootDoc = OBJECT_MAPPER.writeValueAsString(entry.getValue()); allExtensionGeneratedDocs.put(entry.getKey(), serializableConfigRootDoc); } @@ -149,10 +145,10 @@ public Map> scanExtensionsConfigurationItems(Propert } } - final Map> foundExtensionConfigurationItems = new HashMap<>(); + final ScannedConfigDocsItemHolder foundExtensionConfigurationItems = new ScannedConfigDocsItemHolder(); for (String member : processorClassMembers) { - List configDocItems = inMemoryConfigurationItems.get(member); + List configDocItems = inMemoryConfigItemsForConfigRoot.get(member); if (configDocItems == null) { final String serializedContent = allExtensionGeneratedDocs.getProperty(member); if (serializedContent == null) { @@ -163,15 +159,21 @@ public Map> scanExtensionsConfigurationItems(Propert } final String fileName = computeExtensionDocFileName(member); - final List existingConfigDocItems = foundExtensionConfigurationItems.get(fileName); + final List existingConfigDocItems = foundExtensionConfigurationItems.getConfigItemsByName(fileName); if (existingConfigDocItems == null) { - foundExtensionConfigurationItems.put(fileName, configDocItems); + foundExtensionConfigurationItems.addToAllConfigItems(fileName, configDocItems); } else { DocGeneratorUtil.appendConfigItemsIntoExistingOnes(existingConfigDocItems, configDocItems); } } + Map> configItemsPerConfigGroup = inMemoryScannedItemsHolder.getConfigGroupConfigItems(); + for (Map.Entry> entry : configItemsPerConfigGroup.entrySet()) { + final String extensionDocFileName = computeConfigGroupDocFileName(entry.getKey()); + foundExtensionConfigurationItems.addConfigGroupItems(extensionDocFileName, entry.getValue()); + } + return foundExtensionConfigurationItems; } @@ -211,243 +213,9 @@ public Map> loadAllExtensionsConfigurationItems() return foundExtensionConfigurationItems; } - /** - * Find configuration items from current encountered configuration roots - */ - private Map> findInMemoryConfigurationItems(Properties javaDocProperties) { - final Map> configOutput = new HashMap<>(); - - for (ConfigRootInfo configRootInfo : configRoots) { - final TypeElement element = configRootInfo.getClazz(); - final List configDocItems = new ArrayList<>(); - /** - * Config sections will start at level 2 i.e the section title will be prefixed with - * ('='* (N + 1)) where N is section level. - */ - final int sectionLevel = 2; - recordConfigItems(configDocItems, element, configRootInfo.getName(), configRootInfo.getConfigPhase(), - javaDocProperties, false, sectionLevel); - configOutput.put(configRootInfo.getClazz().getQualifiedName().toString(), configDocItems); - } - - return configOutput; - } - - /** - * Recursively record config item found in a config root given as {@link Element} - * - * @param configDocItems - all found config items - * @param element - root element - * @param parentName - root name - * @param configPhase - configuration phase see {@link ConfigPhase} - * @param javaDocProperties - java doc - * @param withinAMap - indicates if a a key is within a map or is a map configuration key - * @param sectionLevel - section sectionLevel - */ - private void recordConfigItems(List configDocItems, Element element, String parentName, - ConfigPhase configPhase, Properties javaDocProperties, boolean withinAMap, int sectionLevel) { - for (Element enclosedElement : element.getEnclosedElements()) { - if (!enclosedElement.getKind().isField()) { - continue; - } - - boolean isStaticField = enclosedElement - .getModifiers() - .stream() - .anyMatch(Modifier.STATIC::equals); - - if (isStaticField) { - continue; - } - - String name = Constants.EMPTY; - String defaultValue = Constants.NO_DEFAULT; - String defaultValueDoc = Constants.EMPTY; - final TypeMirror typeMirror = enclosedElement.asType(); - String type = typeMirror.toString(); - List acceptedValues = null; - Element configGroup = configGroups.get(type); - ConfigDocSection configSection = null; - boolean isConfigGroup = configGroup != null; - final TypeElement clazz = (TypeElement) element; - final String fieldName = enclosedElement.getSimpleName().toString(); - final String javaDocKey = clazz.getQualifiedName().toString() + Constants.DOT + fieldName; - final List annotationMirrors = enclosedElement.getAnnotationMirrors(); - final String rawJavaDoc = javaDocProperties.getProperty(javaDocKey); - - String hyphenatedFieldName = hyphenate(fieldName); - String configDocMapKey = hyphenatedFieldName; - - for (AnnotationMirror annotationMirror : annotationMirrors) { - String annotationName = annotationMirror.getAnnotationType().toString(); - if (annotationName.equals(Constants.ANNOTATION_CONFIG_ITEM) - || annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_MAP_KEY)) { - for (Map.Entry entry : annotationMirror - .getElementValues().entrySet()) { - final String key = entry.getKey().toString(); - final String value = entry.getValue().getValue().toString(); - if (annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_MAP_KEY) && "value()".equals(key)) { - configDocMapKey = value; - } else if (annotationName.equals(Constants.ANNOTATION_CONFIG_ITEM)) { - if ("name()".equals(key)) { - switch (value) { - case Constants.HYPHENATED_ELEMENT_NAME: - name = parentName + Constants.DOT + hyphenatedFieldName; - break; - case Constants.PARENT: - name = parentName; - break; - default: - name = parentName + Constants.DOT + value; - } - } else if ("defaultValue()".equals(key)) { - defaultValue = value; - } else if ("defaultValueDocumentation()".equals(key)) { - defaultValueDoc = value; - } - } - } - } else if (annotationName.equals(Constants.ANNOTATION_CONFIG_DOC_SECTION)) { - final JavaDocParser.SectionHolder sectionHolder = javaDocParser.parseConfigSection(rawJavaDoc, - sectionLevel); - - configSection = new ConfigDocSection(); - configSection.setWithinAMap(withinAMap); - configSection.setConfigPhase(configPhase); - configSection.setSectionDetails(sectionHolder.details); - configSection.setSectionDetailsTitle(sectionHolder.title); - configSection.setName(parentName + Constants.DOT + hyphenatedFieldName); - } - } - - if (name.isEmpty()) { - name = parentName + Constants.DOT + hyphenatedFieldName; - } - - if (Constants.NO_DEFAULT.equals(defaultValue)) { - defaultValue = Constants.EMPTY; - } - if (Constants.EMPTY.equals(defaultValue)) { - defaultValue = defaultValueDoc; - } - - if (isConfigGroup) { - recordSectionConfigItems(configDocItems, configPhase, javaDocProperties, name, configGroup, configSection, - withinAMap, sectionLevel); - } else { - final ConfigDocKey configDocKey = new ConfigDocKey(); - configDocKey.setWithinAMap(withinAMap); - boolean optional = false; - boolean list = false; - if (!typeMirror.getKind().isPrimitive()) { - DeclaredType declaredType = (DeclaredType) typeMirror; - TypeElement typeElement = (TypeElement) declaredType.asElement(); - Name qualifiedName = typeElement.getQualifiedName(); - - if (qualifiedName.contentEquals("java.util.Optional") - || qualifiedName.contentEquals("java.util.OptionalInt") - || qualifiedName.contentEquals("java.util.OptionalDouble") - || qualifiedName.contentEquals("java.util.OptionalLong")) { - optional = true; - } else if (qualifiedName.contentEquals("java.util.List")) { - list = true; - } - List typeArguments = declaredType.getTypeArguments(); - if (!typeArguments.isEmpty()) { - // FIXME: this is super dodgy: we should check the type!! - if (typeArguments.size() == 2) { - final String mapKey = String.format(NAMED_MAP_CONFIG_ITEM_FORMAT, configDocMapKey); - type = typeArguments.get(1).toString(); - configGroup = configGroups.get(type); - name += mapKey; - - if (configGroup != null) { - recordSectionConfigItems(configDocItems, configPhase, javaDocProperties, name, configGroup, - configSection, true, sectionLevel); - continue; - } else { - configDocKey.setWithinAMap(true); - } - } else { - // FIXME: this is for Optional and List - TypeMirror realTypeMirror = typeArguments.get(0); - type = simpleTypeToString(realTypeMirror); - - if (isEnumType(realTypeMirror)) { - acceptedValues = extractEnumValues(realTypeMirror); - } - } - } else { - type = simpleTypeToString(declaredType); - if (isEnumType(declaredType)) { - acceptedValues = extractEnumValues(declaredType); - } - } - } - - final String configDescription = javaDocParser.parseConfigDescription(rawJavaDoc); - - configDocKey.setKey(name); - configDocKey.setType(type); - configDocKey.setConfigPhase(configPhase); - configDocKey.setDefaultValue(defaultValue); - configDocKey.setOptional(optional); - configDocKey.setList(list); - configDocKey.setConfigDoc(configDescription); - configDocKey.setAcceptedValues(acceptedValues); - configDocKey.setJavaDocSiteLink(getJavaDocSiteLink(type)); - ConfigDocItem configDocItem = new ConfigDocItem(); - configDocItem.setConfigDocKey(configDocKey); - configDocItems.add(configDocItem); - } - } - } - - private void recordSectionConfigItems(List configDocItems, ConfigPhase configPhase, - Properties javaDocProperties, - String name, Element configGroup, ConfigDocSection configSection, boolean withinAMap, int sectionLevel) { - if (configSection != null) { - final ConfigDocItem configDocItem = new ConfigDocItem(); - configDocItem.setConfigDocSection(configSection); - configDocItems.add(configDocItem); - recordConfigItems(configSection.getConfigDocItems(), configGroup, name, configPhase, javaDocProperties, withinAMap, - sectionLevel + 1); - } else { - recordConfigItems(configDocItems, configGroup, name, configPhase, javaDocProperties, withinAMap, sectionLevel); - } - } - - private String simpleTypeToString(TypeMirror typeMirror) { - if (typeMirror.getKind().isPrimitive()) { - return typeMirror.toString(); - } - - final String knownGenericType = getKnownGenericType((DeclaredType) typeMirror); - return knownGenericType != null ? knownGenericType : typeMirror.toString(); - } - - private List extractEnumValues(TypeMirror realTypeMirror) { - Element declaredTypeElement = ((DeclaredType) realTypeMirror).asElement(); - List acceptedValues = new ArrayList<>(); - - for (Element field : declaredTypeElement.getEnclosedElements()) { - if (field.getKind() == ElementKind.ENUM_CONSTANT) { - acceptedValues.add(DocGeneratorUtil.hyphenateEnumValue(field.getSimpleName().toString())); - } - } - - return acceptedValues; - } - - private boolean isEnumType(TypeMirror realTypeMirror) { - return realTypeMirror instanceof DeclaredType - && ((DeclaredType) realTypeMirror).asElement().getKind() == ElementKind.ENUM; - } - @Override public String toString() { return "ConfigDocItemScanner{" + - "javaDocParser=" + javaDocParser + ", configRoots=" + configRoots + ", processorClassMembers=" + processorClassMembers + ", configGroups=" + configGroups + diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocWriter.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocWriter.java index d273b5025007e..317aeb36d26c2 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocWriter.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocWriter.java @@ -14,29 +14,32 @@ final public class ConfigDocWriter { private final DocFormatter summaryTableDocFormatter = new SummaryTableDocFormatter(); /** - * Write extension configuration AsciiDoc format in `{root}/target/asciidoc/generated/config/` + * Write configuration in AsciiDoc format in `{root}/target/asciidoc/generated/config/` directory */ - public void writeExtensionConfigDocumentation(Map> extensionsConfigurations) + public void writeExtensionConfigDocumentation(Map> extensionsConfigurations, + boolean withSearchActivated) throws IOException { for (Map.Entry> entry : extensionsConfigurations.entrySet()) { final List configDocItems = entry.getValue(); - final String extensionFileName = entry.getKey(); + final String fileName = entry.getKey(); sort(configDocItems); - String anchorPrefix = extensionFileName; - if (extensionFileName.endsWith(".adoc")) + String anchorPrefix = fileName; + if (fileName.endsWith(Constants.ADOC_EXTENSION)) { anchorPrefix = anchorPrefix.substring(0, anchorPrefix.length() - 5); + } - generateDocumentation(Constants.GENERATED_DOCS_PATH.resolve(extensionFileName), anchorPrefix + "_", configDocItems); + generateDocumentation(Constants.GENERATED_DOCS_PATH.resolve(fileName), anchorPrefix + "_", withSearchActivated, + configDocItems); } } /** - * Write all extension configuration AsciiDoc format in `{root}/target/asciidoc/generated/config/` + * Write all extension configuration in AsciiDoc format in `{root}/target/asciidoc/generated/config/` directory */ public void writeAllExtensionConfigDocumentation(List allItems) throws IOException { - generateDocumentation(Constants.GENERATED_DOCS_PATH.resolve("all-config.adoc"), "", allItems); + generateDocumentation(Constants.GENERATED_DOCS_PATH.resolve("all-config.adoc"), "", true, allItems); } /** @@ -63,18 +66,20 @@ public static void sort(List configDocItems) { * @param configDocItems * @throws IOException */ - private void generateDocumentation(Path targetPath, String initialAnchorPrefix, List configDocItems) + private void generateDocumentation(Path targetPath, String initialAnchorPrefix, boolean activateSearch, + List configDocItems) throws IOException { try (Writer writer = Files.newBufferedWriter(targetPath)) { - summaryTableDocFormatter.format(writer, initialAnchorPrefix, configDocItems); + summaryTableDocFormatter.format(writer, initialAnchorPrefix, activateSearch, configDocItems); boolean hasDuration = false, hasMemory = false; for (ConfigDocItem item : configDocItems) { - if (item.hasDurationInformationNote()) + if (item.hasDurationInformationNote()) { hasDuration = true; - if (item.hasMemoryInformationNote()) + } + if (item.hasMemoryInformationNote()) { hasMemory = true; - + } } if (hasDuration) { writer.append(Constants.DURATION_FORMAT_NOTE); diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocFormatter.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocFormatter.java index 225ac6dabc52b..b33fe84401bb1 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocFormatter.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocFormatter.java @@ -61,7 +61,8 @@ default String getAnchor(String string) { return string.toLowerCase(); } - void format(Writer writer, String initialAnchorPrefix, List configDocItems) throws IOException; + void format(Writer writer, String initialAnchorPrefix, boolean activateSearch, List configDocItems) + throws IOException; void format(Writer writer, ConfigDocKey configDocKey) throws IOException; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java index 70e4e183d2fb5..50f5590e0dfd1 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtil.java @@ -16,6 +16,7 @@ import io.quarkus.annotation.processor.Constants; public class DocGeneratorUtil { + private static String CONFIG_GROUP_PREFIX = "config-group-"; static final String VERTX_JAVA_DOC_SITE = "https://vertx.io/docs/apidocs/"; static final String OFFICIAL_JAVA_DOC_BASE_LINK = "https://docs.oracle.com/javase/8/docs/api/"; static final String AGROAL_API_JAVA_DOC_SITE = "https://jar-download.com/javaDoc/io.agroal/agroal-api/1.5/index.html?"; @@ -256,44 +257,65 @@ static boolean hasMemoryInformationNote(ConfigDocKey configDocKey) { } /** - * Guess extension name from given configuration root file + * Guess extension name from given configuration root class name */ public static String computeExtensionDocFileName(String configRoot) { + StringBuilder extensionNameBuilder = new StringBuilder(); final Matcher matcher = Constants.PKG_PATTERN.matcher(configRoot); if (!matcher.find()) { - return configRoot + Constants.ADOC_EXTENSION; + extensionNameBuilder.append(configRoot); + } else { + String extensionName = matcher.group(1); + final String subgroup = matcher.group(2); + extensionNameBuilder.append(Constants.QUARKUS); + extensionNameBuilder.append(Constants.DASH); + + if (Constants.DEPLOYMENT.equals(extensionName) || Constants.RUNTIME.equals(extensionName)) { + final String configClass = configRoot.substring(configRoot.lastIndexOf(Constants.DOT) + 1); + extensionName = hyphenate(configClass); + extensionNameBuilder.append(Constants.CORE); + extensionNameBuilder.append(extensionName); + } else if (subgroup != null && !Constants.DEPLOYMENT.equals(subgroup) + && !Constants.RUNTIME.equals(subgroup) && !Constants.COMMON.equals(subgroup) + && subgroup.matches(Constants.DIGIT_OR_LOWERCASE)) { + extensionNameBuilder.append(extensionName); + extensionNameBuilder.append(Constants.DASH); + extensionNameBuilder.append(subgroup); + + final String qualifier = matcher.group(3); + if (qualifier != null && !Constants.DEPLOYMENT.equals(qualifier) + && !Constants.RUNTIME.equals(qualifier) && !Constants.COMMON.equals(qualifier) + && qualifier.matches(Constants.DIGIT_OR_LOWERCASE)) { + extensionNameBuilder.append(Constants.DASH); + extensionNameBuilder.append(qualifier); + } + } else { + extensionNameBuilder.append(extensionName); + } } - String extensionName = matcher.group(1); - final String subgroup = matcher.group(2); - final StringBuilder key = new StringBuilder(Constants.QUARKUS); - key.append(Constants.DASH); - - if (Constants.DEPLOYMENT.equals(extensionName) || Constants.RUNTIME.equals(extensionName)) { - final String configClass = configRoot.substring(configRoot.lastIndexOf(Constants.DOT) + 1); - extensionName = hyphenate(configClass); - key.append(Constants.CORE); - key.append(extensionName); - } else if (subgroup != null && !Constants.DEPLOYMENT.equals(subgroup) - && !Constants.RUNTIME.equals(subgroup) && !Constants.COMMON.equals(subgroup) - && subgroup.matches(Constants.DIGIT_OR_LOWERCASE)) { - key.append(extensionName); - key.append(Constants.DASH); - key.append(subgroup); - - final String qualifier = matcher.group(3); - if (qualifier != null && !Constants.DEPLOYMENT.equals(qualifier) - && !Constants.RUNTIME.equals(qualifier) && !Constants.COMMON.equals(qualifier) - && qualifier.matches(Constants.DIGIT_OR_LOWERCASE)) { - key.append(Constants.DASH); - key.append(qualifier); - } - } else { - key.append(extensionName); + extensionNameBuilder.append(Constants.ADOC_EXTENSION); + return extensionNameBuilder.toString(); + } + + /** + * Guess config group file name from given configuration group class name + */ + public static String computeConfigGroupDocFileName(String configGroup) { + final Matcher matcher = Constants.PKG_PATTERN.matcher(configGroup); + if (!matcher.find()) { + return CONFIG_GROUP_PREFIX + hyphenate(configGroup) + Constants.ADOC_EXTENSION; } - key.append(Constants.ADOC_EXTENSION); - return key.toString(); + String replacement = Constants.DASH + CONFIG_GROUP_PREFIX; + String sanitizedClassName = configGroup + .replaceFirst("io.", "") + .replaceFirst("\\.runtime\\.", replacement) + .replaceFirst("\\.deployment\\.", replacement); + + return hyphenate(sanitizedClassName) + .replaceAll("[\\.-]+", Constants.DASH) + + Constants.ADOC_EXTENSION; } public static void appendConfigItemsIntoExistingOnes(List existingConfigItems, diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ScannedConfigDocsItemHolder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ScannedConfigDocsItemHolder.java new file mode 100644 index 0000000000000..0251cb84bf20e --- /dev/null +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ScannedConfigDocsItemHolder.java @@ -0,0 +1,33 @@ +package io.quarkus.annotation.processor.generate_doc; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +final public class ScannedConfigDocsItemHolder { + private final Map> allConfigItemsPerExtension = new HashMap<>(); + private final Map> configGroupConfigItems = new HashMap<>(); + + public ScannedConfigDocsItemHolder() { + } + + public Map> getAllConfigItemsPerExtension() { + return allConfigItemsPerExtension; + } + + public Map> getConfigGroupConfigItems() { + return configGroupConfigItems; + } + + public void addToAllConfigItems(String configRootName, List configDocItems) { + allConfigItemsPerExtension.put(configRootName, configDocItems); + } + + public void addConfigGroupItems(String configGroupName, List configDocItems) { + configGroupConfigItems.put(configGroupName, configDocItems); + } + + public boolean isEmpty() { + return allConfigItemsPerExtension.isEmpty(); + } +} diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java index 40f65d4e291d9..b87e226ac9c4f 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java @@ -8,22 +8,25 @@ final class SummaryTableDocFormatter implements DocFormatter { private static final String TABLE_CLOSING_TAG = "\n|==="; + public static final String SEARCHABLE_TABLE_CLASS = ".searchable"; // a css class indicating if a table is searchable + public static final String CONFIGURATION_TABLE_CLASS = ".configuration-reference"; private static final String TABLE_ROW_FORMAT = "\n\na|%s [[%s]]`link:#%s[%s]`\n\n[.description]\n--\n%s\n--|%s %s\n|%s\n"; private static final String TABLE_SECTION_ROW_FORMAT = "\n\nh|[[%s]]link:#%s[%s]\nh|Type\nh|Default"; - private static final String TABLE_HEADER_FORMAT = "[.configuration-legend]%s\n[.configuration-reference, cols=\"80,.^10,.^10\"]\n|==="; + private static final String TABLE_HEADER_FORMAT = "[.configuration-legend]%s\n[%s, cols=\"80,.^10,.^10\"]\n|==="; // private static final String MORE_INFO_ABOUT_SECTION_FORMAT = "link:#%s[icon:plus-circle[], title=More information about %s]"; private String anchorPrefix = ""; /** - * Generate configuration keys in table format. - * Generated table will contain a key column that points to the long descriptive format. - * - * @param configDocItems + * Generate configuration keys in table format with search engine activated or not. + * Useful when we want to optionally activate or deactivate search engine */ @Override - public void format(Writer writer, String initialAnchorPrefix, List configDocItems) throws IOException { - final String tableHeaders = String.format(TABLE_HEADER_FORMAT, Constants.CONFIG_PHASE_LEGEND); + public void format(Writer writer, String initialAnchorPrefix, boolean activateSearch, List configDocItems) + throws IOException { + String searchableClass = activateSearch ? SEARCHABLE_TABLE_CLASS : Constants.EMPTY; + String tableClasses = CONFIGURATION_TABLE_CLASS + searchableClass; + final String tableHeaders = String.format(TABLE_HEADER_FORMAT, Constants.CONFIG_PHASE_LEGEND, tableClasses); writer.append(tableHeaders); anchorPrefix = initialAnchorPrefix; diff --git a/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java b/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java index 39ced7c4ff749..3ff77a75b2fb4 100644 --- a/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java +++ b/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/DocGeneratorUtilTest.java @@ -4,6 +4,7 @@ import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.OFFICIAL_JAVA_DOC_BASE_LINK; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.VERTX_JAVA_DOC_SITE; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.appendConfigItemsIntoExistingOnes; +import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeConfigGroupDocFileName; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.computeExtensionDocFileName; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.getJavaDocSiteLink; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -122,7 +123,7 @@ public void shouldReturnEmptyLinkIfUnknownJavaDocType() { } @Test - public void shouldReturnConfigRootName() { + public void shouldReturnConfigRootNameWhenComputingExtensionName() { String configRoot = "org.acme.ConfigRoot"; String expected = "org.acme.ConfigRoot.adoc"; String fileName = computeExtensionDocFileName(configRoot); @@ -148,7 +149,7 @@ public void shouldAddCoreInComputedExtensionName() { } @Test - public void shouldGuessArtifactId() { + public void shouldGuessArtifactIdWhenComputingExtensionName() { String configRoot = "io.quarkus.agroal.Config"; String expected = "quarkus-agroal.adoc"; String fileName = computeExtensionDocFileName(configRoot); @@ -165,6 +166,39 @@ public void shouldGuessArtifactId() { assertEquals(expected, fileName); } + @Test + public void shouldUseHyphenatedClassNameWithoutRuntimeOrDeploymentNamespaceWhenComputingConfigGroupFileName() { + String configRoot = "ClassName"; + String expected = "config-group-class-name.adoc"; + String fileName = computeConfigGroupDocFileName(configRoot); + assertEquals(expected, fileName); + + configRoot = "io.quarkus.agroal.ConfigGroup"; + expected = "quarkus-agroal-config-group.adoc"; + fileName = computeConfigGroupDocFileName(configRoot); + assertEquals(expected, fileName); + + configRoot = "io.quarkus.agroal.runtime.ClassName"; + expected = "quarkus-agroal-config-group-class-name.adoc"; + fileName = computeConfigGroupDocFileName(configRoot); + assertEquals(expected, fileName); + + configRoot = "io.quarkus.keycloak.deployment.RealmConfig"; + expected = "quarkus-keycloak-config-group-realm-config.adoc"; + fileName = computeConfigGroupDocFileName(configRoot); + assertEquals(expected, fileName); + + configRoot = "io.quarkus.extension.deployment.BuildTimeConfig"; + expected = "quarkus-extension-config-group-build-time-config.adoc"; + fileName = computeConfigGroupDocFileName(configRoot); + assertEquals(expected, fileName); + + configRoot = "io.quarkus.extension.deployment.name.BuildTimeConfig"; + expected = "quarkus-extension-config-group-name-build-time-config.adoc"; + fileName = computeConfigGroupDocFileName(configRoot); + assertEquals(expected, fileName); + } + @Test public void shouldPreserveExistingConfigItemsWhenAppendAnEmptyConfigItems() { List existingConfigItems = Arrays.asList(new ConfigDocItem(), new ConfigDocItem()); diff --git a/docs/src/main/asciidoc/javascript/config.js b/docs/src/main/asciidoc/javascript/config.js index 8090c9475336e..5e720c3929f64 100644 --- a/docs/src/main/asciidoc/javascript/config.js +++ b/docs/src/main/asciidoc/javascript/config.js @@ -10,20 +10,23 @@ if(tables){ var idx = 0; for (var table of tables) { var caption = table.previousElementSibling; - var input = document.createElement("input"); - input.setAttribute("type", "search"); - input.setAttribute("placeholder", "filter configuration"); - input.id = "config-search-"+(idx++); - caption.children.item(0).appendChild(input); - input.addEventListener("keyup", initiateSearch); - input.addEventListener("input", initiateSearch); - inputs[input.id] = {"table": table}; - var descriptions = table.querySelectorAll(".description"); - if(descriptions){ + if (table.classList.contains('searchable')) { // activate search engine only when needed + var input = document.createElement("input"); + input.setAttribute("type", "search"); + input.setAttribute("placeholder", "filter configuration"); + input.id = "config-search-"+(idx++); + caption.children.item(0).appendChild(input); + input.addEventListener("keyup", initiateSearch); + input.addEventListener("input", initiateSearch); + inputs[input.id] = {"table": table}; + var descriptions = table.querySelectorAll(".description"); + if(descriptions){ for (description of descriptions){ - makeCollapsible(input, description); + makeCollapsible(input, description); } + } } + var rowIdx = 0; for (var row of table.querySelectorAll("tr")) { var heads = row.querySelectorAll("th"); @@ -58,7 +61,7 @@ function highlight(element, text){ var elementText = n.nodeValue; if(elementText == undefined) continue; - var elementTextLC = elementText.toLowerCase(); + var elementTextLC = elementText.toLowerCase(); var index = elementTextLC.indexOf(text); if(index != -1 && acceptTextForSearch(n)){ @@ -153,10 +156,10 @@ function reinstallClickHandlers(input, table){ var decoration = content.lastElementChild; var iconDecoration = decoration.children.item(0); var collapsibleSpan = decoration.children.item(1); - var collapsibleHandler = makeCollapsibleHandler(input, descDiv, td, row, - collapsibleSpan, + var collapsibleHandler = makeCollapsibleHandler(input, descDiv, td, row, + collapsibleSpan, iconDecoration); - + row.addEventListener("click", collapsibleHandler); } } @@ -206,7 +209,7 @@ function applySearch(table, search, autoExpand){ if(!search){ row.style.removeProperty("display"); // recollapse when searching is over - if(autoExpand + if(autoExpand && row.classList.contains("row-collapsible") && !row.classList.contains("row-collapsed")) row.click(); @@ -268,7 +271,7 @@ function makeCollapsible(input, descDiv){ collapsibleSpan.appendChild(document.createTextNode("Show more")); descDecoration.appendChild(collapsibleSpan); - var collapsibleHandler = makeCollapsibleHandler(input, descDiv, td, row, + var collapsibleHandler = makeCollapsibleHandler(input, descDiv, td, row, collapsibleSpan, iconDecoration); @@ -284,10 +287,10 @@ function makeCollapsible(input, descDiv){ }; -function makeCollapsibleHandler(input, descDiv, td, row, +function makeCollapsibleHandler(input, descDiv, td, row, collapsibleSpan, iconDecoration) { - + return function(event) { var target = event.target; if( (target.localName == 'a' || getAncestor(target, "a"))) { @@ -310,4 +313,4 @@ function makeCollapsibleHandler(input, descDiv, td, row, } -}); \ No newline at end of file +}); diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index c59c723d940c3..40c4a60741930 100644 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -2363,6 +2363,15 @@ This is simple, include the generated documentation in your guide: \include::{generated-dir}/config/quarkus-your-extension.adoc[opts=optional, leveloffset=+1] ---- +If you are interested in including the generated documentation for the config group, you can use the include statement below +[source,asciidoc] +---- +\include::{generated-dir}/config/hyphenated-config-group-class-name-with-runtime-or-deployment-namespace-replaced-by-config-group-namespace.adoc[opts=optional, leveloffset=+1] +---- + +For example, the `io.quarkus.vertx.http.runtime.FormAuthConfig` configuration group will be generated in a file named `quarkus-vertx-http-config-group-form-auth-config.adoc`. + + A few recommendations: * `opts=optional` is mandatory as we don't want the build to fail if only part of the configuration documentation has been generated