Skip to content

Commit cf3e288

Browse files
committed
Add include-code support
Add 'include-code' convention support. Closes gh-56
1 parent 4c77f31 commit cf3e288

File tree

23 files changed

+515
-41
lines changed

23 files changed

+515
-41
lines changed

README.adoc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,42 @@ initializzzing=initializing
476476

477477

478478

479+
=== Convention Based Code Import
480+
Convention based code imports allows you to quickly import code snippets from package names that are constructed using the section ID.
481+
For example, the following `.adoc` file will try to import the Java file `{docs-java}/my/exampleproject/SomeCode.java`.
482+
483+
[source,subs="verbatim,attributes"]
484+
....
485+
[[my.example-project]]
486+
== My Example Project
487+
import-code::SomeCode[]
488+
....
489+
490+
The following languages are supported by convention based imports:
491+
492+
|===
493+
| Language | Root Directory Property | Extension
494+
495+
| Java
496+
| `{docs-java}`
497+
| `.java`
498+
499+
| Kotlin
500+
| `{docs-kotlin}`
501+
| `.kt`
502+
503+
| Groovy
504+
| `{docs-groovy}`
505+
| `.grovy`
506+
|===
507+
508+
If more than one language file is found then tabs will automatically be created to allow the user to see all the examples.
509+
An exception will be raised if no files are found.
510+
511+
The package name is constructed by removing all `-` characters from the section ID and replacing all `.`'s with `/`.
512+
513+
514+
479515
== Contributing
480516
If you're looking to contribute to this project, or you're just trying to navigate the code please take a look at the link:CONTRIBUTING.adoc[CONTRIBUTING] file.
481517

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 the original author or authors.
2+
* Copyright 2021-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +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;
2021
import org.asciidoctor.Asciidoctor;
2122
import org.asciidoctor.jruby.extension.spi.ExtensionRegistry;
2223

