Skip to content

Commit 2f20303

Browse files
authored
Cleanup Static analysis warnings for webify... (kroxylicious#3022)
* Move webify to kroxylicious-docs Signed-off-by: Sam Barker <[email protected]> * Simple warning cleanup Signed-off-by: Sam Barker <[email protected]> * Use non deprecated API Signed-off-by: Sam Barker <[email protected]> * Avoid potential NPE Signed-off-by: Sam Barker <[email protected]> * Fix possible NPE from spotbugs in call. Signed-off-by: Sam Barker <[email protected]> * Fix possible NPE from spotbugs in tocify. Signed-off-by: Sam Barker <[email protected]> * Fix possible NPE from spotbugs in walk. Signed-off-by: Sam Barker <[email protected]> * Fix possible NPEs from spotbugs in DocConverter. Signed-off-by: Sam Barker <[email protected]> * Intellij: simplify constant conditions Signed-off-by: Sam Barker <[email protected]> * Don't leak FS Signed-off-by: Sam Barker <[email protected]> * Intellij warning cleanup Signed-off-by: Sam Barker <[email protected]> * Fixup jbang execution of webify Signed-off-by: Sam Barker <[email protected]> * Update jbang deps to latest versions Signed-off-by: Sam Barker <[email protected]> * Sync jbang deps with whats in the pom. This means that dependabot catches them too Signed-off-by: Sam Barker <[email protected]> * Intellij warning: never thrown Signed-off-by: Sam Barker <[email protected]> * use better property for resolving webify Signed-off-by: Sam Barker <[email protected]> * Suppress warning about script naming. Signed-off-by: Sam Barker <[email protected]> --------- Signed-off-by: Sam Barker <[email protected]>
1 parent 202e3be commit 2f20303

File tree

4 files changed

+367
-244
lines changed

4 files changed

+367
-244
lines changed

.idea/misc.xml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

kroxylicious-docs/pom.xml

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,64 @@
2020
<artifactId>kroxylicious-docs</artifactId>
2121
<name>Kroxylicious documentation</name>
2222
<description>User-facing documentation for the Kroxylicuous project.</description>
23+
<properties>
24+
<jsoup.version>1.21.2</jsoup.version>
25+
</properties>
26+
27+
<dependencies>
28+
<dependency>
29+
<groupId>info.picocli</groupId>
30+
<artifactId>picocli</artifactId>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.jsoup</groupId>
34+
<artifactId>jsoup</artifactId>
35+
<version>${jsoup.version}</version>
36+
</dependency>
37+
<dependency>
38+
<groupId>com.fasterxml.jackson.core</groupId>
39+
<artifactId>jackson-databind</artifactId>
40+
</dependency>
41+
<dependency>
42+
<groupId>com.fasterxml.jackson.dataformat</groupId>
43+
<artifactId>jackson-dataformat-yaml</artifactId>
44+
</dependency>
45+
<dependency>
46+
<groupId>org.slf4j</groupId>
47+
<artifactId>slf4j-api</artifactId>
48+
</dependency>
49+
<dependency>
50+
<groupId>org.slf4j</groupId>
51+
<artifactId>slf4j-simple</artifactId>
52+
</dependency>
53+
</dependencies>
2354

2455
<build>
56+
<sourceDirectory>src/main/java</sourceDirectory>
57+
<resources>
58+
<resource>
59+
<directory>${project.build.sourceDirectory}</directory>
60+
<filtering>true</filtering>
61+
</resource>
62+
</resources>
63+
2564
<plugins>
65+
<plugin>
66+
<groupId>org.apache.maven.plugins</groupId>
67+
<artifactId>maven-resources-plugin</artifactId>
68+
<executions>
69+
<execution>
70+
<id>update-jbang-deps</id>
71+
<phase>generate-sources</phase>
72+
<goals>
73+
<goal>resources</goal>
74+
</goals>
75+
<configuration>
76+
<outputDirectory>${project.build.directory}/generated-sources</outputDirectory>
77+
</configuration>
78+
</execution>
79+
</executions>
80+
</plugin>
2681
<plugin>
2782
<groupId>org.asciidoctor</groupId>
2883
<artifactId>asciidoctor-maven-plugin</artifactId>
@@ -110,13 +165,10 @@
110165
<configuration>
111166
<skip>${skipDocs}</skip>
112167
<!-- jbang plugin has a somewhat funky handling of cwd, https://github.com/jbangdev/jbang/issues/2062 -->
113-
<!--suppress UnresolvedMavenProperty -->
114-
<script>${rootdir}/scripts/webify.java</script>
168+
<script>${project.build.directory}/generated-sources/webify.java</script>
115169
<args>
116170
<arg>--project-version=${project.version}</arg>
117-
<!--suppress UnresolvedMavenProperty -->
118171
<arg>--src-dir=${project.build.directory}/docs/embedded/</arg>
119-
<!--suppress UnresolvedMavenProperty -->
120172
<arg>--dest-dir=${project.build.directory}/web</arg>
121173
<arg>--tocify-omit=index.html</arg>
122174
<arg>--tocify-omit=doc-schema.yaml</arg>
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
/*
2+
* Copyright Kroxylicious Authors.
3+
*
4+
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
7+
///usr/bin/env jbang "$0" "$@" ; exit $?
8+
//JAVA 21+
9+
//DEPS org.jsoup:jsoup:${jsoup.version}
10+
//DEPS info.picocli:picocli:${picocli.version}
11+
//DEPS com.fasterxml.jackson.core:jackson-core:${jackson.version}
12+
//DEPS com.fasterxml.jackson.core:jackson-databind:${jackson.version}
13+
//DEPS com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${jackson.version}
14+
//DEPS org.slf4j:slf4j-api:${slf4j.version}
15+
//DEPS org.slf4j:slf4j-simple:${slf4j.version}
16+
import java.io.IOException;
17+
import java.nio.file.FileSystem;
18+
import java.nio.file.FileSystems;
19+
import java.nio.file.Files;
20+
import java.nio.file.Path;
21+
import java.nio.file.PathMatcher;
22+
import java.nio.file.StandardCopyOption;
23+
import java.nio.file.StandardOpenOption;
24+
import java.util.ArrayList;
25+
import java.util.Comparator;
26+
import java.util.List;
27+
import java.util.Map;
28+
import java.util.Objects;
29+
import java.util.Optional;
30+
import java.util.Set;
31+
import java.util.concurrent.Callable;
32+
import java.util.function.Consumer;
33+
34+
import org.jsoup.Jsoup;
35+
import org.jsoup.nodes.Document;
36+
import org.jsoup.nodes.Node;
37+
import org.slf4j.Logger;
38+
import org.slf4j.LoggerFactory;
39+
40+
import com.fasterxml.jackson.databind.JsonNode;
41+
import com.fasterxml.jackson.databind.ObjectMapper;
42+
import com.fasterxml.jackson.databind.node.ObjectNode;
43+
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature;
44+
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
45+
46+
import picocli.CommandLine;
47+
import picocli.CommandLine.Command;
48+
import picocli.CommandLine.Option;
49+
50+
@SuppressWarnings("java:S101") // lowercase name to match jbang conventions
51+
// https://www.jbang.dev/documentation/jbang/latest/faq.html#:~:text=Why%20is%20JBang%20scripting%20examples%20using%20lower%20case%20class%20names%20%3F
52+
@Command(name = "webify", mixinStandardHelpOptions = true, version = "webify 0.1", description = "Converts Asciidoc standalone HTML output into content ready for kroxylicious.io")
53+
public class webify implements Callable<Integer> {
54+
55+
@Option(names = { "--project-version" }, required = true, description = "The kroxy version.")
56+
private String projectVersion;
57+
58+
@Option(names = { "--src-dir" }, required = true, description = "The source directory containing Asciidoc standalone HTML.")
59+
private Path srcDir;
60+
61+
@Option(names = { "--dest-dir" }, required = true, description = "The output directory ready for copying to the website.")
62+
private Path destdir;
63+
64+
@Option(names = { "--tocify-omit" }, description = "Glob matching file(s) to omit from the HTML output.")
65+
private List<String> omitGlobs = List.of();
66+
67+
@Option(names = { "--tocify" }, description = "Glob matching HTML files within --src-dir to tocify.")
68+
private String tocifyGlob;
69+
70+
@Option(names = { "--tocify-toc-file" }, description = "The name to give to output TOC files")
71+
private String tocifyTocName;
72+
73+
@Option(names = { "--tocify-tocless-file" }, description = "The name to give to output TOC-less files")
74+
private String tocifyToclessName;
75+
76+
@Option(names = { "--datafy" }, description = "Glob matching data yamls")
77+
private String datafyGlob;
78+
79+
private final Logger logger = LoggerFactory.getLogger(webify.class);
80+
81+
private Path outdir;
82+
private Path dataDestPath;
83+
84+
private final ObjectMapper mapper = new YAMLMapper()
85+
.disable(Feature.WRITE_DOC_START_MARKER)
86+
.enable(Feature.MINIMIZE_QUOTES)
87+
.enable(Feature.INDENT_ARRAYS_WITH_INDICATOR);
88+
89+
public static void main(String... args) {
90+
int exitCode = new CommandLine(new webify()).execute(args);
91+
System.exit(exitCode);
92+
}
93+
94+
@Override
95+
public Integer call() {
96+
this.outdir = this.destdir.resolve("documentation").resolve(this.projectVersion).resolve("html");
97+
this.dataDestPath = this.destdir.resolve("_data/documentation").resolve(this.projectVersion.replace(".", "_") + ".yaml");
98+
99+
try (FileSystem fs = FileSystems.getDefault()) {
100+
PathMatcher tocifyGlob = fs.getPathMatcher("glob:" + this.tocifyGlob);
101+
List<PathMatcher> omitGlobs = this.omitGlobs.stream().map(glob -> fs.getPathMatcher("glob:" + glob)).toList();
102+
PathMatcher datafyGlob = fs.getPathMatcher("glob:" + this.datafyGlob);
103+
104+
walk(omitGlobs, tocifyGlob, datafyGlob);
105+
106+
Path parentDir = outdir.getParent();
107+
// If condition to keep spotbugs happy
108+
if (parentDir != null && Files.exists(parentDir)) {
109+
Files.writeString(parentDir.resolve("index.md"),
110+
docIndexFrontMatter(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
111+
return 0;
112+
}
113+
else {
114+
logger.warn("could not find a parent directory for {}", outdir);
115+
return 1;
116+
}
117+
}
118+
catch (UnsupportedOperationException ex) {
119+
// Only certain filesystem implementations are actually closeable (waves at ZipFileSystem) the rest tell us to get stuffed.
120+
logger.debug(ex.getMessage(), ex);
121+
return 0;
122+
123+
}
124+
catch (Exception ex) {
125+
logger.error(ex.getMessage(), ex);
126+
return 2;
127+
}
128+
}
129+
130+
String docIndexFrontMatter() {
131+
return """
132+
---
133+
layout: released-documentation
134+
title: Documentation
135+
permalink: /documentation/${project.version}/
136+
---
137+
\s""".replace("${project.version}", this.projectVersion);
138+
}
139+
140+
private void walk(List<PathMatcher> omitGlobs,
141+
PathMatcher tocifyGlob,
142+
PathMatcher datafyGlob)
143+
throws IOException {
144+
var resultDocsList = new ArrayList<ObjectNode>();
145+
try (var stream = Files.walk(this.srcDir)) {
146+
stream.forEach(new DocConverter(omitGlobs, tocifyGlob, datafyGlob, resultDocsList));
147+
}
148+
149+
resultDocsList.sort(Comparator.nullsLast(Comparator.comparing(node -> node.get("rank").asText(null))));
150+
151+
var resultRootObject = this.mapper.createObjectNode();
152+
var resultDocsArray = resultRootObject.putArray("docs");
153+
resultDocsArray.addAll(resultDocsList);
154+
logger.info(mapper.writeValueAsString(resultRootObject));
155+
Files.createDirectories(Objects.requireNonNull(dataDestPath.getParent()));
156+
Files.writeString(dataDestPath, mapper.writeValueAsString(resultRootObject), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
157+
}
158+
159+
String guideFrontMatter(ObjectNode dataDocObject, String relPath) throws IOException {
160+
return "---\n" + this.mapper.writeValueAsString(this.mapper.createObjectNode()
161+
.put("layout", "guide")
162+
.<ObjectNode> setAll(dataDocObject)
163+
.put("version", this.projectVersion)
164+
.put("permalink", "/documentation/${project.version}/${relPath}/"
165+
.replace("${project.version}", this.projectVersion)
166+
.replace("${relPath}", relPath)))
167+
+ "---\n";
168+
}
169+
170+
ObjectNode readMetadata(Path filePath)
171+
throws IOException {
172+
var dataDocObject = (ObjectNode) this.mapper.readTree(filePath.toFile());
173+
return buildDocNode(dataDocObject);
174+
}
175+
176+
private ObjectNode buildDocNode(ObjectNode dataDocObject) {
177+
var resultDocObject = this.mapper.createObjectNode();
178+
Set<Map.Entry<String, JsonNode>> dataDoc = dataDocObject.properties();
179+
dataDoc.forEach(dataDocEntry -> {
180+
if (!"$schema".equals(dataDocEntry.getKey())) {
181+
resultDocObject.set(dataDocEntry.getKey(), dataDocEntry.getValue());
182+
}
183+
});
184+
return resultDocObject;
185+
}
186+
187+
void tocify(Path filePath,
188+
Path outFilePath)
189+
throws IOException {
190+
var outDir = Objects.requireNonNull(outFilePath.getParent());
191+
Files.createDirectories(outDir);
192+
if (!splitOutToc(filePath,
193+
outDir.resolve(this.tocifyTocName),
194+
outDir.resolve(this.tocifyToclessName))) {
195+
logger.info("copying {} to {}", filePath, outFilePath);
196+
Files.copy(filePath, outFilePath, StandardCopyOption.REPLACE_EXISTING);
197+
}
198+
}
199+
200+
static boolean splitOutToc(Path sourcePath,
201+
Path tocPath,
202+
Path toclessPath)
203+
throws IOException {
204+
// Parse the SOURCE
205+
Document doc = Jsoup.parse(sourcePath, "UTF-8");
206+
// Find the toc by its ID
207+
var toc = doc.getElementById("toc");
208+
if (toc == null) {
209+
return false;
210+
}
211+
// Remove the toc from the doc
212+
toc.remove();
213+
// Drop the "Table of Content" title
214+
Optional.ofNullable(toc.getElementById("toctitle")).ifPresent(Node::remove);
215+
216+
// Write the two nodes
217+
writeRaw(toc, tocPath);
218+
writeRaw(doc, toclessPath);
219+
return true;
220+
}
221+
222+
static void writeRaw(Node node,
223+
Path path)
224+
throws IOException {
225+
// Jekyll/Liquid doesn't have a way of {% include ... %} which
226+
// *prevents* the included file processing as a liquid template
227+
// Bracketing with {% raw %}/{% endraw %} is about the best we can do
228+
// to prevent evaluation, but an {% endraw %} in the HMTL would be
229+
// enough to break it ☹
230+
try (var writer = Files.newBufferedWriter(path)) {
231+
writer.append("{% raw %}\n");
232+
writer.append(node.toString());
233+
writer.append("\n{% endraw %}\n");
234+
writer.flush();
235+
}
236+
}
237+
238+
private class DocConverter implements Consumer<Path> {
239+
private final List<PathMatcher> omitGlobs;
240+
private final PathMatcher tocifyGlob;
241+
private final PathMatcher datafyGlob;
242+
private final List<ObjectNode> resultDocsList;
243+
244+
DocConverter(List<PathMatcher> omitGlobs, PathMatcher tocifyGlob, PathMatcher datafyGlob, List<ObjectNode> resultDocsList) {
245+
this.omitGlobs = omitGlobs;
246+
this.tocifyGlob = tocifyGlob;
247+
this.datafyGlob = datafyGlob;
248+
this.resultDocsList = resultDocsList;
249+
}
250+
251+
@Override
252+
public void accept(Path filePath) {
253+
try {
254+
if (!Files.isRegularFile(filePath)) {
255+
return;
256+
}
257+
var relFilePath = Objects.requireNonNull(webify.this.srcDir.relativize(filePath));
258+
var outFilePath = Objects.requireNonNull(webify.this.outdir.resolve(relFilePath));
259+
var omitable = omitGlobs.stream().anyMatch(glob -> glob.matches(relFilePath));
260+
var tocifiable = tocifyGlob.matches(relFilePath);
261+
var datafiable = datafyGlob.matches(relFilePath);
262+
if (omitable && !tocifiable && !datafiable) {
263+
// exit early when there is nothing to be done.
264+
// noinspection UnnecessaryReturnStatement
265+
return;
266+
}
267+
else if (!omitable && tocifiable && !datafiable) {
268+
webify.this.tocify(filePath, outFilePath);
269+
}
270+
else {
271+
Path outputDirectory = Objects.requireNonNull(outFilePath.getParent());
272+
if (!omitable && !tocifiable && datafiable) {
273+
var dataDocObject = webify.this.readMetadata(filePath);
274+
String relPath;
275+
if (!dataDocObject.has("path")) {
276+
Path relFilePathParent = Objects.requireNonNull(relFilePath.getParent());
277+
relPath = "html/" + relFilePathParent;
278+
Files.createDirectories(outputDirectory);
279+
Files.writeString(outputDirectory.resolve("index.html"),
280+
webify.this.guideFrontMatter(dataDocObject, "html/" + relFilePathParent),
281+
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
282+
}
283+
else {
284+
relPath = dataDocObject.get("path").textValue().replace("${project.version}", webify.this.projectVersion);
285+
}
286+
dataDocObject.put("path", relPath);
287+
resultDocsList.add(dataDocObject);
288+
}
289+
else if (!omitable && !tocifiable) {
290+
Files.createDirectories(outputDirectory);
291+
Files.copy(filePath, outFilePath, StandardCopyOption.REPLACE_EXISTING);
292+
}
293+
else {
294+
throw new IOException((filePath + " matched multiple globs: "
295+
+ (omitable ? "--tocify-omit " : "")
296+
+ (tocifiable ? "--tocify " : "")
297+
+ (datafiable ? "--datafy " : "")).trim());
298+
}
299+
}
300+
}
301+
catch (Exception e) {
302+
throw new RuntimeException(filePath.toString(), e);
303+
}
304+
}
305+
}
306+
}

0 commit comments

Comments
 (0)