Skip to content

Commit 1fc2014

Browse files
authored
(main) Port fixes for maven-site breadcrumbs from v2.2.x #763 (#774)
* Add new HeaderParser and HeaderMetadata components to obtain Asciidoctor header details and inject them into the Sink header * Renames package `org.asciidoctor.maven.site.ast` to `org.asciidoctor.maven.site.parser` to match module name * Updated Integration Tests * Quick docs update (may require review after v3.0.0 release)
1 parent 5432f0c commit 1fc2014

File tree

56 files changed

+853
-166
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+853
-166
lines changed

CHANGELOG.adoc

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Bug Fixes::
1818
* Fix default value for eruby which caused a fail when using erb templates (#610)
1919
* Fix maven properties not being injected as attributes during site conversion (#656)
2020
* Remove Java 'requires open access' module warning in modern Java versions with JRuby v9.4.5.0 (#553)
21+
* Fix breadcrumbs not showing the document title in maven-site pages (#763)
2122

2223
Improvements::
2324

asciidoctor-converter-doxia-module/src/it/maven-site-plugin/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<plugin>
2323
<groupId>org.apache.maven.plugins</groupId>
2424
<artifactId>maven-project-info-reports-plugin</artifactId>
25-
<version>3.1.2</version>
25+
<version>3.4.5</version>
2626
</plugin>
2727
<!-- tag::plugin-decl[] -->
2828
<plugin>

asciidoctor-converter-doxia-module/src/it/maven-site-plugin/src/site/asciidoc/file-with-toc.adoc

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
= File with TOC
2+
The Author
3+
:docdatetime: 2024-02-07 23:36:29
24
:toc:
35

46
[.lead]

asciidoctor-converter-doxia-module/src/it/maven-site-plugin/src/site/site.xml

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<project name="Maven Site Plugin IT">
22
<body>
3+
<breadcrumbs>
4+
<item name="Doxia" href="https://maven.apache.org/doxia/index.html"/>
5+
<item name="Trunk" href="https://maven.apache.org/doxia/doxia/index.html"/>
6+
</breadcrumbs>
37
<menu ref="reports" inherit="top"/>
48
<menu ref="modules"/>
59
<menu name="AsciiDoc Pages">
@@ -10,6 +14,6 @@
1014
<skin>
1115
<groupId>org.apache.maven.skins</groupId>
1216
<artifactId>maven-fluido-skin</artifactId>
13-
<version>1.11.1</version>
17+
<version>1.12.0</version>
1418
</skin>
1519
</project>

asciidoctor-converter-doxia-module/src/it/maven-site-plugin/validate.groovy

+88-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import java.nio.file.Files
2-
import java.util.regex.Matcher
32
import java.util.regex.Pattern
43

54
File outputDir = new File(basedir, "target/site")
@@ -12,44 +11,42 @@ final Pattern elementIdPattern = Pattern.compile(".* id=\"(.*?)\".*")
1211

1312
def asserter = new FileAsserter()
1413

14+
def tocEntryCapturer = new PatternCapturer("<li><a href=\"#(.*?)\">.*")
15+
def elementIdCapturer = new PatternCapturer(".* id=\"(.*?)\".*")
16+
1517
for (String expectedFile : expectedFiles) {
1618
final File file = new File(outputDir, expectedFile)
17-
println "Checking for presence of $file"
1819

20+
println "Checking for presence of $file"
1921
asserter.isNotEmpty(file)
2022

2123
if (expectedFile == "file-with-toc.html") {
2224
List lines = Files.readAllLines(file.toPath())
2325
println "Ensuring IDs match TOC links"
2426

25-
List tocEntries = new ArrayList()
27+
// == Assert TOC ==
2628
for (String line : lines) {
27-
Matcher matcher = tocEntry.matcher(line)
28-
if (matcher.matches()) {
29-
tocEntries.add(matcher.group(1))
30-
}
29+
tocEntryCapturer.tryMatches(line)
3130

32-
matcher = elementIdPattern.matcher(line)
33-
if (matcher.matches()) {
34-
String elementId = matcher.group(1)
35-
if (tocEntries.contains(elementId)) {
36-
tocEntries.remove(tocEntries.indexOf(elementId))
37-
}
31+
String match = elementIdCapturer.tryMatches(line)
32+
if (match != null) {
33+
tocEntryCapturer.remove(match)
3834
}
3935
}
4036

41-
if (tocEntries.size() != 0) {
42-
throw new Exception("Couldn't find matching IDs for the following TOC entries: $tocEntries")
37+
if (tocEntryCapturer.size() != 0) {
38+
throw new Exception("Couldn't find matching IDs for the following TOC entries: ${tocEntryCapturer.getHits()}")
4339
}
4440

4541
boolean includeResolved = false
4642
boolean sourceHighlighted = false
4743

44+
// == Assert includes ==
4845
for (String line : lines) {
4946
if (!includeResolved && line.contains("Content included from the file ")) {
5047
includeResolved = true
5148
} else if (!sourceHighlighted && line.contains("<span style=\"color:#070;font-weight:bold\">&lt;plugin&gt;</span>")) {
52-
sourceHighlighted = true
49+
sourceHighlighted = true;
5350
}
5451
}
5552

@@ -72,6 +69,46 @@ for (String expectedFile : expectedFiles) {
7269
if (!foundReplacement) {
7370
throw new Exception("Maven properties not replaced.")
7471
}
72+
73+
// == Assert header metadata ==
74+
def metaPattern = Pattern.compile("<meta name=\"(author|date)\" content=\"(.*)\" />")
75+
boolean headFound = false
76+
Map<String, String> metaTags = new HashMap<>()
77+
78+
for (String line : lines) {
79+
if (!headFound) {
80+
headFound = line.endsWith("<head>")
81+
continue
82+
}
83+
if (line.endsWith("</head>")) break
84+
def matcher = metaPattern.matcher(line.trim())
85+
if (matcher.matches()) {
86+
metaTags.put(matcher.group(1), matcher.group(2))
87+
}
88+
}
89+
90+
if (metaTags['author'] != 'The Author')
91+
throw new RuntimeException("Author not found in $metaTags")
92+
if (metaTags['date'] != '2024-02-07 23:36:29')
93+
throw new RuntimeException("docdatetime not found in: $metaTags")
94+
95+
// assert breadcrumbs
96+
boolean breadcrumbTagFound = false
97+
boolean breadcrumbFound = false
98+
final String docTitle = "File with TOC"
99+
100+
for (String line : lines) {
101+
if (!breadcrumbTagFound) {
102+
breadcrumbTagFound = line.endsWith("<div id=\"breadcrumbs\">")
103+
continue
104+
}
105+
println "Testing: ${line.trim()}"
106+
breadcrumbFound = line.trim().endsWith("${docTitle}</li>")
107+
if (breadcrumbFound) break
108+
}
109+
110+
if (!breadcrumbFound)
111+
throw new RuntimeException("No breadcrumb found: expected title: $docTitle")
75112
}
76113
}
77114

@@ -95,4 +132,39 @@ class FileAsserter {
95132
}
96133
}
97134

135+
class PatternCapturer {
136+
137+
private final Pattern pattern
138+
private final List<String> hits
139+
140+
PatternCapturer(String pattern) {
141+
this.pattern = Pattern.compile(pattern)
142+
this.hits = new ArrayList()
143+
}
144+
145+
146+
String tryMatches(String text) {
147+
def matcher = pattern.matcher(text)
148+
if (matcher.matches()) {
149+
def group = matcher.group(1)
150+
hits.add(group)
151+
return group
152+
}
153+
return null
154+
}
155+
156+
void remove(String text) {
157+
hits.remove(text)
158+
}
159+
160+
int size() {
161+
return hits.size()
162+
}
163+
164+
List<String> getHits() {
165+
return hits
166+
}
167+
}
168+
169+
98170
return true

asciidoctor-converter-doxia-module/src/main/java/org/asciidoctor/maven/site/AsciidoctorConverterDoxiaParser.java

+30-26
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
package org.asciidoctor.maven.site;
22

3+
import javax.inject.Inject;
4+
import javax.inject.Provider;
5+
import java.io.File;
6+
import java.io.IOException;
7+
import java.io.Reader;
8+
import java.util.logging.Logger;
9+
310
import org.apache.maven.doxia.parser.AbstractTextParser;
411
import org.apache.maven.doxia.parser.ParseException;
512
import org.apache.maven.doxia.parser.Parser;
613
import org.apache.maven.doxia.sink.Sink;
714
import org.apache.maven.project.MavenProject;
8-
import org.asciidoctor.*;
15+
import org.asciidoctor.Asciidoctor;
16+
import org.asciidoctor.AttributesBuilder;
17+
import org.asciidoctor.OptionsBuilder;
18+
import org.asciidoctor.SafeMode;
919
import org.asciidoctor.maven.log.LogHandler;
1020
import org.asciidoctor.maven.log.LogRecordFormatter;
1121
import org.asciidoctor.maven.log.LogRecordsProcessors;
1222
import org.asciidoctor.maven.log.MemoryLogHandler;
23+
import org.asciidoctor.maven.site.SiteConverterDecorator.Result;
1324
import org.codehaus.plexus.component.annotations.Component;
1425
import org.codehaus.plexus.util.IOUtil;
1526
import org.codehaus.plexus.util.xml.Xpp3Dom;
1627

17-
import javax.inject.Inject;
18-
import javax.inject.Provider;
19-
import java.io.File;
20-
import java.io.IOException;
21-
import java.io.Reader;
22-
import java.util.logging.Logger;
23-
2428
/**
2529
* This class is used by <a href="https://maven.apache.org/doxia/overview.html">the Doxia framework</a>
2630
* to handle the actual parsing of the AsciiDoc input files into HTML to be consumed/wrapped
@@ -67,31 +71,36 @@ public void parse(Reader reader, Sink sink, String reference) throws ParseExcept
6771
final Asciidoctor asciidoctor = Asciidoctor.Factory.create();
6872

6973
SiteConversionConfiguration conversionConfig = new SiteConversionConfigurationParser(project)
70-
.processAsciiDocConfig(siteConfig, defaultOptions(siteDirectory), defaultAttributes());
74+
.processAsciiDocConfig(siteConfig, defaultOptions(siteDirectory), defaultAttributes());
7175
for (String require : conversionConfig.getRequires()) {
7276
requireLibrary(asciidoctor, require);
7377
}
7478

7579
final LogHandler logHandler = getLogHandlerConfig(siteConfig);
7680
final MemoryLogHandler memoryLogHandler = asciidoctorLoggingSetup(asciidoctor, logHandler, siteDirectory);
7781

78-
// QUESTION should we keep OptionsBuilder & AttributesBuilder separate for call to convertAsciiDoc?
79-
String asciidocHtml = convertAsciiDoc(asciidoctor, source, conversionConfig.getOptions());
82+
final SiteConverterDecorator siteConverter = new SiteConverterDecorator(asciidoctor);
83+
final Result headerMetadata = siteConverter.process(source, conversionConfig.getOptions());
84+
8085
try {
8186
// process log messages according to mojo configuration
8287
new LogRecordsProcessors(logHandler, siteDirectory, errorMessage -> getLog().error(errorMessage))
83-
.processLogRecords(memoryLogHandler);
88+
.processLogRecords(memoryLogHandler);
8489
} catch (Exception exception) {
8590
throw new ParseException(exception.getMessage(), exception);
8691
}
8792

88-
sink.rawText(asciidocHtml);
93+
new HeadParser(sink)
94+
.parse(headerMetadata.getHeaderMetadata());
95+
96+
sink.rawText(headerMetadata.getHtml());
8997
}
9098

99+
91100
private MemoryLogHandler asciidoctorLoggingSetup(Asciidoctor asciidoctor, LogHandler logHandler, File siteDirectory) {
92101

93102
final MemoryLogHandler memoryLogHandler = new MemoryLogHandler(logHandler.getOutputToConsole(),
94-
logRecord -> getLog().info(LogRecordFormatter.format(logRecord, siteDirectory)));
103+
logRecord -> getLog().info(LogRecordFormatter.format(logRecord, siteDirectory)));
95104
asciidoctor.registerLogHandler(memoryLogHandler);
96105
// disable default console output of AsciidoctorJ
97106
Logger.getLogger("asciidoctor").setUseParentHandlers(false);
@@ -119,16 +128,16 @@ protected File resolveSiteDirectory(MavenProject project, Xpp3Dom siteConfig) {
119128
}
120129

121130
protected OptionsBuilder defaultOptions(File siteDirectory) {
122-
return Options.builder()
123-
.backend("xhtml")
124-
.safe(SafeMode.UNSAFE)
125-
.baseDir(new File(siteDirectory, ROLE_HINT));
131+
return OptionsBuilder.options()
132+
.backend("xhtml")
133+
.safe(SafeMode.UNSAFE)
134+
.baseDir(new File(siteDirectory, ROLE_HINT));
126135
}
127136

128137
protected AttributesBuilder defaultAttributes() {
129-
return Attributes.builder()
130-
.attribute("idprefix", "@")
131-
.attribute("showtitle", "@");
138+
return AttributesBuilder.attributes()
139+
.attribute("idprefix", "@")
140+
.attribute("showtitle", "@");
132141
}
133142

134143
private void requireLibrary(Asciidoctor asciidoctor, String require) {
@@ -140,9 +149,4 @@ private void requireLibrary(Asciidoctor asciidoctor, String require) {
140149
}
141150
}
142151
}
143-
144-
protected String convertAsciiDoc(Asciidoctor asciidoctor, String source, Options options) {
145-
return asciidoctor.convert(source, options);
146-
}
147-
148152
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package org.asciidoctor.maven.site;
2+
3+
import java.util.Map;
4+
5+
import org.asciidoctor.Asciidoctor;
6+
import org.asciidoctor.Options;
7+
import org.asciidoctor.OptionsBuilder;
8+
import org.asciidoctor.ast.Document;
9+
10+
/**
11+
* Asciidoctor conversion wrapper for maven-site integration.
12+
* In addition to conversion, handles header metadata extraction.
13+
*/
14+
class SiteConverterDecorator {
15+
16+
private final Asciidoctor asciidoctor;
17+
18+
SiteConverterDecorator(Asciidoctor asciidoctor) {
19+
this.asciidoctor = asciidoctor;
20+
}
21+
22+
Result process(String content, Options options) {
23+
final Document document = asciidoctor.load(content, headerProcessingMetadata(options));
24+
final HeaderMetadata headerMetadata = HeaderMetadata.from(document);
25+
26+
final String html = asciidoctor.convert(content, options);
27+
28+
return new Result(headerMetadata, html);
29+
}
30+
31+
private static Options headerProcessingMetadata(Options options) {
32+
Map<String, Object> optionsMap = options.map();
33+
OptionsBuilder builder = Options.builder();
34+
for (Map.Entry<String, Object> entry : optionsMap.entrySet()) {
35+
builder.option(entry.getKey(), entry.getValue());
36+
}
37+
38+
builder.parseHeaderOnly(Boolean.TRUE);
39+
return builder.build();
40+
}
41+
42+
/**
43+
* Simple tuple to return Asciidoctor extracted metadata and conversion result.
44+
*/
45+
final class Result {
46+
47+
private final HeaderMetadata headerMetadata;
48+
private final String html;
49+
50+
Result(HeaderMetadata headerMetadata, String html) {
51+
this.headerMetadata = headerMetadata;
52+
this.html = html;
53+
}
54+
55+
HeaderMetadata getHeaderMetadata() {
56+
return headerMetadata;
57+
}
58+
59+
String getHtml() {
60+
return html;
61+
}
62+
}
63+
64+
65+
}

0 commit comments

Comments
 (0)