@@ -31,6 +32,7 @@ public class SpringBackendsExtensionRegistry implements ExtensionRegistry {
3132
public void register(Asciidoctor asciidoctor) {
3233
asciidoctor.requireLibrary("spring-asciidoctor-backends");
3334
asciidoctor.javaExtensionRegistry().docinfoProcessor(new AnchorRewriteDocinfoProcessor());
35+
asciidoctor.javaExtensionRegistry().blockMacro(new IncludeCodeBlockMacroProcessor());
3436
}
3537

3638
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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.File;
20+
import java.io.IOException;
21+
import java.nio.file.Files;
22+
import java.nio.file.Path;
23+
import java.util.ArrayList;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.stream.Collectors;
27+
import java.util.stream.Stream;
28+
29+
import org.asciidoctor.ast.Block;
30+
import org.asciidoctor.ast.ContentNode;
31+
import org.asciidoctor.ast.Document;
32+
import org.asciidoctor.ast.Section;
33+
import org.asciidoctor.ast.StructuralNode;
34+
import org.asciidoctor.extension.BlockMacroProcessor;
35+
import org.asciidoctor.extension.Name;
36+
import org.asciidoctor.extension.Processor;
37+
38+
/**
39+
* {@link BlockMacroProcessor} to support {@code include-code} imports.
40+
*
41+
* @author Phillip Webb
42+
*/
43+
@Name("include-code")
44+
public class IncludeCodeBlockMacroProcessor extends BlockMacroProcessor {
45+
46+
@Override
47+
public Object process(StructuralNode parent, String target, Map<String, Object> attributes) {
48+
try {
49+
Document document = parent.getDocument();
50+
Path root = getDirectory(document).toPath();
51+
String sectionId = getSectionId(parent);
52+
String packagePath = getPackagePath(sectionId);
53+
List<Content> contents = new ArrayList<>();
54+
for (Language language : Language.values()) {
55+
Content content = Content.load(document, language, root, packagePath, target);
56+
if (content != null) {
57+
contents.add(content);
58+
}
59+
}
60+
return process(parent, contents);
61+
}
62+
catch (Exception ex) {
63+
throw new IllegalStateException("Error processing include-code::" + target + " : " + ex.getMessage(), ex);
64+
}
65+
}
66+
67+
private File getDirectory(Document document) {
68+
Object directory = document.getOptions().get("base_dir");
69+
directory = (directory != null) ? directory : document.getOptions().get("docdir");
70+
directory = (directory != null) ? directory : new File(".");
71+
return (directory instanceof File) ? (File) directory : new File(directory.toString());
72+
}
73+
74+
private Object process(StructuralNode parent, List<Content> contents) {
75+
if (contents.isEmpty()) {
76+
throw new IllegalStateException("Unable to find code");
77+
}
78+
Block container = createBlock(parent, "open", (String) null);
79+
if (contents.size() == 1) {
80+
container.append(contents.get(0).createBlock(this, parent));
81+
return container;
82+
}
83+
boolean primary = true;
84+
for (Content content : contents) {
85+
Block block = content.createBlock(this, container);
86+
block.setAttribute("role", (primary) ? "primary" : "secondary", true);
87+
container.append(block);
88+
primary = false;
89+
}
90+
return container;
91+
}
92+
93+
private String getSectionId(ContentNode node) {
94+
Section section = getSection(node);
95+
String sectionId = section.getId();
96+
return sectionId;
97+
}
98+
99+
private Section getSection(ContentNode node) {
100+
while (node != null) {
101+
if (node instanceof Section) {
102+
return (Section) node;
103+
}
104+
node = node.getParent();
105+
}
106+
throw new IllegalStateException("Unable to find section");
107+
}
108+
109+
private String getPackagePath(String sectionId) {
110+
return sectionId.replace('.', '/').replace("-", "");
111+
}
112+
113+
static class Content {
114+
115+
private final Language language;
116+
117+
private final String content;
118+
119+
Content(Language language, Path source) throws IOException {
120+
this.language = language;
121+
try (Stream<String> lines = Files.lines(source)) {
122+
this.content = lines.collect(Collectors.joining("\n"));
123+
}
124+
}
125+
126+
Block createBlock(Processor processor, StructuralNode parent) {
127+
Block block = processor.createBlock(parent, "listing", this.content);
128+
block.setStyle("source");
129+
block.setAttribute("language", this.language.getLanguage(), true);
130+
block.setAttribute("indent", 0, true);
131+
block.setAttribute("subs", "verbatim", true);
132+
block.setTitle(this.language.getTitle());
133+
return block;
134+
}
135+
136+
static Content load(Document document, Language language, Path root, String packagePath, String target)
137+
throws IOException {
138+
String path = (String) document.getAttribute(language.getPathAttribute());
139+
if (path != null) {
140+
Path source = root.resolve(path).resolve(packagePath).resolve(target + "." + language.GetExtension());
141+
if (Files.isRegularFile(source)) {
142+
return new Content(language, source);
143+
}
144+
}
145+
return null;
146+
}
147+
148+
}
149+
150+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
/**
20+
* Languages supported by {@link IncludeCodeBlockMacroProcessor}.
21+
*
22+
* @author Phillip Webb
23+
*/
24+
enum Language {
25+
26+
/**
27+
* Java.
28+
*/
29+
JAVA("Java", "docs-java", "java"),
30+
31+
/**
32+
* Kotlin.
33+
*/
34+
KOTLIN("Kotlin", "docs-kotlin", "kt"),
35+
36+
/**
37+
* Groovy.
38+
*/
39+
GROOVY("Groovy", "docs-groovy", "groovy");
40+
41+
private final String title;
42+
43+
private final String pathAttribute;
44+
45+
private final String extension;
46+
47+
Language(String title, String pathAttribute, String extension) {
48+
this.title = title;
49+
this.pathAttribute = pathAttribute;
50+
this.extension = extension;
51+
}
52+
53+
String getTitle() {
54+
return this.title;
55+
}
56+
57+
String getPathAttribute() {
58+
return this.pathAttribute;
59+
}
60+
61+
public String GetExtension() {
62+
return this.extension;
63+
}
64+
65+
String getLanguage() {
66+
return this.name().toLowerCase();
67+
}
68+
69+
}

src/test/java/io/spring/asciidoctor/backend/codetools/ChompListingContentConverterTests.java

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 the original author or authors.
2+
* Copyright 2021-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,72 +34,72 @@ class ChompListingContentConverterTests {
3434

3535
@Test
3636
void convertWhenNotSupportedLanguageReturnsContent(ConvertedHtml html) {
37-
assertThat(html.getElementHtml("code")).isEqualTo("&lt;!-- /**/ test --&gt;");
37+
assertThat(html.getElementByTag("code")).isEqualTo("&lt;!-- /**/ test --&gt;");
3838
}
3939

4040
@Test
4141
void convertWhenChompHeaderReturnsWithoutHeader(ConvertedHtml html) {
42-
assertThat(html.getElementHtml("code")).isEqualTo("package com.example;");
42+
assertThat(html.getElementByTag("code")).isEqualTo("package com.example;");
4343
}
4444

4545
@Test
4646
void convertWhenChompPackageReturnsWithoutPackage(ConvertedHtml html) {
47-
assertThat(html.getElementHtml("code")).isEqualTo("public class Example {}");
47+
assertThat(html.getElementByTag("code")).isEqualTo("public class Example {}");
4848
}
4949

5050
@Test
5151
void convertWhenChompPackageAndReplacementReturnsWithReplacedPackage(ConvertedHtml html, ExpectedHtml expected) {
52-
assertThat(html.getElementHtml("code")).satisfies(expected);
52+
assertThat(html.getElementByTag("code")).satisfies(expected);
5353
}
5454

5555
@Test
5656
void convertWhenShortChompTagReturnsChompedLines(ConvertedHtml html) {
57-
assertThat(html.getElementHtml("code")).isEqualTo("Object chomp = ...");
57+
assertThat(html.getElementByTag("code")).isEqualTo("Object chomp = ...");
5858
}
5959

6060
@Test
6161
void convertWhenMultiLineShortChompTagsReturnsChompedLines(ConvertedHtml html, ExpectedHtml expected) {
62-
assertThat(html.getElementHtml("code")).satisfies(expected);
62+
assertThat(html.getElementByTag("code")).satisfies(expected);
6363
}
6464

6565
@Test
6666
void convertWhenNoChompsReturnsContent(ConvertedHtml html) {
67-
assertThat(html.getElementHtml("code")).isEqualTo("Object nonChomp = /* comment */ new Object();");
67+
assertThat(html.getElementByTag("code")).isEqualTo("Object nonChomp = /* comment */ new Object();");
6868
}
6969

7070
@Test
7171
void convertWhenLineChompTagReturnsChompedLines(ConvertedHtml html) {
72-
assertThat(html.getElementHtml("code")).isEqualTo("Object o =");
72+
assertThat(html.getElementByTag("code")).isEqualTo("Object o =");
7373
}
7474

7575
@Test
7676
void convertWhenLineChompTagWithReplacementReturnsChompedLines(ConvertedHtml html) {
77-
assertThat(html.getElementHtml("code")).isEqualTo("Object o = // ... your instance");
77+
assertThat(html.getElementByTag("code")).isEqualTo("Object o = // ... your instance");
7878
}
7979

8080
@Test
8181
void convertWhenFileChompTagReturnsChompedLines(ConvertedHtml html) {
82-
assertThat(html.getElementHtml("code")).isEqualTo("public class Example {}");
82+
assertThat(html.getElementByTag("code")).isEqualTo("public class Example {}");
8383
}
8484

8585
@Test
8686
void convertWhenFormattersChompReturnsChompedLines(ConvertedHtml html) {
87-
assertThat(html.getElementHtml("code")).isEqualTo("public class Example {}");
87+
assertThat(html.getElementByTag("code")).isEqualTo("public class Example {}");
8888
}
8989

9090
@Test
9191
void convertWhenMethodFormattersChompReturnsChompedLines(ConvertedHtml html, ExpectedHtml expected) {
92-
assertThat(html.getElementHtml("code")).satisfies(expected);
92+
assertThat(html.getElementByTag("code")).satisfies(expected);
9393
}
9494

9595
@Test
9696
void convertWhenSuppressWarningsChompReturnsChompedLines(ConvertedHtml html, ExpectedHtml expected) {
97-
assertThat(html.getElementHtml("code")).satisfies(expected);
97+
assertThat(html.getElementByTag("code")).satisfies(expected);
9898
}
9999

100100
@Test
101101
void convertWhenMultipleChompsReturnsChompedLines(ConvertedHtml html, ExpectedHtml expected) {
102-
assertThat(html.getElementHtml("code")).satisfies(expected);
102+
assertThat(html.getElementByTag("code")).satisfies(expected);
103103
}
104104

105105
}

0 commit comments

Comments
 (0)