Skip to content

Commit 4b58dd4

Browse files
committed
Rewrite include code support using a IncludeCodeBlockMacroProcessor
The original `IncludeCodeBlockMacroProcessor` unfortunately didn't support [tag=] attributes. This commit rewrites it using a `IncludeCodeIncludeProcessor` instead. Closes gh-56
1 parent d290beb commit 4b58dd4

File tree

25 files changed

+273
-225
lines changed

25 files changed

+273
-225
lines changed

README.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ For example, the following `.adoc` file will try to import the Java file `{docs-
484484
....
485485
[[my.example-project]]
486486
== My Example Project
487-
import-code::SomeCode[]
487+
import::code:SomeCode[]
488488
....
489489

490490
The following languages are supported by convention based imports:

src/main/java/io/spring/asciidoctor/backend/SpringBackendsExtensionRegistry.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package io.spring.asciidoctor.backend;
1818

1919
import io.spring.asciidoctor.backend.anchorrewrite.AnchorRewriteDocinfoProcessor;
20-
import io.spring.asciidoctor.backend.includecode.IncludeCodeBlockMacroProcessor;
20+
import io.spring.asciidoctor.backend.includecode.IncludeCodeIncludeProcessor;
2121
import org.asciidoctor.Asciidoctor;
2222
import org.asciidoctor.jruby.extension.spi.ExtensionRegistry;
2323

@@ -32,7 +32,9 @@ public class SpringBackendsExtensionRegistry implements ExtensionRegistry {
3232
public void register(Asciidoctor asciidoctor) {
3333
asciidoctor.requireLibrary("spring-asciidoctor-backends");
3434
asciidoctor.javaExtensionRegistry().docinfoProcessor(new AnchorRewriteDocinfoProcessor());
35-
asciidoctor.javaExtensionRegistry().blockMacro(new IncludeCodeBlockMacroProcessor());
35+
// asciidoctor.javaExtensionRegistry().blockMacro(new
36+
// IncludeCodeBlockMacroProcessor());
37+
asciidoctor.javaExtensionRegistry().includeProcessor(new IncludeCodeIncludeProcessor());
3638
}
3739

3840
}

src/main/java/io/spring/asciidoctor/backend/includecode/IncludeCodeBlockMacroProcessor.java

Lines changed: 0 additions & 151 deletions
This file was deleted.
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
* Copyright 2021-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.spring.asciidoctor.backend.includecode;
18+
19+
import java.io.IOException;
20+
import java.nio.file.Files;
21+
import java.nio.file.Path;
22+
import java.nio.file.Paths;
23+
import java.util.ArrayList;
24+
import java.util.Collections;
25+
import java.util.LinkedHashMap;
26+
import java.util.List;
27+
import java.util.Map;
28+
import java.util.stream.Collectors;
29+
30+
import io.spring.asciidoctor.backend.language.Language;
31+
import org.asciidoctor.ast.Document;
32+
import org.asciidoctor.extension.IncludeProcessor;
33+
import org.asciidoctor.extension.PreprocessorReader;
34+
35+
/**
36+
* {@link IncludeProcessor} to deal with convention based code imports.
37+
*
38+
* @author Phillip Webb
39+
*/
40+
public class IncludeCodeIncludeProcessor extends IncludeProcessor {
41+
42+
private static final String PREFIX = "code:";
43+
44+
private final Map<String, AsciidoctorFile> files = new LinkedHashMap<String, AsciidoctorFile>() {
45+
46+
@Override
47+
protected boolean removeEldestEntry(Map.Entry<String, AsciidoctorFile> eldest) {
48+
return size() > 10;
49+
};
50+
51+
};
52+
53+
@Override
54+
public boolean handles(String target) {
55+
return target.startsWith(PREFIX);
56+
}
57+
58+
@Override
59+
public void process(Document document, PreprocessorReader reader, String target, Map<String, Object> attributes) {
60+
target = target.substring(PREFIX.length());
61+
String includeAttributes = getIncludeAttributes(attributes);
62+
AsciidoctorFile asciidoctorFile = this.files.computeIfAbsent(reader.getFile(), AsciidoctorFile::new);
63+
String sectionId = asciidoctorFile.getSectionId(reader.getLineNumber() - 2);
64+
try {
65+
String packagePath = getPackagePath(sectionId);
66+
List<Include> includes = new ArrayList<>();
67+
for (Language language : Language.values()) {
68+
Include include = Include.load(document, language, includeAttributes, packagePath, target);
69+
if (include != null) {
70+
includes.add(include);
71+
}
72+
}
73+
reader.push_include(getIncludeData(includes), null, null, 0, Collections.emptyMap());
74+
}
75+
catch (Exception ex) {
76+
throw new IllegalStateException(
77+
"Error processing code include " + target + " in " + sectionId + " : " + ex.getMessage(), ex);
78+
}
79+
}
80+
81+
private String getIncludeAttributes(Map<String, Object> attributes) {
82+
StringBuilder markup = new StringBuilder();
83+
markup.append("[");
84+
attributes.forEach((key, value) -> markup.append(key + "=" + value));
85+
markup.append("]");
86+
return markup.toString();
87+
}
88+
89+
private String getIncludeData(List<Include> includes) {
90+
if (includes.isEmpty()) {
91+
throw new IllegalStateException("Unable to find code");
92+
}
93+
StringBuilder data = new StringBuilder();
94+
for (int i = 0; i < includes.size(); i++) {
95+
String role = (includes.size() == 1) ? null : (i == 0) ? "primary" : "secondary";
96+
includes.get(i).append(role, data);
97+
}
98+
return data.toString();
99+
}
100+
101+
private String getPackagePath(String sectionId) {
102+
return sectionId.replace('.', '/').replace("-", "");
103+
}
104+
105+
static class AsciidoctorFile {
106+
107+
private final List<String> lines;
108+
109+
AsciidoctorFile(String filename) {
110+
try {
111+
this.lines = Files.readAllLines(Paths.get(filename));
112+
}
113+
catch (IOException ex) {
114+
throw new IllegalStateException(ex);
115+
}
116+
}
117+
118+
String getSectionId(int lineNumber) {
119+
while (lineNumber >= 0) {
120+
String line = this.lines.get(lineNumber).trim();
121+
if (line.startsWith("[[") && line.endsWith("]]")) {
122+
return line.substring(2, line.length() - 2);
123+
}
124+
lineNumber--;
125+
}
126+
throw new IllegalStateException("Cannot find anchor");
127+
128+
}
129+
130+
}
131+
132+
static class Include {
133+
134+
private final Language language;
135+
136+
private final String includeAttributes;
137+
138+
private Path source;
139+
140+
Include(Language language, String includeAttributes, Path source) throws IOException {
141+
this.language = language;
142+
this.includeAttributes = includeAttributes;
143+
this.source = source;
144+
}
145+
146+
void append(String role, StringBuilder data) {
147+
data.append("[source," + this.language.getId() + ",indent=0,subs=\"verbatim\"");
148+
if (role != null) {
149+
data.append("role=" + role);
150+
}
151+
data.append("]\n");
152+
if (role != null) {
153+
data.append("." + this.language.getTitle() + "\n");
154+
}
155+
data.append("----\n");
156+
data.append("include::" + this.source.toAbsolutePath().toString() + this.includeAttributes + "\n");
157+
data.append("----\n");
158+
}
159+
160+
static Include load(Document document, Language language, String includeAttributes, String packagePath,
161+
String target) throws IOException {
162+
String path = (String) document.getAttribute(language.getPathAttribute());
163+
if (path != null) {
164+
Path source = cleanPath(path + "/" + packagePath + "/" + target + "." + language.GetExtension());
165+
if (Files.isRegularFile(source)) {
166+
return new Include(language, includeAttributes, source);
167+
}
168+
}
169+
return null;
170+
}
171+
172+
private static Path cleanPath(String path) {
173+
path = path.replace("//", "/");
174+
List<String> elements = new ArrayList<>();
175+
for (String element : path.split("\\/")) {
176+
if ("..".equals(element) && !elements.isEmpty()) {
177+
elements.remove(elements.size() - 1);
178+
}
179+
else {
180+
elements.add(element);
181+
}
182+
}
183+
String cleanPath = elements.stream().collect(Collectors.joining("/"));
184+
cleanPath = (path.startsWith("/")) ? "/" + cleanPath : cleanPath;
185+
return Paths.get(cleanPath);
186+
}
187+
188+
}
189+
190+
}

src/test/java/io/spring/asciidoctor/backend/includecode/IncludeCodeBlockMacroProcessorTests.java renamed to src/test/java/io/spring/asciidoctor/backend/includecode/IncludeCodeIncludeProcessorTests.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,23 @@
2727
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
2828

2929
/**
30-
* Tests for {@link IncludeCodeBlockMacroProcessor}.
30+
* Tests for {@link IncludeCodeIncludeProcessor}.
3131
*
3232
* @author Phillip Webb
3333
*/
3434
@ExtendWith(AsciidoctorExtension.class)
35-
class IncludeCodeBlockMacroProcessorTests {
35+
class IncludeCodeIncludeProcessorTests {
3636

3737
@Test
3838
void includeOnlyJava(ConvertedHtml html, ExpectedHtml expected) {
3939
assertThat(html.getElementByClass("sectionbody")).satisfies(expected.whenIgnoringTrailingWhitespace());
4040
}
4141

42+
@Test
43+
void includeOnlyJavaWithTag(ConvertedHtml html, ExpectedHtml expected) {
44+
assertThat(html.getElementByClass("sectionbody")).satisfies(expected.whenIgnoringTrailingWhitespace());
45+
}
46+
4247
@Test
4348
void includeJavaAndKotlin(ConvertedHtml html, ExpectedHtml expected) {
4449
assertThat(html.getElementByClass("sectionbody")).satisfies(expected.whenIgnoringTrailingWhitespace());

0 commit comments

Comments
 (0)