This is an automated email from the ASF dual-hosted git repository. vy pushed a commit to branch feature/docgen-multi-template in repository https://gitbox.apache.org/repos/asf/logging-log4j-tools.git
commit 3ede3369104fc91db1adffaff8b01ae8f5ae9d2c Author: Volkan Yazıcı <vol...@yazi.ci> AuthorDate: Wed May 8 14:26:09 2024 +0200 Support multiple templates in `log4j-docgen:generate-documentation` --- .../docgen/maven/DocumentationGeneratorMojo.java | 20 +++--- .../docgen/generator/DocumentationGenerator.java | 18 +++-- .../generator/DocumentationGeneratorArgs.java | 12 ++-- .../docgen/generator/DocumentationTemplate.java | 19 ++++++ .../generator/DocumentationGeneratorTest.java | 9 +-- log4j-tools-internal-freemarker-util/pom.xml | 14 ++++ .../internal/freemarker/util/FreeMarkerUtils.java | 70 ++++++++++++++++++-- .../freemarker/util/FreeMarkerUtilsTest.java | 77 ++++++++++++++++++++++ .../.0.x.x/add-freemarker-stop-method.xml | 9 +++ .../.0.x.x/add-multi-doc-generator-template.xml | 8 +++ .../pages/log4j-docgen-asciidoctor-extension.adoc | 1 + .../ROOT/pages/log4j-docgen-maven-plugin.adoc | 24 ++++--- 12 files changed, 242 insertions(+), 39 deletions(-) diff --git a/log4j-docgen-maven-plugin/src/main/java/org/apache/logging/log4j/docgen/maven/DocumentationGeneratorMojo.java b/log4j-docgen-maven-plugin/src/main/java/org/apache/logging/log4j/docgen/maven/DocumentationGeneratorMojo.java index bbb5c66..5d0ce40 100644 --- a/log4j-docgen-maven-plugin/src/main/java/org/apache/logging/log4j/docgen/maven/DocumentationGeneratorMojo.java +++ b/log4j-docgen-maven-plugin/src/main/java/org/apache/logging/log4j/docgen/maven/DocumentationGeneratorMojo.java @@ -17,8 +17,10 @@ package org.apache.logging.log4j.docgen.maven; import java.io.File; +import java.util.Arrays; import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.apache.logging.log4j.docgen.PluginSet; import org.apache.logging.log4j.docgen.generator.DocumentationGenerator; import org.apache.logging.log4j.docgen.generator.DocumentationGeneratorArgs; @@ -44,14 +46,14 @@ public class DocumentationGeneratorMojo extends AbstractDocgenMojo { /** * The template that will be used to document all types. */ - @Parameter(required = true) - private DocumentationTemplateMojo indexTemplate; + @Parameter + private DocumentationTemplateMojo[] indexTemplates; /** * The template that will be used to document types. */ - @Parameter(required = true) - private DocumentationTemplateMojo typeTemplate; + @Parameter + private DocumentationTemplateMojo[] typeTemplates; @Override public void execute() { @@ -66,12 +68,14 @@ public class DocumentationGeneratorMojo extends AbstractDocgenMojo { pluginSets, classNameFilter, templateDirectory.toPath(), - toApiModel(indexTemplate), - toApiModel(typeTemplate)); + toApiModel(indexTemplates), + toApiModel(typeTemplates)); DocumentationGenerator.generateDocumentation(generatorArgs); } - private static DocumentationTemplate toApiModel(final DocumentationTemplateMojo mojo) { - return new DocumentationTemplate(mojo.source, mojo.target); + private static Set<DocumentationTemplate> toApiModel(final DocumentationTemplateMojo[] mojos) { + return Arrays.stream(mojos) + .map(mojo -> new DocumentationTemplate(mojo.source, mojo.target)) + .collect(Collectors.toSet()); } } diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/generator/DocumentationGenerator.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/generator/DocumentationGenerator.java index 9d26559..72dd7da 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/generator/DocumentationGenerator.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/generator/DocumentationGenerator.java @@ -22,6 +22,7 @@ import static org.apache.logging.log4j.tools.internal.freemarker.util.FreeMarker import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.logging.log4j.docgen.PluginSet; @@ -39,20 +40,22 @@ public final class DocumentationGenerator { .collect(Collectors.toList()); final TypeLookup lookup = TypeLookup.of(extendedSets, args.classNameFilter); lookup.values() - .forEach(sourcedType -> renderType(sourcedType, lookup, args.templateDirectory, args.typeTemplate)); - renderIndex(lookup, args.templateDirectory, args.indexTemplate); + .forEach(sourcedType -> renderType(sourcedType, lookup, args.templateDirectory, args.typeTemplates)); + renderIndex(lookup, args.templateDirectory, args.indexTemplates); } private static void renderType( final ArtifactSourcedType sourcedType, final TypeLookup lookup, final Path templateDirectory, - final DocumentationTemplate template) { + final Set<DocumentationTemplate> templates) { final Map<String, Object> templateData = Map.of( "sourcedType", sourcedType, "lookup", lookup); - final Path targetPath = createTypeTargetPath(sourcedType, template.targetPath); - render(templateDirectory, template.name, templateData, targetPath); + templates.forEach(template -> { + final Path targetPath = createTypeTargetPath(sourcedType, template.targetPath); + render(templateDirectory, template.name, templateData, targetPath); + }); } private static Path createTypeTargetPath(final ArtifactSourcedType sourcedType, final String targetPathPattern) { @@ -72,8 +75,9 @@ public final class DocumentationGenerator { } private static void renderIndex( - final TypeLookup lookup, final Path templateDirectory, final DocumentationTemplate template) { + final TypeLookup lookup, final Path templateDirectory, final Set<DocumentationTemplate> templates) { final Map<String, Object> templateData = Map.of("lookup", lookup); - render(templateDirectory, template.name, templateData, Path.of(template.targetPath)); + templates.forEach( + template -> render(templateDirectory, template.name, templateData, Path.of(template.targetPath))); } } diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/generator/DocumentationGeneratorArgs.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/generator/DocumentationGeneratorArgs.java index f05d22a..6acf8bc 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/generator/DocumentationGeneratorArgs.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/generator/DocumentationGeneratorArgs.java @@ -31,20 +31,20 @@ public final class DocumentationGeneratorArgs { final Path templateDirectory; - final DocumentationTemplate indexTemplate; + final Set<DocumentationTemplate> indexTemplates; - final DocumentationTemplate typeTemplate; + final Set<DocumentationTemplate> typeTemplates; public DocumentationGeneratorArgs( final Set<PluginSet> pluginSets, final Predicate<String> classNameFilter, final Path templateDirectory, - final DocumentationTemplate indexTemplate, - final DocumentationTemplate typeTemplate) { + final Set<DocumentationTemplate> indexTemplates, + final Set<DocumentationTemplate> typeTemplates) { this.pluginSets = requireNonNull(pluginSets, "pluginSets"); this.classNameFilter = requireNonNull(classNameFilter, "classNameFilter"); this.templateDirectory = requireNonNull(templateDirectory, "templateDirectory"); - this.indexTemplate = requireNonNull(indexTemplate, "indexTemplate"); - this.typeTemplate = requireNonNull(typeTemplate, "typeTemplate"); + this.indexTemplates = requireNonNull(indexTemplates, "indexTemplates"); + this.typeTemplates = requireNonNull(typeTemplates, "typeTemplates"); } } diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/generator/DocumentationTemplate.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/generator/DocumentationTemplate.java index 59219f0..32597f8 100644 --- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/generator/DocumentationTemplate.java +++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/generator/DocumentationTemplate.java @@ -18,6 +18,8 @@ package org.apache.logging.log4j.docgen.generator; import static java.util.Objects.requireNonNull; +import java.util.Objects; + public final class DocumentationTemplate { final String name; @@ -28,4 +30,21 @@ public final class DocumentationTemplate { this.name = requireNonNull(name, "name"); this.targetPath = requireNonNull(targetPath, "targetPath"); } + + @Override + public boolean equals(Object instance) { + if (this == instance) { + return true; + } + if (instance == null || getClass() != instance.getClass()) { + return false; + } + DocumentationTemplate that = (DocumentationTemplate) instance; + return Objects.equals(name, that.name) && Objects.equals(targetPath, that.targetPath); + } + + @Override + public int hashCode() { + return Objects.hash(name, targetPath); + } } diff --git a/log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/generator/DocumentationGeneratorTest.java b/log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/generator/DocumentationGeneratorTest.java index 44d07d6..22322e1 100644 --- a/log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/generator/DocumentationGeneratorTest.java +++ b/log4j-docgen/src/test/java/org/apache/logging/log4j/docgen/generator/DocumentationGeneratorTest.java @@ -16,6 +16,7 @@ */ package org.apache.logging.log4j.docgen.generator; +import static java.util.Collections.singleton; import static org.apache.logging.log4j.docgen.generator.PluginSetUtils.readDescriptor; import static org.apache.logging.log4j.docgen.generator.PluginSetUtils.readDescriptors; import static org.apache.logging.log4j.tools.internal.test.util.FileTestUtils.assertDirectoryContentMatches; @@ -58,10 +59,10 @@ class DocumentationGeneratorTest { pluginSets, className -> !className.startsWith("java."), templateDirectory, - new DocumentationTemplate( - "index.adoc.ftl", outputDir.resolve("index.adoc").toString()), - new DocumentationTemplate( - "type.adoc.ftl", outputDir.resolve("%a/%c.adoc").toString())); + singleton(new DocumentationTemplate( + "index.adoc.ftl", outputDir.resolve("index.adoc").toString())), + singleton(new DocumentationTemplate( + "type.adoc.ftl", outputDir.resolve("%a/%c.adoc").toString()))); DocumentationGenerator.generateDocumentation(generatorArgs); // Verify the output diff --git a/log4j-tools-internal-freemarker-util/pom.xml b/log4j-tools-internal-freemarker-util/pom.xml index 71b2b72..aeeb4c9 100644 --- a/log4j-tools-internal-freemarker-util/pom.xml +++ b/log4j-tools-internal-freemarker-util/pom.xml @@ -37,10 +37,24 @@ </properties> <dependencies> + <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> </dependency> + + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + </dependencies> </project> diff --git a/log4j-tools-internal-freemarker-util/src/main/java/org/apache/logging/log4j/tools/internal/freemarker/util/FreeMarkerUtils.java b/log4j-tools-internal-freemarker-util/src/main/java/org/apache/logging/log4j/tools/internal/freemarker/util/FreeMarkerUtils.java index ec9aec8..82c2bbc 100644 --- a/log4j-tools-internal-freemarker-util/src/main/java/org/apache/logging/log4j/tools/internal/freemarker/util/FreeMarkerUtils.java +++ b/log4j-tools-internal-freemarker-util/src/main/java/org/apache/logging/log4j/tools/internal/freemarker/util/FreeMarkerUtils.java @@ -23,6 +23,8 @@ import freemarker.template.DefaultObjectWrapper; import freemarker.template.DefaultObjectWrapperBuilder; import freemarker.template.Template; import freemarker.template.TemplateExceptionHandler; +import freemarker.template.TemplateMethodModelEx; +import freemarker.template.TemplateModelException; import freemarker.template.Version; import java.io.BufferedWriter; import java.io.IOException; @@ -35,6 +37,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.List; public final class FreeMarkerUtils { @@ -46,6 +49,52 @@ public final class FreeMarkerUtils { private FreeMarkerUtils() {} + private static final class StopMethod implements TemplateMethodModelEx { + + private static final StopMethod INSTANCE = new StopMethod(); + + private static final TemplateModelException SIGNAL = new TemplateModelException(); + + @Override + public Object exec(final List arguments) throws TemplateModelException { + throw SIGNAL; + } + + private static boolean invoked(final Throwable throwable) { + + // Keep a second pointer that slowly walks the causal chain. + // If the fast pointer ever catches the slower pointer, then there's a loop. + Throwable slowPointer = throwable; + boolean advanceSlowPointer = false; + + Throwable parent = throwable; + while (true) { + + // Check the exception + if (parent == StopMethod.SIGNAL) { + return true; + } + + // Advance the cause + final Throwable cause = parent.getCause(); + if (cause == null) { + break; + } + + // Advance pointers + parent = cause; + if (parent == slowPointer) { + throw new IllegalArgumentException("loop in causal chain"); + } + if (advanceSlowPointer) { + slowPointer = slowPointer.getCause(); + } + advanceSlowPointer = !advanceSlowPointer; // only advance every other iteration + } + return false; + } + } + @SuppressFBWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME") private static Configuration createConfiguration(final Path templateDirectory) { final Configuration configuration = new Configuration(CONFIGURATION_VERSION); @@ -63,6 +112,7 @@ public final class FreeMarkerUtils { configuration.setLogTemplateExceptions(false); configuration.setWrapUncheckedExceptions(true); configuration.setFallbackOnNullLoopVariable(false); + configuration.setSharedVariable("stop", StopMethod.INSTANCE); return configuration; } @@ -70,21 +120,31 @@ public final class FreeMarkerUtils { public static void render( final Path templateDirectory, final String templateName, final Object templateData, final Path outputFile) { try { + + // Render the template final Configuration configuration = createConfiguration(templateDirectory); final Template template = configuration.getTemplate(templateName); + final StringWriter templateOutputWriter = new StringWriter(); + template.process(templateData, templateOutputWriter); + final String templateOutput = templateOutputWriter.toString(); + + // Write the template output to the target file final Path outputFileParent = outputFile.getParent(); if (outputFileParent != null) { Files.createDirectories(outputFileParent); } try (final BufferedWriter outputFileWriter = Files.newBufferedWriter( outputFile, CHARSET, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { - template.process(templateData, outputFileWriter); + outputFileWriter.write(templateOutput); } } catch (final Exception error) { - final String message = String.format( - "failed rendering template `%s` in directory `%s` to file `%s`", - templateName, templateDirectory, outputFile); - throw new RuntimeException(message, error); + final boolean stopMethodInvoked = StopMethod.invoked(error); + if (!stopMethodInvoked) { + final String message = String.format( + "failed rendering template `%s` in directory `%s` to file `%s`", + templateName, templateDirectory, outputFile); + throw new RuntimeException(message, error); + } } } diff --git a/log4j-tools-internal-freemarker-util/src/test/java/org/apache/logging/log4j/tools/internal/freemarker/util/FreeMarkerUtilsTest.java b/log4j-tools-internal-freemarker-util/src/test/java/org/apache/logging/log4j/tools/internal/freemarker/util/FreeMarkerUtilsTest.java new file mode 100644 index 0000000..5aeb65d --- /dev/null +++ b/log4j-tools-internal-freemarker-util/src/test/java/org/apache/logging/log4j/tools/internal/freemarker/util/FreeMarkerUtilsTest.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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.apache.logging.log4j.tools.internal.freemarker.util; + +import static java.nio.file.Files.write; +import static org.apache.logging.log4j.tools.internal.freemarker.util.FreeMarkerUtils.render; +import static org.apache.logging.log4j.tools.internal.freemarker.util.FreeMarkerUtils.renderString; +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Collections; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; + +class FreeMarkerUtilsTest { + + @Test + void render_should_work(@TempDir(cleanup = CleanupMode.ON_SUCCESS) final Path tempDir) throws Exception { + + // Create the template file + final String templateName = "test.txt.ftl"; + final Path templateFile = tempDir.resolve(templateName); + write( + templateFile, + "Hello, ${name?capitalize}!".getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE_NEW); + + // Render the template + final Path outputFile = tempDir.resolve("test.txt"); + render(tempDir, templateName, Collections.singletonMap("name", "volkan"), outputFile); + + // Verify the generated content + assertThat(outputFile).hasContent("Hello, Volkan!"); + } + + @Test + void render_should_stop(@TempDir(cleanup = CleanupMode.ON_SUCCESS) final Path tempDir) throws Exception { + + // Create the template file + final String templateName = "test.txt.ftl"; + final Path templateFile = tempDir.resolve(templateName); + final String excessiveContentToEnsureSuccessIsNotDueToBuffering = String.format("%010000d\n", 1); + final String templateFileContent = excessiveContentToEnsureSuccessIsNotDueToBuffering + + "You might expect awesome things to get rendered.\nBut I tell you: they won't.\n${stop()}"; + write(templateFile, templateFileContent.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE_NEW); + + // Render the template + final Path outputFile = tempDir.resolve("test.txt"); + render(tempDir, templateName, Collections.singletonMap("name", "volkan"), outputFile); + + // Verify the generated content + assertThat(outputFile).doesNotExist(); + } + + @Test + void renderString_should_work() { + final String output = renderString("Hello, ${name?capitalize}!", Collections.singletonMap("name", "volkan")); + assertThat(output).isEqualTo("Hello, Volkan!"); + } +} diff --git a/src/changelog/.0.x.x/add-freemarker-stop-method.xml b/src/changelog/.0.x.x/add-freemarker-stop-method.xml new file mode 100644 index 0000000..a7b4093 --- /dev/null +++ b/src/changelog/.0.x.x/add-freemarker-stop-method.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="https://logging.apache.org/xml/ns" + xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" + type="added"> + <issue id="122" link="https://github.com/apache/logging-log4j-tools/pull/122"/> + <description format="asciidoc">Add `stop()` FreeMarker method to skip template file generation. +A use case is to have multiple `typeTemplates` in the `log4j-docgen:generate-documentation` configuration and skip generating certain files if desired.</description> +</entry> diff --git a/src/changelog/.0.x.x/add-multi-doc-generator-template.xml b/src/changelog/.0.x.x/add-multi-doc-generator-template.xml new file mode 100644 index 0000000..9863f96 --- /dev/null +++ b/src/changelog/.0.x.x/add-multi-doc-generator-template.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="https://logging.apache.org/xml/ns" + xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" + type="added"> + <issue id="122" link="https://github.com/apache/logging-log4j-tools/pull/122"/> + <description format="asciidoc">Support multiple index and type templates in the `log4j-docgen:generate-documentation` configuration</description> +</entry> diff --git a/src/site/antora/modules/ROOT/pages/log4j-docgen-asciidoctor-extension.adoc b/src/site/antora/modules/ROOT/pages/log4j-docgen-asciidoctor-extension.adoc index 0b6ba93..a3e69de 100644 --- a/src/site/antora/modules/ROOT/pages/log4j-docgen-asciidoctor-extension.adoc +++ b/src/site/antora/modules/ROOT/pages/log4j-docgen-asciidoctor-extension.adoc @@ -49,6 +49,7 @@ That is, if `true`, `apiref:some.unknown.Class[]` will be converted to `<code>Cl Note that this only applies to types where the label is not provided. This flag is disabled by default. +[#log4j-docgen-type-target-template] `log4j-docgen-type-target-template`:: The FreeMarker template to produce the link target for individual types documented, for instance: + diff --git a/src/site/antora/modules/ROOT/pages/log4j-docgen-maven-plugin.adoc b/src/site/antora/modules/ROOT/pages/log4j-docgen-maven-plugin.adoc index 7b7f623..7b72844 100644 --- a/src/site/antora/modules/ROOT/pages/log4j-docgen-maven-plugin.adoc +++ b/src/site/antora/modules/ROOT/pages/log4j-docgen-maven-plugin.adoc @@ -56,18 +56,24 @@ The `generate-documentation` goal generates an AsciiDoc-formatted documentation </typeFilter> <templateDirectory>${project.basedir}/src/docgen-templates</templateDirectory> - <indexTemplate> - <source>index.adoc.ftl</source> - <target>${project.build.directory}/generated-site/asciidoc/plugin-reference/index.adoc</target> - </indexTemplate> - <typeTemplate> - <source>type.adoc.ftl</source> - <!-- `target` must be in sync. with the `log4j-docgen-type-template-target` configuration of `log4j-docgen-asciidoctor-extension`! --> - <target>${project.build.directory}/generated-site/asciidoc/plugin-reference/%g/%a/%c.adoc</target> - </typeTemplate> + + <indexTemplates> + <template> + <source>index.adoc.ftl</source> + <target>${project.build.directory}/generated-site/asciidoc/plugin-reference/index.adoc</target> + </template> + </indexTemplates> + + <typeTemplates> + <template> + <source>type.adoc.ftl</source> + <target>${project.build.directory}/generated-site/asciidoc/plugin-reference/%g/%a/%c.adoc</target><!--1--> + </template> + </typeTemplates> </configuration> ---- +<1> `target` must be in sync. with xref:log4j-docgen-asciidoctor-extension.adoc#log4j-docgen-type-target-template[the `log4j-docgen-type-target-template` configuration of `log4j-docgen-asciidoctor-extension`]. The `generate-documentation` goal configuration also accepts the following parameters: