You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by vy...@apache.org on 2022/12/30 10:33:13 UTC
[logging-log4j-tools] branch master updated: Add FreeMarker support
This is an automated email from the ASF dual-hosted git repository.
vy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j-tools.git
The following commit(s) were added to refs/heads/master by this push:
new 94bec35 Add FreeMarker support
94bec35 is described below
commit 94bec35e689b3a547212fefb92d480a925e07f50
Author: Volkan Yazıcı <vo...@yazi.ci>
AuthorDate: Fri Dec 30 11:23:12 2022 +0100
Add FreeMarker support
---
log4j-changelog/README.adoc | 124 +++++--
log4j-changelog/pom.xml | 7 +
.../apache/logging/log4j/tools/CharsetUtils.java | 30 ++
.../org/apache/logging/log4j/tools/FileUtils.java | 4 +-
.../org/apache/logging/log4j/tools/XmlWriter.java | 8 +-
.../log4j/tools/changelog/ChangelogFiles.java | 49 ++-
.../tools/changelog/exporter/AsciiDocExporter.java | 402 ---------------------
.../changelog/exporter/ChangelogExporter.java | 194 ++++++++++
...xporterArgs.java => ChangelogExporterArgs.java} | 8 +-
.../tools/changelog/exporter/FreeMarkerUtils.java | 83 +++++
.../changelog/releaser/ChangelogReleaser.java | 37 +-
log4j-tools-parent/pom.xml | 9 +
12 files changed, 466 insertions(+), 489 deletions(-)
diff --git a/log4j-changelog/README.adoc b/log4j-changelog/README.adoc
index fe9a156..f965ad4 100644
--- a/log4j-changelog/README.adoc
+++ b/log4j-changelog/README.adoc
@@ -47,6 +47,7 @@ $ ls -a -1 src/changelog
2.18.0
2.19.0
.2.x.x
+.index.adoc.ftl
----
Changelog sources of _released versions_ are stored in `<changelogDirectory>/<releaseVersion>` folders (e.g., `/src/changelog/2.19.0`):
@@ -56,7 +57,7 @@ Changelog sources of _released versions_ are stored in `<changelogDirectory>/<re
$ ls -a -1 src/changelog/2.19.0
.
..
-.intro.adoc
+.changelog.adoc.ftl
LOG4J2-2975_Add_implementation_of_SLF4J2_fluent_API.xml
LOG4J2-3545_Add_correct_manifest_entries_for_OSGi_to_log4j_jcl.xml
LOG4J2-3548_Improve_support_for_passwordless_keystores.xml
@@ -75,24 +76,35 @@ Changelog sources of _upcoming releases_ are stored in `<changelogDirectory>/.<r
$ ls -a -1 src/changelog/.2.x.x
.
..
+.changelog.adoc.ftl
LOG4J2-2678_Add_LogEvent_timestamp_to_ProducerRecord_in_KafkaAppender.xml
LOG4J2-3628_new_changelog_infra.xml
LOG4J2-3631_Fix_Configurator_setLevel_for_internal_classes.xml
LOG4J2-3634_Fix_level_propagation_in_Log4jBridgeHandler.xml
----
-A typical changelog entry looks as follows:
+A typical `.release.xml` looks as follows:
+
+[source]
+----
+$ cat src/changelog/2.19.0/release.xml
+<?xml version="1.0" encoding="UTF-8"?>
+<release date="2022-09-09" version="2.19.0"/>
+----
+
+A typical changelog entry file looks as follows:
[source]
----
$ cat src/changelog/.2.x.x/LOG4J2-3628_new_changelog_infra.xml
<?xml version="1.0" encoding="UTF-8"?>
-<entry type="changed">
- <issue id="LOG4J2-3628" link="https://issues.apache.org/jira/browse/LOG4J2-3628"/>
+<entry type="fixed">
+ <issue id="LOG4J2-3556" link="https://issues.apache.org/jira/browse/LOG4J2-3556"/>
<author id="vy"/>
+ <author name="Arthur Gavlyukovskiy"/>
<description format="asciidoc">
- Replaced `maven-changes-plugin` with a custom changelog implementation
- </description>
+ Make `JsonTemplateLayout` stack trace truncation operate for each label block
+ </description>
</entry>
----
@@ -117,21 +129,21 @@ A released version changelog directory consists of following files:
`.release.xml`::
the meta information about the release (e.g., release version and date)
-`.intro.adoc`::
-the introductory content used by the AsciiDoc exporter
+`.changelog.adoc.ftl`::
+FreeMarker templated AsciiDoc file used by `ChangelogExporter`
`[<issueId>_]<shortSummary>.xml`::
changelog entry associated with a change
-The AsciiDoc exporter will compile these source files and output a `<releaseVersion>.adoc` (e.g., `2.19.0.adoc`) file for each release and an `index.adoc`.
+`ChangelogExporter` compiles these source files and outputs a `<releaseVersion>.adoc` (e.g., `2.19.0.adoc`) file for each release.
[#unreleased-version-changelogs]
== Unreleased version changelogs
Changelogs of upcoming release versions are stored in `<changelogDirectory>/.<releaseVersionMajor>.x.x` directories (e.g., `/src/changelog/.2.x.x`).
-Compared to released version changelog directories (e.g., `2.19.0`), `.<releaseVersionMajor>.x.x` directories only consist of changelog entry files (i.e., `[<issueId>_]<shortSummary>.xml`).
+Compared to released version changelog directories (e.g., `2.19.0`), `.<releaseVersionMajor>.x.x` directories do not contain a `.release.xml`.
-The AsciiDoc exporter will compile these source files and output `<releaseVersionMajor>.x.x.adoc` (e.g., `2.x.x.adoc`) file for each upcoming release.
+`ChangelogExporter` compiles these source files and outputs `<releaseVersionMajor>.x.x.adoc` (e.g., `2.x.x.adoc`) file for each upcoming release.
[#changelog-entry-file]
== Changelog entry file
@@ -153,13 +165,15 @@ A sample _changelog entry_ file is shared below.
<issue id="LOG4J2-3556" link="https://issues.apache.org/jira/browse/LOG4J2-3556"/>
<author id="vy"/>
<author name="Arthur Gavlyukovskiy"/>
- <description format="asciidoc">Make `JsonTemplateLayout` stack trace truncation operate for each label block.</description>
+ <description format="asciidoc">
+ Make `JsonTemplateLayout` stack trace truncation operate for each label block
+ </description>
</entry>
----
Some remarks about the structure of changelog entry files:
-* The root element must be named `entry`.
+* The root element must be named `entry`
* `entry.type` attribute is required and must be one of the https://keepachangelog.com/en/1.0.0/#how[_Keep a Changelog_ change types]:
** `added` – for new features
** `changed` – for changes in existing functionality
@@ -167,10 +181,65 @@ Some remarks about the structure of changelog entry files:
** `removed` – for now removed features
** `fixed` – for any bug fixes
** `security` – for vulnerabilities
-* `issue` element is optional, and, if present, must contain `id` and `link` attributes.
-* `author` element must have at least one of `id` or `name` attributes.
-* There must be at least one author.
-* There must be a single `description` element with non-blank content and `format="asciidoc"` attribute.
+* `issue` element is optional, and, if present, must contain `id` and `link` attributes
+* `author` element must have at least one of `id` or `name` attributes
+* There must be at least one `author`
+* There must be a single `description` element with non-blank content and `format="asciidoc"` attribute
+
+[#changelog-template-file]
+== Changelog template file
+
+Each `.changelog.adoc.ftl` FreeMarker templated AsciiDoc files are compiled by `ChangelogExporter` with the following input data hash:
+
+* `release` -> `ChangelogRelease`
+* `entriesByType` -> `Map<ChangelogEntry.Type, List<ChangelogEntry>>`
+
+See xref:src/main/java/org/apache/logging/log4j/tools/changelog/ChangelogRelease.java[ChangelogRelease.java] and xref:src/main/java/org/apache/logging/log4j/tools/changelog/ChangelogEntry.java[ChangelogEntry.java] for details.
+
+A sample changelog template file is shared below.
+
+.`src/changelog/2.19.0/.changelog.adoc.ftl` file contents
+[source,asciidoc]
+----
+= ${release.version}<#if release.date?has_content> (${release.date})</#if>
+
+Changes staged for the next version that is yet to be released.
+
+<#if entriesByType?size gt 0>== Changes
+<#list entriesByType as entryType, entries>
+
+=== ${entryType?capitalize}
+
+<#list entries as entry>
+* ${entry.description.text?replace("\\s+", " ", "r")}
+(for <#list entry.issues as issue>${issue.link}[${issue.id}]<#if issue?has_next>, </#if></#list>
+by <#list entry.authors as author><#if author.name?has_content>${author.name}<#else>`${author.id}`</#if><#if author?has_next>, </#if></#list>)
+</#list>
+</#list>
+</#if>
+----
+
+[#index-template-file]
+== Index template file
+
+`.index.adoc.ftl` FreeMarker templated AsciiDoc file is compiled by `ChangelogExporter` with the following input data hash:
+
+* `releases` -> list of hashes containing following keys:
+** `version`
+** `date`
+** `changelogFileName`
+
+A sample index template file is shared below.
+
+.`src/changelog/.index.adoc.ftl` file contents
+[source,asciidoc]
+----
+= Release changelogs
+
+<#list releases as release>
+* xref:${release.changelogFileName}[${release.version}]<#if release.date?has_content> (${release.date})</#if>
+</#list>
+----
[#qa]
== Q&A
@@ -184,7 +253,7 @@ Simply create a <<#changelog-entry-file>> and commit it along with your change!
[#qa-generate]
=== How can I export changelogs to AsciiDoc files?
-You need to use `AsciiDocExporter` as follows:
+You need to use `ChangelogExporter` as follows:
[source,bash]
----
@@ -192,16 +261,17 @@ java \
-cp /path/to/log4j-changelog.jar \
-Dlog4j.changelog.directory=/path/to/changelog/directory \
-Dlog4j.changelog.exporter.outputDirectory=/path/to/asciiDocOutputDirectory \
- org.apache.logging.log4j.tools.changelog.exporter.AsciiDocExporter
+ org.apache.logging.log4j.tools.changelog.exporter.ChangelogExporter
----
[#qa-deploy-release]
=== I am about to deploy a new Log4j release. What shall I do?
-Just before a release, two things need to happen in the changelog sources:
+Just before a release, three things need to happen in the changelog sources:
-. Changelog entries of the upcoming release directory `<changelogDirectory>/.<releaseVersionMajor>.x.x` needs to be moved to the release changelog directory `<changelogDirectory>/<releaseVersion>`
-. `.index.adoc` and `.release.xml` need to be created in the release changelog directory `<changelogDirectory>/<releaseVersion>`
+. *Changelog entry files needs to be moved* from the _upcoming_ release changelog directory `<changelogDirectory>/.<releaseVersionMajor>.x.x` to the _new_ release changelog directory `<changelogDirectory>/<releaseVersion>`
+. *`.changelog.adoc.ftl` needs to be copied* from the _upcoming_ release changelog directory to the _new_ release changelog directory, unless it already exists in the target
+. *`.release.xml` needs to be created* in the _new_ release changelog directory
Due to the nature of release candidates, above steps might need to be repeated multiple times.
@@ -214,7 +284,7 @@ Once a release candidate voting reaches to a consensus for release, associated a
Hence, there are no differences between releases and release candidates.
====
-How to carry out aforementioned two changes are explained below in steps:
+How to carry out aforementioned changes are explained below in steps:
. Populate the `<changelogDirectory>/<releaseVersion>` directory (e.g., `/src/changelog/2.19.0`) from the upcoming release changelog directory (e.g., `<changelogDirectory>/.2.x.x`):
+
@@ -226,13 +296,13 @@ java \
-Dlog4j.changelog.releaseVersion=X.Y.Z \
org.apache.logging.log4j.tools.changelog.releaser.ChangelogReleaser
----
-. Verify that `<changelogDirectory>/.<releaseVersionMajor>.x.x` directory (e.g., `/src/changelog/.2.x.x`) is emptied
-. Verify that `<changelogDirectory>/<releaseVersion>` directory (e.g., `/src/changelog/2.19.0`) is created, and it contains `.intro.adoc`, `.release.xml`, and changelog entry files
+. Verify that all changelog entry files are moved from `<changelogDirectory>/.<releaseVersionMajor>.x.x` directory (e.g., `/src/changelog/.2.x.x`)
+. Verify that `<changelogDirectory>/<releaseVersion>` directory (e.g., `/src/changelog/2.19.0`) is created, and it contains `.changelog.adoc.ftl`, `.release.xml`, and changelog entry files
+
[IMPORTANT]
====
-If `<changelogDirectory>/<releaseVersion>` directory (e.g., `/src/changelog/2.19.0`) already exists with certain content, `ChangelogReleaser` will only move new changelog entry files and override `.release.xml`; `.intro.adoc` will not be touched, if exists.
+If `<changelogDirectory>/<releaseVersion>` directory (e.g., `/src/changelog/2.19.0`) already exists with certain content, `ChangelogReleaser` will only move new changelog entry files and override `.release.xml`; `.changelog.adoc.ftl` will not be touched, if it already exists.
This allows one to run `ChangelogReleaser` multiple times, e.g., to incorporate changes added to a release candidate.
====
-. Edit the created `.intro.adoc`
+. Edit the populated `.changelog.adoc.ftl`
. `git add` the changes in the changelog directory (e.g., `/src/changelog`) and commit them
diff --git a/log4j-changelog/pom.xml b/log4j-changelog/pom.xml
index bd71dd0..30f29aa 100644
--- a/log4j-changelog/pom.xml
+++ b/log4j-changelog/pom.xml
@@ -31,11 +31,18 @@
<artifactId>log4j-changelog</artifactId>
<dependencies>
+
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<scope>provided</scope>
</dependency>
+
+ <dependency>
+ <groupId>org.freemarker</groupId>
+ <artifactId>freemarker</artifactId>
+ </dependency>
+
</dependencies>
</project>
diff --git a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/CharsetUtils.java b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/CharsetUtils.java
new file mode 100644
index 0000000..f23df9e
--- /dev/null
+++ b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/CharsetUtils.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+public final class CharsetUtils {
+
+ private CharsetUtils() {}
+
+ public static final Charset CHARSET = StandardCharsets.UTF_8;
+
+ public static final String CHARSET_NAME = CHARSET.name();
+
+}
diff --git a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/FileUtils.java b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/FileUtils.java
index 41a3d57..555c250 100644
--- a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/FileUtils.java
+++ b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/FileUtils.java
@@ -33,7 +33,7 @@ public final class FileUtils {
* </p>
*/
@SuppressWarnings("RedundantIfStatement")
- public static Stream<Path> findAdjacentFiles(final Path directory) {
+ public static Stream<Path> findAdjacentFiles(final Path directory, final boolean dotFilesSkipped) {
try {
return Files
.walk(directory, 1)
@@ -45,7 +45,7 @@ public final class FileUtils {
}
// Skip hidden files.
- boolean hiddenFile = path.getFileName().toString().startsWith(".");
+ boolean hiddenFile = dotFilesSkipped && path.getFileName().toString().startsWith(".");
if (hiddenFile) {
return false;
}
diff --git a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/XmlWriter.java b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/XmlWriter.java
index 78a0f59..8a72c3d 100644
--- a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/XmlWriter.java
+++ b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/XmlWriter.java
@@ -17,8 +17,6 @@
package org.apache.logging.log4j.tools;
import java.io.StringWriter;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
@@ -38,14 +36,12 @@ import org.w3c.dom.Document;
public final class XmlWriter {
- private static final Charset ENCODING = StandardCharsets.UTF_8;
-
private XmlWriter() {}
public static void toFile(final Path filepath, final Consumer<Document> documentConsumer) {
try {
final String xml = toString(documentConsumer);
- final byte[] xmlBytes = xml.getBytes(ENCODING);
+ final byte[] xmlBytes = xml.getBytes(CharsetUtils.CHARSET);
Path filepathParent = filepath.getParent();
if (filepathParent != null) {
Files.createDirectories(filepathParent);
@@ -101,7 +97,7 @@ public final class XmlWriter {
final Transformer transformer = TransformerFactory.newInstance().newTransformer();
final StreamResult result = new StreamResult(new StringWriter());
final DOMSource source = new DOMSource(document);
- transformer.setOutputProperty(OutputKeys.ENCODING, ENCODING.name());
+ transformer.setOutputProperty(OutputKeys.ENCODING, CharsetUtils.CHARSET_NAME);
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
transformer.transform(source, result);
diff --git a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/ChangelogFiles.java b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/ChangelogFiles.java
index 20b0b6e..f4a90aa 100644
--- a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/ChangelogFiles.java
+++ b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/ChangelogFiles.java
@@ -16,9 +16,6 @@
*/
package org.apache.logging.log4j.tools.changelog;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
import java.util.regex.Matcher;
@@ -26,6 +23,8 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.apache.logging.log4j.tools.FileUtils;
+
public final class ChangelogFiles {
private ChangelogFiles() {}
@@ -36,32 +35,26 @@ public final class ChangelogFiles {
}
public static Set<Integer> unreleasedDirectoryVersionMajors(final Path changelogDirectory) {
- try {
- return Files
- .walk(changelogDirectory, 1)
- .flatMap(path -> {
+ return FileUtils
+ .findAdjacentFiles(changelogDirectory, false)
+ .flatMap(path -> {
- // Skip the directory itself.
- if (path.equals(changelogDirectory)) {
- return Stream.empty();
- }
+ // Only select directories matching with the `^\.(\d+)\.x\.x$` pattern.
+ final Pattern versionPattern = Pattern.compile("^\\.(\\d+)\\.x\\.x$");
+ final Matcher versionMatcher = versionPattern.matcher(path.getFileName().toString());
+ if (!versionMatcher.matches()) {
+ return Stream.empty();
+ }
+ final String versionMajorString = versionMatcher.group(1);
+ final int versionMajor = Integer.parseInt(versionMajorString);
+ return Stream.of(versionMajor);
- // Only select directories matching with the `^\.(\d+)\.x\.x$` pattern.
- final Pattern versionPattern = Pattern.compile("^\\.(\\d+)\\.x\\.x$");
- final Matcher versionMatcher = versionPattern.matcher(path.getFileName().toString());
- if (!versionMatcher.matches()) {
- return Stream.empty();
- }
- final String versionMajorString = versionMatcher.group(1);
- final int versionMajor = Integer.parseInt(versionMajorString);
- return Stream.of(versionMajor);
+ })
+ .collect(Collectors.toSet());
+ }
- })
- .collect(Collectors.toSet());
- } catch (final IOException error) {
- final String message = String.format("failed walking directory: `%s`", changelogDirectory);
- throw new UncheckedIOException(message, error);
- }
+ public static Path indexTemplateFile(final Path changelogDirectory) {
+ return changelogDirectory.resolve(".index.adoc.ftl");
}
public static Path releaseDirectory(final Path changelogDirectory, final String releaseVersion) {
@@ -72,8 +65,8 @@ public final class ChangelogFiles {
return releaseDirectory.resolve(".release.xml");
}
- public static Path introAsciiDocFile(final Path releaseDirectory) {
- return releaseDirectory.resolve(".intro.adoc");
+ public static Path releaseChangelogTemplateFile(final Path releaseDirectory) {
+ return releaseDirectory.resolve(".changelog.adoc.ftl");
}
}
diff --git a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/AsciiDocExporter.java b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/AsciiDocExporter.java
deleted file mode 100644
index 23220dd..0000000
--- a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/AsciiDocExporter.java
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- * 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.changelog.exporter;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import org.apache.logging.log4j.tools.AsciiDocUtils;
-import org.apache.logging.log4j.tools.FileUtils;
-import org.apache.logging.log4j.tools.changelog.ChangelogEntry;
-import org.apache.logging.log4j.tools.changelog.ChangelogFiles;
-import org.apache.logging.log4j.tools.changelog.ChangelogRelease;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-public final class AsciiDocExporter {
-
- private static final String AUTO_GENERATION_WARNING_ASCIIDOC = "////\n" +
- "*DO NOT EDIT THIS FILE!*\n" +
- "This file is automatically generated from the release changelog directory!\n" +
- "////\n";
-
- private AsciiDocExporter() {}
-
- public static void main(final String[] mainArgs) {
-
- // Read arguments.
- final AsciiDocExporterArgs args = AsciiDocExporterArgs.fromSystemProperties();
-
- // Find release directories.
- final List<Path> releaseDirectories = FileUtils
- .findAdjacentFiles(args.changelogDirectory)
- .filter(file -> file.toFile().isDirectory())
- .sorted(Comparator.comparing(releaseDirectory -> {
- final Path releaseXmlFile = ChangelogFiles.releaseXmlFile(releaseDirectory);
- final ChangelogRelease changelogRelease = ChangelogRelease.readFromXmlFile(releaseXmlFile);
- return changelogRelease.date;
- }))
- .collect(Collectors.toList());
- final int releaseDirectoryCount = releaseDirectories.size();
-
- // Read the release information files.
- final List<ChangelogRelease> changelogReleases = releaseDirectories
- .stream()
- .map(releaseDirectory -> {
- final Path releaseXmlFile = ChangelogFiles.releaseXmlFile(releaseDirectory);
- return ChangelogRelease.readFromXmlFile(releaseXmlFile);
- })
- .collect(Collectors.toList());
-
- // Export releases.
- if (releaseDirectoryCount > 0) {
-
- // Export each release directory.
- for (int releaseIndex = 0; releaseIndex < releaseDirectories.size(); releaseIndex++) {
- final Path releaseDirectory = releaseDirectories.get(releaseIndex);
- final ChangelogRelease changelogRelease = changelogReleases.get(releaseIndex);
- try {
- exportRelease(args.outputDirectory, releaseDirectory, changelogRelease);
- } catch (final Exception error) {
- final String message =
- String.format("failed exporting release from directory `%s`", releaseDirectory);
- throw new RuntimeException(message, error);
- }
- }
-
- // Report the operation.
- if (releaseDirectoryCount == 1) {
- System.out.format("exported a single release directory: `%s`%n", releaseDirectories.get(0));
- } else {
- System.out.format(
- "exported %d release directories: ..., `%s`%n",
- releaseDirectories.size(),
- releaseDirectories.get(releaseDirectoryCount - 1));
- }
-
- }
-
- // Export unreleased.
- ChangelogFiles
- .unreleasedDirectoryVersionMajors(args.changelogDirectory)
- .stream()
- .sorted(Comparator.reverseOrder())
- .forEach(upcomingReleaseVersionMajor -> {
- final Path upcomingReleaseDirectory =
- ChangelogFiles.unreleasedDirectory(args.changelogDirectory, upcomingReleaseVersionMajor);
- final ChangelogRelease upcomingRelease = upcomingRelease(upcomingReleaseVersionMajor);
- System.out.format("exporting upcoming release directory: `%s`%n", upcomingReleaseDirectory);
- exportUnreleased(args.outputDirectory, upcomingReleaseDirectory, upcomingRelease);
- changelogReleases.add(upcomingRelease);
- });
-
- // Export the release index.
- exportReleaseIndex(args.outputDirectory, changelogReleases);
-
- }
-
- private static void exportRelease(
- final Path outputDirectory,
- final Path releaseDirectory,
- final ChangelogRelease changelogRelease) {
- final String introAsciiDoc = readIntroAsciiDoc(releaseDirectory);
- final List<ChangelogEntry> changelogEntries = readChangelogEntries(releaseDirectory);
- try {
- exportRelease(outputDirectory, changelogRelease, introAsciiDoc, changelogEntries);
- } catch (final IOException error) {
- final String message = String.format("failed exporting release from directory `%s`", releaseDirectory);
- throw new UncheckedIOException(message, error);
- }
- }
-
- private static String readIntroAsciiDoc(final Path releaseDirectory) {
-
- // Determine the file to be read.
- final Path introAsciiDocFile = ChangelogFiles.introAsciiDocFile(releaseDirectory);
- if (!Files.exists(introAsciiDocFile)) {
- return "";
- }
-
- // Read the file.
- final List<String> introAsciiDocLines;
- try {
- introAsciiDocLines = Files.readAllLines(introAsciiDocFile, StandardCharsets.UTF_8);
- } catch (final IOException error) {
- final String message = String.format("failed reading intro AsciiDoc file: `%s`", introAsciiDocFile);
- throw new UncheckedIOException(message, error);
- }
-
- // Erase comment blocks.
- final boolean[] inCommentBlock = {false};
- return introAsciiDocLines
- .stream()
- .filter(line -> {
- final boolean commentBlock = "////".equals(line);
- if (commentBlock) {
- inCommentBlock[0] = !inCommentBlock[0];
- return false;
- }
- return !inCommentBlock[0];
- })
- .collect(Collectors.joining("\n"))
- + "\n";
-
- }
-
- private static List<ChangelogEntry> readChangelogEntries(final Path releaseDirectory) {
- return FileUtils
- .findAdjacentFiles(releaseDirectory)
- // Sorting is needed to generate the same output between different runs.
- .sorted()
- .map(ChangelogEntry::readFromXmlFile)
- .collect(Collectors.toList());
- }
-
- private static void exportRelease(
- final Path outputDirectory,
- final ChangelogRelease release,
- final String introAsciiDoc,
- final List<ChangelogEntry> entries)
- throws IOException {
- final String asciiDocFilename = changelogReleaseAsciiDocFilename(release);
- final Path asciiDocFile = outputDirectory.resolve(asciiDocFilename);
- Path asciiDocFileParent = asciiDocFile.getParent();
- if (asciiDocFileParent != null) {
- Files.createDirectories(asciiDocFileParent);
- }
- final String asciiDoc = exportReleaseToAsciiDoc(release, introAsciiDoc, entries);
- final byte[] asciiDocBytes = asciiDoc.getBytes(StandardCharsets.UTF_8);
- Files.write(asciiDocFile, asciiDocBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
- }
-
- private static String exportReleaseToAsciiDoc(
- final ChangelogRelease release,
- final String introAsciiDoc,
- final List<ChangelogEntry> entries) {
-
- // Write the header.
- final StringBuilder stringBuilder = new StringBuilder();
- stringBuilder
- .append(AsciiDocUtils.LICENSE_COMMENT_BLOCK)
- .append('\n')
- .append(AUTO_GENERATION_WARNING_ASCIIDOC)
- .append('\n')
- .append("= ")
- .append(release.version);
- if (release.date != null) {
- stringBuilder
- .append(" (")
- .append(release.date)
- .append(")\n")
- .append(introAsciiDoc)
- .append("\n");
- } else {
- stringBuilder.append("\n\nChanges staged for the next version that is yet to be released.\n\n");
- }
-
- if (!entries.isEmpty()) {
-
- stringBuilder.append("== Changes\n");
-
- // Group entries by type.
- final Map<ChangelogEntry.Type, List<ChangelogEntry>> entriesByType = entries
- .stream()
- .collect(Collectors.groupingBy(changelogEntry -> changelogEntry.type));
-
- // Write entries for each type.
- entriesByType
- .keySet()
- .stream()
- // Sorting is necessary for a consistent layout across different runs.
- .sorted()
- .forEach(type -> {
- stringBuilder.append('\n');
- appendEntryTypeHeader(stringBuilder, type);
- entriesByType.get(type).forEach(entry -> appendEntry(stringBuilder, entry));
- });
-
- }
-
- // Return the accumulated document so far.
- return stringBuilder.toString();
-
- }
-
- private static void appendEntryTypeHeader(final StringBuilder stringBuilder, final ChangelogEntry.Type type) {
- final String typeName = type.toString().toLowerCase(Locale.US);
- final String header = typeName.substring(0, 1).toUpperCase(Locale.US) + typeName.substring(1);
- stringBuilder
- .append("=== ")
- .append(header)
- .append("\n\n");
- }
-
- private static void appendEntry(final StringBuilder stringBuilder, final ChangelogEntry entry) {
- stringBuilder.append("* ");
- appendEntryDescription(stringBuilder, entry.description);
- final boolean containingIssues = !entry.issues.isEmpty();
- final boolean containingAuthors = !entry.authors.isEmpty();
- if (containingIssues || containingAuthors) {
- stringBuilder.append(" (");
- if (containingIssues) {
- appendEntryIssues(stringBuilder, entry.issues);
- }
- if (containingIssues && containingAuthors) {
- stringBuilder.append(' ');
- }
- if (containingAuthors) {
- appendEntryAuthors(stringBuilder, entry.authors);
- }
- stringBuilder.append(")");
- }
- stringBuilder.append('\n');
- }
-
- private static void appendEntryDescription(
- final StringBuilder stringBuilder,
- final ChangelogEntry.Description description) {
- if (!"asciidoc".equals(description.format)) {
- final String message = String.format("unsupported description format: `%s`", description.format);
- throw new RuntimeException(message);
- }
- stringBuilder.append(description.text);
- }
-
- private static void appendEntryIssues(
- final StringBuilder stringBuilder,
- final List<ChangelogEntry.Issue> issues) {
- stringBuilder.append("for ");
- final int issueCount = issues.size();
- for (int issueIndex = 0; issueIndex < issueCount; issueIndex++) {
- final ChangelogEntry.Issue issue = issues.get(issueIndex);
- appendEntryIssue(stringBuilder, issue);
- if ((issueIndex + 1) != issueCount) {
- stringBuilder.append(", ");
- }
- }
- }
-
- private static void appendEntryIssue(final StringBuilder stringBuilder, final ChangelogEntry.Issue issue) {
- stringBuilder
- .append(issue.link)
- .append('[')
- .append(issue.id)
- .append(']');
- }
-
- private static void appendEntryAuthors(
- final StringBuilder stringBuilder,
- final List<ChangelogEntry.Author> authors) {
- stringBuilder.append("by ");
- final int authorCount = authors.size();
- for (int authorIndex = 0; authorIndex < authors.size(); authorIndex++) {
- final ChangelogEntry.Author author = authors.get(authorIndex);
- appendEntryAuthor(stringBuilder, author);
- if ((authorIndex + 1) != authorCount) {
- stringBuilder.append(", ");
- }
- }
- }
-
- private static void appendEntryAuthor(final StringBuilder stringBuilder, final ChangelogEntry.Author author) {
- if (author.id != null) {
- stringBuilder
- .append('`')
- .append(author.id)
- .append('`');
- } else {
- // Normalize author names written in `Doe, John` form.
- if (author.name.contains(",")) {
- String[] nameFields = author.name.split(",", 2);
- stringBuilder.append(nameFields[1].trim());
- stringBuilder.append(nameFields[0].trim());
- } else {
- stringBuilder.append(author.name);
- }
- }
- }
-
- private static void exportUnreleased(
- final Path outputDirectory,
- final Path upcomingReleaseDirectory,
- final ChangelogRelease upcomingRelease) {
- final List<ChangelogEntry> changelogEntries = readChangelogEntries(upcomingReleaseDirectory);
- try {
- exportRelease(outputDirectory, upcomingRelease, null, changelogEntries);
- } catch (final IOException error) {
- throw new UncheckedIOException("failed exporting unreleased changes", error);
- }
- }
-
- private static ChangelogRelease upcomingRelease(final int versionMajor) {
- final String releaseVersion = versionMajor + ".x.x";
- return new ChangelogRelease(releaseVersion, null);
- }
-
- private static void exportReleaseIndex(
- final Path outputDirectory,
- final List<ChangelogRelease> changelogReleases) {
- final String asciiDoc = exportReleaseIndexToAsciiDoc(changelogReleases);
- final byte[] asciiDocBytes = asciiDoc.getBytes(StandardCharsets.UTF_8);
- final Path asciiDocFile = outputDirectory.resolve("index.adoc");
- System.out.format("exporting release index to `%s`%n", asciiDocFile);
- try {
- Files.write(asciiDocFile, asciiDocBytes);
- } catch (final IOException error) {
- throw new UncheckedIOException(error);
- }
- }
-
- @SuppressFBWarnings("VA_FORMAT_STRING_USES_NEWLINE")
- private static String exportReleaseIndexToAsciiDoc(final List<ChangelogRelease> changelogReleases) {
- final StringBuilder stringBuilder = new StringBuilder();
- stringBuilder
- .append(AsciiDocUtils.LICENSE_COMMENT_BLOCK)
- .append('\n')
- .append(AUTO_GENERATION_WARNING_ASCIIDOC)
- .append("\n= Release changelogs\n\n");
- for (int releaseIndex = changelogReleases.size() - 1; releaseIndex >= 0; releaseIndex--) {
- final ChangelogRelease changelogRelease = changelogReleases.get(releaseIndex);
- final String asciiDocFilename = changelogReleaseAsciiDocFilename(changelogRelease);
- final String asciiDocBulletDateSuffix = changelogRelease.date != null
- ? (" (" + changelogRelease.date + ')')
- : "";
- final String asciiDocBullet = String.format(
- "* xref:%s[%s]%s\n",
- asciiDocFilename,
- changelogRelease.version,
- asciiDocBulletDateSuffix);
- stringBuilder.append(asciiDocBullet);
- }
- return stringBuilder.toString();
- }
-
- private static String changelogReleaseAsciiDocFilename(final ChangelogRelease changelogRelease) {
- // Using only the version (that is, avoiding the date) in the filename so that one can determine the link to the changelog of a particular release with only version information.
- return String.format("%s.adoc", changelogRelease.version);
- }
-
-}
diff --git a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/ChangelogExporter.java b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/ChangelogExporter.java
new file mode 100644
index 0000000..b274c0c
--- /dev/null
+++ b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/ChangelogExporter.java
@@ -0,0 +1,194 @@
+/*
+ * 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.changelog.exporter;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.apache.logging.log4j.tools.FileUtils;
+import org.apache.logging.log4j.tools.changelog.ChangelogEntry;
+import org.apache.logging.log4j.tools.changelog.ChangelogFiles;
+import org.apache.logging.log4j.tools.changelog.ChangelogRelease;
+
+public final class ChangelogExporter {
+
+ private ChangelogExporter() {}
+
+ public static void main(final String[] mainArgs) {
+
+ // Read arguments.
+ final ChangelogExporterArgs args = ChangelogExporterArgs.fromSystemProperties();
+
+ // Find release directories.
+ final List<Path> releaseDirectories = FileUtils
+ .findAdjacentFiles(args.changelogDirectory, true)
+ .filter(file -> file.toFile().isDirectory())
+ .sorted(Comparator.comparing(releaseDirectory -> {
+ final Path releaseXmlFile = ChangelogFiles.releaseXmlFile(releaseDirectory);
+ final ChangelogRelease changelogRelease = ChangelogRelease.readFromXmlFile(releaseXmlFile);
+ return changelogRelease.date;
+ }))
+ .collect(Collectors.toList());
+ final int releaseDirectoryCount = releaseDirectories.size();
+
+ // Read the release information files.
+ final List<ChangelogRelease> changelogReleases = releaseDirectories
+ .stream()
+ .map(releaseDirectory -> {
+ final Path releaseXmlFile = ChangelogFiles.releaseXmlFile(releaseDirectory);
+ return ChangelogRelease.readFromXmlFile(releaseXmlFile);
+ })
+ .collect(Collectors.toList());
+
+ // Export releases.
+ if (releaseDirectoryCount > 0) {
+
+ // Export each release directory.
+ for (int releaseIndex = 0; releaseIndex < releaseDirectories.size(); releaseIndex++) {
+ final Path releaseDirectory = releaseDirectories.get(releaseIndex);
+ final ChangelogRelease changelogRelease = changelogReleases.get(releaseIndex);
+ final Path releaseChangelogTemplateFile = ChangelogFiles.releaseChangelogTemplateFile(releaseDirectory);
+ try {
+ exportRelease(
+ args.outputDirectory,
+ releaseDirectory,
+ changelogRelease,
+ releaseChangelogTemplateFile);
+ } catch (final Exception error) {
+ final String message =
+ String.format("failed exporting release from directory `%s`", releaseDirectory);
+ throw new RuntimeException(message, error);
+ }
+ }
+
+ // Report the operation.
+ if (releaseDirectoryCount == 1) {
+ System.out.format("exported a single release directory: `%s`%n", releaseDirectories.get(0));
+ } else {
+ System.out.format(
+ "exported %d release directories: ..., `%s`%n",
+ releaseDirectories.size(),
+ releaseDirectories.get(releaseDirectoryCount - 1));
+ }
+
+ }
+
+ // Export unreleased.
+ ChangelogFiles
+ .unreleasedDirectoryVersionMajors(args.changelogDirectory)
+ .stream()
+ .sorted(Comparator.reverseOrder())
+ .forEach(upcomingReleaseVersionMajor -> {
+ final Path upcomingReleaseDirectory =
+ ChangelogFiles.unreleasedDirectory(args.changelogDirectory, upcomingReleaseVersionMajor);
+ final ChangelogRelease upcomingRelease = upcomingRelease(upcomingReleaseVersionMajor);
+ final Path upcomingReleaseChangelogTemplateFile =
+ ChangelogFiles.releaseChangelogTemplateFile(upcomingReleaseDirectory);
+ System.out.format("exporting upcoming release directory: `%s`%n", upcomingReleaseDirectory);
+ exportRelease(
+ args.outputDirectory,
+ upcomingReleaseDirectory,
+ upcomingRelease,
+ upcomingReleaseChangelogTemplateFile);
+ changelogReleases.add(upcomingRelease);
+ });
+
+ // Export the release index.
+ final Path changelogIndexTemplateFile = ChangelogFiles.indexTemplateFile(args.changelogDirectory);
+ exportIndex(args.outputDirectory, changelogReleases, changelogIndexTemplateFile);
+
+ }
+
+ private static void exportRelease(
+ final Path outputDirectory,
+ final Path releaseDirectory,
+ final ChangelogRelease changelogRelease,
+ final Path releaseChangelogTemplateFile) {
+ final Map<ChangelogEntry.Type, List<ChangelogEntry>> changelogEntriesByType = readChangelogEntriesByType(releaseDirectory);
+ try {
+ exportRelease(outputDirectory, changelogRelease, changelogEntriesByType, releaseChangelogTemplateFile);
+ } catch (final IOException error) {
+ final String message = String.format("failed exporting release from directory `%s`", releaseDirectory);
+ throw new UncheckedIOException(message, error);
+ }
+ }
+
+ private static Map<ChangelogEntry.Type, List<ChangelogEntry>> readChangelogEntriesByType(
+ final Path releaseDirectory) {
+ return FileUtils
+ .findAdjacentFiles(releaseDirectory, true)
+ // Sorting is needed to generate the same output between different runs
+ .sorted()
+ .map(ChangelogEntry::readFromXmlFile)
+ .collect(Collectors.groupingBy(
+ changelogEntry -> changelogEntry.type,
+ // A sorted map is needed to generate the same output between different runs
+ TreeMap::new,
+ Collectors.toList()));
+ }
+
+ private static void exportRelease(
+ final Path outputDirectory,
+ final ChangelogRelease release,
+ final Map<ChangelogEntry.Type, List<ChangelogEntry>> entriesByType,
+ final Path releaseChangelogTemplateFile)
+ throws IOException {
+ final String releaseChangelogFileName = releaseChangelogFileName(release);
+ final Path releaseChangelogFile = outputDirectory.resolve(releaseChangelogFileName);
+ final Map<String, Object> releaseChangelogTemplateData = new LinkedHashMap<>();
+ releaseChangelogTemplateData.put("release", release);
+ releaseChangelogTemplateData.put("entriesByType", entriesByType);
+ FreeMarkerUtils.render(releaseChangelogTemplateFile, releaseChangelogTemplateData, releaseChangelogFile);
+ }
+
+ private static ChangelogRelease upcomingRelease(final int versionMajor) {
+ final String releaseVersion = versionMajor + ".x.x";
+ return new ChangelogRelease(releaseVersion, null);
+ }
+
+ private static void exportIndex(
+ final Path outputDirectory,
+ final List<ChangelogRelease> changelogReleases,
+ final Path indexTemplateFile) {
+ final Object indexTemplateData = Collections.singletonMap(
+ "releases", IntStream
+ .range(0, changelogReleases.size())
+ .boxed()
+ .sorted(Comparator.reverseOrder())
+ .map(releaseIndex -> {
+ final ChangelogRelease changelogRelease = changelogReleases.get(releaseIndex);
+ Map<String, Object> changelogReleaseData = new LinkedHashMap<>();
+ changelogReleaseData.put("version", changelogRelease.version);
+ changelogReleaseData.put("date", changelogRelease.date);
+ changelogReleaseData.put("changelogFileName", releaseChangelogFileName(changelogRelease));
+ return (Object) changelogReleaseData;
+ })
+ .collect(Collectors.toList()));
+ final Path indexFile = outputDirectory.resolve("index.adoc");
+ FreeMarkerUtils.render(indexTemplateFile, indexTemplateData, indexFile);
+ }
+
+ private static String releaseChangelogFileName(final ChangelogRelease changelogRelease) {
+ // Using only the version (that is, avoiding the date) in the filename so that one can determine the link to the changelog of a particular release with only version information.
+ return String.format("%s.adoc", changelogRelease.version);
+ }
+
+}
diff --git a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/AsciiDocExporterArgs.java b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/ChangelogExporterArgs.java
similarity index 83%
rename from log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/AsciiDocExporterArgs.java
rename to log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/ChangelogExporterArgs.java
index 05cdd80..371985f 100644
--- a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/AsciiDocExporterArgs.java
+++ b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/ChangelogExporterArgs.java
@@ -20,21 +20,21 @@ import java.nio.file.Path;
import static org.apache.logging.log4j.tools.PropertyUtils.requireNonBlankPathProperty;
-final class AsciiDocExporterArgs {
+final class ChangelogExporterArgs {
final Path changelogDirectory;
final Path outputDirectory;
- private AsciiDocExporterArgs(final Path changelogDirectory, final Path outputDirectory) {
+ private ChangelogExporterArgs(final Path changelogDirectory, final Path outputDirectory) {
this.changelogDirectory = changelogDirectory;
this.outputDirectory = outputDirectory;
}
- static AsciiDocExporterArgs fromSystemProperties() {
+ static ChangelogExporterArgs fromSystemProperties() {
final Path changelogDirectory = requireNonBlankPathProperty("log4j.changelog.directory");
final Path outputDirectory = requireNonBlankPathProperty("log4j.changelog.exporter.outputDirectory");
- return new AsciiDocExporterArgs(changelogDirectory, outputDirectory);
+ return new ChangelogExporterArgs(changelogDirectory, outputDirectory);
}
}
diff --git a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/FreeMarkerUtils.java b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/FreeMarkerUtils.java
new file mode 100644
index 0000000..1275809
--- /dev/null
+++ b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/exporter/FreeMarkerUtils.java
@@ -0,0 +1,83 @@
+/*
+ * 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.changelog.exporter;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+
+import org.apache.logging.log4j.tools.CharsetUtils;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import freemarker.cache.FileTemplateLoader;
+import freemarker.template.*;
+
+final class FreeMarkerUtils {
+
+ private FreeMarkerUtils() {}
+
+ private static final Configuration CONFIGURATION = createConfiguration();
+
+ @SuppressFBWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME")
+ private static Configuration createConfiguration() {
+ Configuration configuration = new Configuration(Configuration.VERSION_2_3_29);
+ configuration.setDefaultEncoding(CharsetUtils.CHARSET_NAME);
+ configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+ try {
+ configuration.setTemplateLoader(new FileTemplateLoader(new File("/")));
+ } catch (IOException error) {
+ throw new UncheckedIOException(error);
+ }
+ DefaultObjectWrapperBuilder objectWrapperBuilder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_27);
+ objectWrapperBuilder.setExposeFields(true);
+ DefaultObjectWrapper objectWrapper = objectWrapperBuilder.build();
+ configuration.setObjectWrapper(objectWrapper);
+ configuration.setLogTemplateExceptions(false);
+ configuration.setWrapUncheckedExceptions(true);
+ configuration.setFallbackOnNullLoopVariable(false);
+ return configuration;
+ }
+
+ @SuppressFBWarnings("TEMPLATE_INJECTION_FREEMARKER")
+ static void render(final Path templateFile, final Object templateData, final Path outputFile) {
+ try {
+ final Template template = CONFIGURATION.getTemplate(templateFile.toAbsolutePath().toString());
+ final Path outputFileParent = outputFile.getParent();
+ if (outputFileParent != null) {
+ Files.createDirectories(outputFileParent);
+ }
+ try (final BufferedWriter outputFileWriter = Files.newBufferedWriter(
+ outputFile,
+ CharsetUtils.CHARSET,
+ StandardOpenOption.CREATE,
+ StandardOpenOption.TRUNCATE_EXISTING)) {
+ template.process(templateData, outputFileWriter);
+ }
+ } catch (final Exception error) {
+ final String message = String.format(
+ "failed rendering template `%s` to file `%s`",
+ templateFile,
+ outputFile);
+ throw new RuntimeException(message, error);
+ }
+ }
+
+}
diff --git a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/releaser/ChangelogReleaser.java b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/releaser/ChangelogReleaser.java
index 72b0e7b..c8bb858 100644
--- a/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/releaser/ChangelogReleaser.java
+++ b/log4j-changelog/src/main/java/org/apache/logging/log4j/tools/changelog/releaser/ChangelogReleaser.java
@@ -18,12 +18,10 @@ package org.apache.logging.log4j.tools.changelog.releaser;
import java.io.IOException;
import java.io.UncheckedIOException;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
-import org.apache.logging.log4j.tools.AsciiDocUtils;
import org.apache.logging.log4j.tools.FileUtils;
import org.apache.logging.log4j.tools.VersionUtils;
import org.apache.logging.log4j.tools.changelog.ChangelogFiles;
@@ -48,23 +46,22 @@ public final class ChangelogReleaser {
System.out.format("using `%s` for the release date%n", releaseDate);
// Populate the changelog entry files in the release directory.
+ final Path unreleasedDirectory = ChangelogFiles.unreleasedDirectory(args.changelogDirectory, releaseVersionMajor);
final Path releaseDirectory = releaseDirectory(args.changelogDirectory, args.releaseVersion);
- populateChangelogEntryFiles(args.changelogDirectory, releaseVersionMajor, releaseDirectory);
+ populateChangelogEntryFiles(unreleasedDirectory, releaseDirectory);
// Write the release information.
populateReleaseXmlFiles(releaseDate, args.releaseVersion, releaseDirectory);
- // Write the release introduction.
- populateReleaseIntroAsciiDocFile(releaseDirectory);
+ // Write the release changelog template.
+ populateReleaseChangelogTemplateFile(unreleasedDirectory, releaseDirectory);
}
private static void populateChangelogEntryFiles(
- final Path changelogDirectory,
- int releaseVersionMajor,
+ final Path unreleasedDirectory,
final Path releaseDirectory)
throws IOException {
- final Path unreleasedDirectory = ChangelogFiles.unreleasedDirectory(changelogDirectory, releaseVersionMajor);
if (Files.exists(releaseDirectory)) {
System.out.format(
"release directory `%s` already exists, only moving the changelog entry files from `%s`%n",
@@ -80,7 +77,7 @@ public final class ChangelogReleaser {
private static void moveUnreleasedChangelogEntryFiles(final Path unreleasedDirectory, final Path releaseDirectory) {
FileUtils
- .findAdjacentFiles(unreleasedDirectory)
+ .findAdjacentFiles(unreleasedDirectory, true)
.forEach(unreleasedChangelogEntryFile -> {
final String fileName = unreleasedChangelogEntryFile.getFileName().toString();
final Path releasedChangelogEntryFile = releaseDirectory.resolve(fileName);
@@ -90,11 +87,7 @@ public final class ChangelogReleaser {
try {
Files.move(unreleasedChangelogEntryFile, releasedChangelogEntryFile);
} catch (final IOException error) {
- final String message = String.format(
- "failed to move `%s` to `%s`",
- unreleasedChangelogEntryFile,
- releasedChangelogEntryFile);
- throw new UncheckedIOException(message, error);
+ throw new UncheckedIOException(error);
}
});
}
@@ -126,13 +119,17 @@ public final class ChangelogReleaser {
changelogRelease.writeToXmlFile(releaseXmlFile);
}
- private static void populateReleaseIntroAsciiDocFile(final Path releaseDirectory) throws IOException {
- final Path introAsciiDocFile = ChangelogFiles.introAsciiDocFile(releaseDirectory);
- if (Files.exists(introAsciiDocFile)) {
- System.out.format("keeping the existing intro file: `%s`%n", introAsciiDocFile);
+ private static void populateReleaseChangelogTemplateFile(
+ final Path unreleasedDirectory,
+ final Path releaseDirectory)
+ throws IOException {
+ final Path targetFile = ChangelogFiles.releaseChangelogTemplateFile(releaseDirectory);
+ if (Files.exists(targetFile)) {
+ System.out.format("keeping the existing changelog template file: `%s`%n", targetFile);
} else {
- Files.write(introAsciiDocFile, AsciiDocUtils.LICENSE_COMMENT_BLOCK.getBytes(StandardCharsets.UTF_8));
- System.out.format("created the intro file: `%s`%n", introAsciiDocFile);
+ final Path sourceFile = ChangelogFiles.releaseChangelogTemplateFile(unreleasedDirectory);
+ System.out.format("moving the changelog template file `%s` to `%s`%n", sourceFile, targetFile);
+ Files.move(sourceFile, targetFile);
}
}
diff --git a/log4j-tools-parent/pom.xml b/log4j-tools-parent/pom.xml
index 6885985..2c1c8ab 100644
--- a/log4j-tools-parent/pom.xml
+++ b/log4j-tools-parent/pom.xml
@@ -52,6 +52,7 @@
<maven.site.deploy.skip>true</maven.site.deploy.skip>
<!-- dependency versions -->
+ <freemarker.version>2.3.31</freemarker.version>
<spotbugs.version>4.7.3</spotbugs.version>
<!-- plugin versions -->
@@ -64,11 +65,19 @@
<dependencyManagement>
<dependencies>
+
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<version>${spotbugs.version}</version>
</dependency>
+
+ <dependency>
+ <groupId>org.freemarker</groupId>
+ <artifactId>freemarker</artifactId>
+ <version>${freemarker.version}</version>
+ </dependency>
+
</dependencies>
</dependencyManagement>