From f7b6773b97fead35a8752f8b3979d211c630e320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Tue, 13 Sep 2022 21:10:54 +0200 Subject: [PATCH] Generate Quarkus Maven Plugin Config Docs resolves: #1204 --- .../annotation/processor/Constants.java | 2 + .../processor/generate_doc/ConfigDoc.java | 20 ++++ .../generate_doc/ConfigDocBuilder.java | 91 +++++++++++++++ .../generate_doc/ConfigDocWriter.java | 58 ++++------ .../processor/generate_doc/DocFormatter.java | 4 +- .../processor/generate_doc/JavaDocParser.java | 17 ++- .../generate_doc/MavenConfigDocBuilder.java | 98 ++++++++++++++++ .../SummaryTableDocFormatter.java | 48 +++++--- .../JavaDocConfigDescriptionParserTest.java | 6 + docs/pom.xml | 17 +++ .../main/asciidoc/quarkus-maven-plugin.adoc | 27 +++++ .../QuarkusMavenPluginDocsGenerator.java | 106 ++++++++++++++++++ 12 files changed, 437 insertions(+), 57 deletions(-) create mode 100644 core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoc.java create mode 100644 core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocBuilder.java create mode 100644 core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/MavenConfigDocBuilder.java create mode 100644 docs/src/main/asciidoc/quarkus-maven-plugin.adoc create mode 100644 docs/src/main/java/io/quarkus/docs/generation/QuarkusMavenPluginDocsGenerator.java diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java b/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java index 3f99646027c9ba..60ad1ab17f006a 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java @@ -28,6 +28,8 @@ final public class Constants { public static final String DASH = "-"; public static final String ADOC_EXTENSION = ".adoc"; public static final String DIGIT_OR_LOWERCASE = "^[a-z0-9]+$"; + public static final String NEW_LINE = "\n"; + public static final String SECTION_TITLE_L1 = "= "; public static final String PARENT = "<>"; public static final String NO_DEFAULT = "<>"; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoc.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoc.java new file mode 100644 index 00000000000000..0ad3e5a327d4d5 --- /dev/null +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoc.java @@ -0,0 +1,20 @@ +package io.quarkus.annotation.processor.generate_doc; + +import java.io.IOException; +import java.io.Writer; +import java.util.List; + +/** + * Represent one output file, its items are going to be appended to the file + */ +interface ConfigDoc { + + List getWriteItems(); + + /** + * An item is a summary table, note below the table, ... + */ + interface WriteItem { + void accept(Writer writer) throws IOException; + } +} diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocBuilder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocBuilder.java new file mode 100644 index 00000000000000..57edebb518529b --- /dev/null +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocBuilder.java @@ -0,0 +1,91 @@ +package io.quarkus.annotation.processor.generate_doc; + +import static io.quarkus.annotation.processor.Constants.SUMMARY_TABLE_ID_VARIABLE; +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; + +import io.quarkus.annotation.processor.Constants; + +/** + * {@link ConfigDoc} builder + */ +class ConfigDocBuilder { + + /** + * Declare AsciiDoc variable + */ + private static final String DECLARE_VAR = "\n:%s: %s\n"; + private final DocFormatter summaryTableDocFormatter; + protected final List writeItems = new ArrayList<>(); + + public ConfigDocBuilder() { + summaryTableDocFormatter = new SummaryTableDocFormatter(); + } + + protected ConfigDocBuilder(boolean showEnvVars) { + summaryTableDocFormatter = new SummaryTableDocFormatter(showEnvVars); + } + + /** + * Add documentation in a summary table and descriptive format + */ + public final ConfigDocBuilder addSummaryTable(String initialAnchorPrefix, boolean activateSearch, + List configDocItems, String fileName, + boolean includeConfigPhaseLegend) { + + writeItems.add(writer -> { + + // Create var with unique value for each summary table that will make DURATION_FORMAT_NOTE (see below) unique + var fileNameWithoutExtension = fileName.substring(0, fileName.length() - Constants.ADOC_EXTENSION.length()); + writer.append(String.format(DECLARE_VAR, SUMMARY_TABLE_ID_VARIABLE, fileNameWithoutExtension)); + + summaryTableDocFormatter.format(writer, initialAnchorPrefix, activateSearch, configDocItems, + includeConfigPhaseLegend); + + boolean hasDuration = false, hasMemory = false; + for (ConfigDocItem item : configDocItems) { + if (item.hasDurationInformationNote()) { + hasDuration = true; + } + + if (item.hasMemoryInformationNote()) { + hasMemory = true; + } + } + + if (hasDuration) { + writer.append(Constants.DURATION_FORMAT_NOTE); + } + + if (hasMemory) { + writer.append(Constants.MEMORY_SIZE_FORMAT_NOTE); + } + }); + return this; + } + + public boolean hasWriteItems() { + return !writeItems.isEmpty(); + } + + /** + * Passed strings are appended to the file + */ + public final ConfigDocBuilder write(String... strings) { + requireNonNull(strings); + writeItems.add(writer -> { + for (String str : strings) { + writer.append(str); + } + }); + return this; + } + + public final ConfigDoc build() { + final List docItemsCopy = List.copyOf(writeItems); + return () -> docItemsCopy; + } + +} 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 d7d99741ef3158..b2baf426709a3e 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 @@ -1,65 +1,47 @@ package io.quarkus.annotation.processor.generate_doc; -import static io.quarkus.annotation.processor.Constants.SUMMARY_TABLE_ID_VARIABLE; - import java.io.IOException; import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; import io.quarkus.annotation.processor.Constants; final public class ConfigDocWriter { - private final DocFormatter summaryTableDocFormatter = new SummaryTableDocFormatter(); - private static final String DECLARE_VAR = "\n:%s: %s\n"; /** * Write all extension configuration in AsciiDoc format in `{root}/target/asciidoc/generated/config/` directory */ public void writeAllExtensionConfigDocumentation(ConfigDocGeneratedOutput output) throws IOException { - generateDocumentation(Constants.GENERATED_DOCS_PATH.resolve(output.getFileName()), output.getAnchorPrefix(), - output.isSearchable(), output.getConfigDocItems(), output.getFileName()); - } - /** - * Generate documentation in a summary table and descriptive format - * - */ - private void generateDocumentation(Path targetPath, String initialAnchorPrefix, boolean activateSearch, - List configDocItems, String fileName) - throws IOException { - if (configDocItems.isEmpty()) { + if (output.getConfigDocItems().isEmpty()) { return; } - try (Writer writer = Files.newBufferedWriter(targetPath)) { - - // Create var with unique value for each summary table that will make DURATION_FORMAT_NOTE (see below) unique - var fileNameWithoutExtension = fileName.substring(0, fileName.length() - Constants.ADOC_EXTENSION.length()); - writer.append(String.format(DECLARE_VAR, SUMMARY_TABLE_ID_VARIABLE, fileNameWithoutExtension)); - - summaryTableDocFormatter.format(writer, initialAnchorPrefix, activateSearch, configDocItems); + // Create single summary table + final var configDocBuilder = new ConfigDocBuilder().addSummaryTable(output.getAnchorPrefix(), output.isSearchable(), + output.getConfigDocItems(), output.getFileName(), true); - boolean hasDuration = false, hasMemory = false; - for (ConfigDocItem item : configDocItems) { - if (item.hasDurationInformationNote()) { - hasDuration = true; - } - - if (item.hasMemoryInformationNote()) { - hasMemory = true; - } - } + generateDocumentation(output.getFileName(), configDocBuilder); + } - if (hasDuration) { - writer.append(Constants.DURATION_FORMAT_NOTE); - } + public void generateDocumentation(String fileName, ConfigDocBuilder configDocBuilder) throws IOException { + generateDocumentation( + // Resolve output file path + Constants.GENERATED_DOCS_PATH.resolve(fileName), + // Write all items + configDocBuilder.build()); + } - if (hasMemory) { - writer.append(Constants.MEMORY_SIZE_FORMAT_NOTE); + private void generateDocumentation(Path targetPath, ConfigDoc configDoc) + throws IOException { + try (Writer writer = Files.newBufferedWriter(targetPath)) { + for (ConfigDoc.WriteItem writeItem : configDoc.getWriteItems()) { + // Write documentation item, f.e. summary table + writeItem.accept(writer); } } } + } 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 b33fe84401bb1b..0564a820dbfc71 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,8 +61,8 @@ default String getAnchor(String string) { return string.toLowerCase(); } - void format(Writer writer, String initialAnchorPrefix, boolean activateSearch, List configDocItems) - throws IOException; + void format(Writer writer, String initialAnchorPrefix, boolean activateSearch, List configDocItems, + boolean includeConfigPhaseLegend) throws IOException; void format(Writer writer, ConfigDocKey configDocKey) throws IOException; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/JavaDocParser.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/JavaDocParser.java index 756f34b97c1053..c0afad96a5ab2c 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/JavaDocParser.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/JavaDocParser.java @@ -24,6 +24,7 @@ final class JavaDocParser { private static final Pattern START_OF_LINE = Pattern.compile("^", Pattern.MULTILINE); private static final Pattern REPLACE_WINDOWS_EOL = Pattern.compile("\r\n"); private static final Pattern REPLACE_MACOS_EOL = Pattern.compile("\r"); + private static final Pattern STARTING_SPACE = Pattern.compile("^ +"); private static final String BACKTICK = "`"; private static final String HASH = "#"; @@ -269,7 +270,21 @@ private void appendHtml(StringBuilder sb, Node node) { sb.append(NEW_LINE); break; case TEXT_NODE: - appendEscapedAsciiDoc(sb, ((TextNode) childNode).text()); + String text = ((TextNode) childNode).text(); + + if (text.isEmpty()) { + break; + } + + // Indenting the first line of a paragraph by one or more spaces makes the block literal + // Please see https://docs.asciidoctor.org/asciidoc/latest/verbatim/literal-blocks/ for more info + // This prevents literal blocks f.e. after
+ final var startingSpaceMatcher = STARTING_SPACE.matcher(text); + if (sb.length() > 0 && '\n' == sb.charAt(sb.length() - 1) && startingSpaceMatcher.find()) { + text = startingSpaceMatcher.replaceFirst(""); + } + + appendEscapedAsciiDoc(sb, text); break; default: appendHtml(sb, childNode); diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/MavenConfigDocBuilder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/MavenConfigDocBuilder.java new file mode 100644 index 00000000000000..e55a495fbc1a34 --- /dev/null +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/MavenConfigDocBuilder.java @@ -0,0 +1,98 @@ +package io.quarkus.annotation.processor.generate_doc; + +import static io.quarkus.annotation.processor.Constants.EMPTY; +import static io.quarkus.annotation.processor.Constants.NEW_LINE; +import static io.quarkus.annotation.processor.Constants.SECTION_TITLE_L1; + +import java.util.ArrayList; +import java.util.List; + +import io.quarkus.annotation.processor.Constants; + +public final class MavenConfigDocBuilder extends ConfigDocBuilder { + + public MavenConfigDocBuilder() { + super(false); + } + + private final JavaDocParser javaDocParser = new JavaDocParser(); + + public void addTableTitle(String goalTitle) { + write(SECTION_TITLE_L1, goalTitle, NEW_LINE); + } + + public void addNewLine() { + write(NEW_LINE); + } + + public void addTableDescription(String goalDescription) { + write(NEW_LINE, javaDocParser.parseConfigDescription(goalDescription), NEW_LINE); + } + + public GoalParamsBuilder newGoalParamsBuilder() { + return new GoalParamsBuilder(javaDocParser); + } + + private static abstract class TableBuilder { + + protected final List configDocItems = new ArrayList<>(); + + /** + * Section name that is displayed in a table header + */ + abstract protected String getSectionName(); + + public List build() { + + // a summary table + final ConfigDocSection parameterSection = new ConfigDocSection(); + parameterSection.setShowSection(true); + parameterSection.setName(getSectionName()); + parameterSection.setSectionDetailsTitle(getSectionName()); + parameterSection.setOptional(false); + parameterSection.setConfigDocItems(List.copyOf(configDocItems)); + + // topConfigDocItem wraps the summary table + final ConfigDocItem topConfigDocItem = new ConfigDocItem(); + topConfigDocItem.setConfigDocSection(parameterSection); + + return List.of(topConfigDocItem); + } + + public boolean tableIsNotEmpty() { + return !configDocItems.isEmpty(); + } + } + + public static final class GoalParamsBuilder extends TableBuilder { + + private final JavaDocParser javaDocParser; + + private GoalParamsBuilder(JavaDocParser javaDocParser) { + this.javaDocParser = javaDocParser; + } + + public void addParam(String type, String name, String defaultValue, boolean required, String description) { + final ConfigDocKey configDocKey = new ConfigDocKey(); + configDocKey.setType(type); + configDocKey.setKey(name); + configDocKey.setConfigPhase(ConfigPhase.RUN_TIME); + configDocKey.setDefaultValue(defaultValue == null ? Constants.EMPTY : defaultValue); + if (description != null && !description.isBlank()) { + configDocKey.setConfigDoc(javaDocParser.parseConfigDescription(description)); + } else { + configDocKey.setConfigDoc(EMPTY); + } + configDocKey.setOptional(!required); + final ConfigDocItem configDocItem = new ConfigDocItem(); + configDocItem.setConfigDocKey(configDocKey); + configDocItems.add(configDocItem); + } + + @Override + protected String getSectionName() { + return "Parameter"; + } + } + +} 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 47935389f9c42f..654b484967c479 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 @@ -1,5 +1,7 @@ package io.quarkus.annotation.processor.generate_doc; +import static io.quarkus.annotation.processor.Constants.CONFIG_PHASE_LEGEND; +import static io.quarkus.annotation.processor.Constants.NEW_LINE; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.toEnvVarName; import java.io.IOException; @@ -15,22 +17,34 @@ final class SummaryTableDocFormatter implements DocFormatter { 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 %s\n|%s\n"; private static final String SECTION_TITLE = "[[%s]]link:#%s[%s]"; + private static final String TABLE_HEADER_FORMAT = "[%s, cols=\"80,.^10,.^10\"]\n|==="; private static final String TABLE_SECTION_ROW_FORMAT = "\n\nh|%s\n%s\nh|Type\nh|Default"; - private static final String TABLE_HEADER_FORMAT = "[.configuration-legend]%s\n[%s, cols=\"80,.^10,.^10\"]\n|==="; + private final boolean showEnvVars; private String anchorPrefix = ""; + public SummaryTableDocFormatter(boolean showEnvVars) { + this.showEnvVars = showEnvVars; + } + + public SummaryTableDocFormatter() { + this(true); + } + /** * 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, boolean activateSearch, List configDocItems) + public void format(Writer writer, String initialAnchorPrefix, boolean activateSearch, + List configDocItems, boolean includeConfigPhaseLegend) throws IOException { + if (includeConfigPhaseLegend) { + writer.append("[.configuration-legend]").append(CONFIG_PHASE_LEGEND).append(NEW_LINE); + } 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); + writer.append(String.format(TABLE_HEADER_FORMAT, tableClasses)); anchorPrefix = initialAnchorPrefix; // make sure that section-less configs get a legend @@ -74,18 +88,20 @@ public void format(Writer writer, ConfigDocKey configDocKey) throws IOException String doc = configDocKey.getConfigDoc(); - // Convert a property name to an environment variable name and show it in the config description - final String envVarExample = String.format("ifdef::add-copy-button-to-env-var[]\n" + - "Environment variable: env_var_with_copy_button:+++%1$s+++[]\n" + - "endif::add-copy-button-to-env-var[]\n" + - "ifndef::add-copy-button-to-env-var[]\n" + - "Environment variable: `+++%1$s+++`\n" + - "endif::add-copy-button-to-env-var[]", toEnvVarName(configDocKey.getKey())); - if (configDocKey.getConfigDoc().isEmpty()) { - doc = envVarExample; - } else { - // Add 2 new lines in order to show the environment variable on next line - doc += TWO_NEW_LINES + envVarExample; + if (showEnvVars) { + // Convert a property name to an environment variable name and show it in the config description + final String envVarExample = String.format("ifdef::add-copy-button-to-env-var[]\n" + + "Environment variable: env_var_with_copy_button:+++%1$s+++[]\n" + + "endif::add-copy-button-to-env-var[]\n" + + "ifndef::add-copy-button-to-env-var[]\n" + + "Environment variable: `+++%1$s+++`\n" + + "endif::add-copy-button-to-env-var[]", toEnvVarName(configDocKey.getKey())); + if (configDocKey.getConfigDoc().isEmpty()) { + doc = envVarExample; + } else { + // Add 2 new lines in order to show the environment variable on next line + doc += TWO_NEW_LINES + envVarExample; + } } final String typeDetail = DocGeneratorUtil.getTypeFormatInformationNote(configDocKey); diff --git a/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/JavaDocConfigDescriptionParserTest.java b/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/JavaDocConfigDescriptionParserTest.java index 292162160a2738..b84268727d0978 100644 --- a/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/JavaDocConfigDescriptionParserTest.java +++ b/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/JavaDocConfigDescriptionParserTest.java @@ -25,6 +25,12 @@ public void parseNullJavaDoc() { assertEquals("", parsed); } + @Test + public void removeParagraphIndentation() { + String parsed = parser.parseConfigDescription("First paragraph

Second Paragraph"); + assertEquals("First paragraph\n\nSecond Paragraph", parsed); + } + @Test public void parseUntrimmedJavaDoc() { String parsed = parser.parseConfigDescription(" "); diff --git a/docs/pom.xml b/docs/pom.xml index 9d822f26545c2d..4ba712b36fe971 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -2802,6 +2802,23 @@ + + generate-quarkus-mvn-plugin-docs + process-classes + + java + + + ${skipDocs} + io.quarkus.docs.generation.QuarkusMavenPluginDocsGenerator + + ${project.basedir}/../devtools/maven/target/classes/META-INF/maven/plugin.xml + + + ${env.MAVEN_CMD_LINE_ARGS} + + + all-build-item-classes process-classes diff --git a/docs/src/main/asciidoc/quarkus-maven-plugin.adoc b/docs/src/main/asciidoc/quarkus-maven-plugin.adoc new file mode 100644 index 00000000000000..249c48287672c4 --- /dev/null +++ b/docs/src/main/asciidoc/quarkus-maven-plugin.adoc @@ -0,0 +1,27 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc +//// += Quarkus Maven Plugin + +The Quarkus Maven Plugin builds the Quarkus applications, and provides helpers to launch dev mode or build native executables. +For more information about how to use the Quarkus Maven Plugin, please refer to the xref:maven-tooling.adoc[Maven Tooling guide]. + +include::./attributes.adoc[] + +== Discover Maven goals + +Like most Maven plugins, the Quarkus Maven Plugin has a `help` goal that prints the description of the plugin, listing all available goals as well as their description. +It is also possible to print out detailed information about a goal, all its parameters and their default values. For instance, to see the help for the `create` goal, run: + +[source,shell] +---- +./mvnw quarkus:help -Ddetail -Dgoal=create +---- + +== Maven goals reference + +Here is the list of all the Quarkus Maven Plugin goals: + +include::{generated-dir}/config/quarkus-maven-plugin-goals.adoc[opts=optional, leveloffset=+2] diff --git a/docs/src/main/java/io/quarkus/docs/generation/QuarkusMavenPluginDocsGenerator.java b/docs/src/main/java/io/quarkus/docs/generation/QuarkusMavenPluginDocsGenerator.java new file mode 100644 index 00000000000000..0020be0a5c9020 --- /dev/null +++ b/docs/src/main/java/io/quarkus/docs/generation/QuarkusMavenPluginDocsGenerator.java @@ -0,0 +1,106 @@ +package io.quarkus.docs.generation; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.Reader; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.maven.plugin.descriptor.MojoDescriptor; +import org.apache.maven.plugin.descriptor.Parameter; +import org.apache.maven.plugin.descriptor.PluginDescriptor; +import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder; +import org.codehaus.plexus.util.xml.XmlStreamReader; + +import io.quarkus.annotation.processor.Constants; +import io.quarkus.annotation.processor.generate_doc.ConfigDocWriter; +import io.quarkus.annotation.processor.generate_doc.MavenConfigDocBuilder; +import io.quarkus.annotation.processor.generate_doc.MavenConfigDocBuilder.GoalParamsBuilder; + +/** + * Generates documentation for the Quarkus Maven Plugin from plugin descriptor. + */ +public class QuarkusMavenPluginDocsGenerator { + + private static final String QUARKUS_MAVEN_PLUGIN = "quarkus-maven-plugin-"; + private static final String GOALS_OUTPUT_FILE_NAME = QUARKUS_MAVEN_PLUGIN + "goals" + Constants.ADOC_EXTENSION; + private static final String GOAL_PARAMETER_ANCHOR_PREFIX = QUARKUS_MAVEN_PLUGIN + "goal-%s-"; + + public static void main(String[] args) throws Exception { + + String errorMessage = null; + + // Path to Quarkus Maven Plugin descriptor (plugin.xml) + final Path pluginXmlDescriptorPath; + if (args.length == 1) { + pluginXmlDescriptorPath = Path.of(args[0]); + } else { + pluginXmlDescriptorPath = null; + errorMessage = String.format("Expected 1 argument ('plugin.xml' file path), got %s", args.length); + } + + // Check the file exist + if (pluginXmlDescriptorPath != null + && (!Files.exists(pluginXmlDescriptorPath) || !Files.isRegularFile(pluginXmlDescriptorPath))) { + errorMessage = String.format("File does not exist: %s", pluginXmlDescriptorPath.toAbsolutePath()); + } + + // Deserialize plugin.xml to PluginDescriptor + PluginDescriptor pluginDescriptor = null; + if (errorMessage == null) { + try (Reader input = new XmlStreamReader(new FileInputStream(pluginXmlDescriptorPath.toFile()))) { + pluginDescriptor = new PluginDescriptorBuilder().build(input); + } catch (IOException e) { + errorMessage = String.format("Failed to deserialize PluginDescriptor: %s", e.getMessage()); + } + } + + // Don't generate documentation if there are no goals (shouldn't happen if correct descriptor is available) + if (pluginDescriptor != null && (pluginDescriptor.getMojos() == null || pluginDescriptor.getMojos().isEmpty())) { + errorMessage = "Found no goals"; + } + + // Don't break the build if Quarkus Maven Plugin Descriptor is not available + if (errorMessage != null) { + System.err.printf("Can't generate the documentation for the Quarkus Maven Plugin\n: %s\n", errorMessage); + return; + } + + // Build Goals documentation + final var goalsConfigDocBuilder = new MavenConfigDocBuilder(); + for (MojoDescriptor mojo : pluginDescriptor.getMojos()) { + + // Add Goal Title + goalsConfigDocBuilder.addTableTitle(mojo.getFullGoalName()); + + // Add Goal Description + if (mojo.getDescription() != null && !mojo.getDescription().isBlank()) { + goalsConfigDocBuilder.addTableDescription(mojo.getDescription()); + } + + // Collect Goal Parameters + final GoalParamsBuilder goalParamsBuilder = goalsConfigDocBuilder.newGoalParamsBuilder(); + if (mojo.getParameters() != null) { + for (Parameter parameter : mojo.getParameters()) { + goalParamsBuilder.addParam(parameter.getType(), parameter.getName(), parameter.getDefaultValue(), + parameter.isRequired(), parameter.getDescription()); + } + } + + // Add Parameters Summary Table if the goal has parameters + if (goalParamsBuilder.tableIsNotEmpty()) { + goalsConfigDocBuilder.addSummaryTable(String.format(GOAL_PARAMETER_ANCHOR_PREFIX, mojo.getGoal()), false, + goalParamsBuilder.build(), GOALS_OUTPUT_FILE_NAME, false); + + // Start next table on a new line + goalsConfigDocBuilder.addNewLine(); + } + } + + // Generate Goals documentation + if (goalsConfigDocBuilder.hasWriteItems()) { + new ConfigDocWriter().generateDocumentation(GOALS_OUTPUT_FILE_NAME, goalsConfigDocBuilder); + } + } + +}