Skip to content

Commit

Permalink
Add a tool to check cross references
Browse files Browse the repository at this point in the history
Unfortunately, it doesn't support additional ids added just after the
title as in:
[id1]
== [id2] [id3] Title

so it will force people to use the canonical id but I think it's
acceptable in our reference doc to enforce the canonical id.

Also move the link transformation to downstream documentation only as
this alters the ability of Asciidoctor to check the internal links.
  • Loading branch information
gsmet committed Jan 29, 2024
1 parent d2e458d commit 101e391
Show file tree
Hide file tree
Showing 4 changed files with 404 additions and 78 deletions.
21 changes: 21 additions & 0 deletions docs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3081,6 +3081,26 @@
</environmentVariables>
</configuration>
</execution>
<execution>
<id>check-cross-references</id>
<phase>prepare-package</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<skip>${skipDocs}</skip>
<!-- Don't wait for unkillable reference reaper threads -->
<cleanupDaemonThreads>false</cleanupDaemonThreads>
<mainClass>io.quarkus.docs.generation.CheckCrossReferences</mainClass>
<arguments>
<argument>${project.basedir}/target/asciidoc/sources</argument>
<argument>${project.basedir}/target/referenceIndex.yaml</argument>
</arguments>
<environmentVariables>
<MAVEN_CMD_LINE_ARGS>${env.MAVEN_CMD_LINE_ARGS}</MAVEN_CMD_LINE_ARGS>
</environmentVariables>
</configuration>
</execution>
<execution>
<id>assemble-downstream-doc</id>
<phase>prepare-package</phase>
Expand All @@ -3096,6 +3116,7 @@
<argument>-classpath</argument>
<classpath />
<argument>io.quarkus.docs.generation.AssembleDownstreamDocumentation</argument>
<argument>${project.basedir}/target/referenceIndex.yaml</argument>
</arguments>
<workingDirectory>${project.basedir}</workingDirectory>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
Expand All @@ -27,6 +29,9 @@
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;

import io.quarkus.docs.generation.ReferenceIndexGenerator.Index;

public class AssembleDownstreamDocumentation {

Expand All @@ -46,7 +51,12 @@ public class AssembleDownstreamDocumentation {
DOC_PATH.resolve("_attributes-local.adoc"));

private static final String ADOC_SUFFIX = ".adoc";
private static final Pattern XREF_PATTERN = Pattern.compile("xref:([^\\.#\\[ ]+)\\" + ADOC_SUFFIX);
private static final Pattern XREF_GUIDE_PATTERN = Pattern.compile("xref:([^\\.#\\[ ]+)\\" + ADOC_SUFFIX);
private static final Pattern XREF_PATTERN = Pattern.compile("xref:([^\\[]+)\\[]");
private static final Pattern ANGLE_BRACKETS_WITHOUT_DESCRIPTION_PATTERN = Pattern.compile("<<([a-z0-9_\\-#\\.]+?)>>",
Pattern.CASE_INSENSITIVE);
private static final Pattern ANGLE_BRACKETS_WITH_DESCRIPTION_PATTERN = Pattern.compile("<<([a-z0-9_\\-#\\.]+?),([^>]+?)>>",
Pattern.CASE_INSENSITIVE);
private static final String SOURCE_BLOCK_PREFIX = "[source";
private static final String SOURCE_BLOCK_DELIMITER = "--";

Expand Down Expand Up @@ -86,6 +96,17 @@ public static void main(String[] args) throws Exception {
if (!Files.isDirectory(GENERATED_FILES_PATH)) {
throw new IllegalStateException("Generated files directory does not exist. Have you built the documentation?");
}
Path referenceIndexPath = Path.of(args[0]);
if (!Files.isReadable(Path.of(args[0]))) {
throw new IllegalStateException("Reference index does not exist? Have you built the documentation?");
}

ObjectMapper om = new ObjectMapper(new YAMLFactory().enable(YAMLGenerator.Feature.MINIMIZE_QUOTES));
Index referenceIndex = om.readValue(referenceIndexPath.toFile(), Index.class);

Map<String, List<String>> linkRewritingErrors = new LinkedHashMap<>();
Map<String, String> titlesByReference = referenceIndex.getReferences().stream()
.collect(Collectors.toMap(s -> s.getReference(), s -> s.getTitle()));

try {
deleteDirectory(TARGET_ROOT_DIRECTORY);
Expand Down Expand Up @@ -154,7 +175,8 @@ public static void main(String[] args) throws Exception {

for (Path guide : guides) {
System.out.println("[INFO] Processing guide " + guide.getFileName());
copyAsciidoc(guide, TARGET_ROOT_DIRECTORY.resolve(guide.getFileName()), downstreamGuides);
copyAsciidoc(guide, TARGET_ROOT_DIRECTORY.resolve(guide.getFileName()), downstreamGuides, titlesByReference,
linkRewritingErrors);
}
for (Path simpleInclude : simpleIncludes) {
Path sourceFile = DOC_PATH.resolve(simpleInclude);
Expand All @@ -168,7 +190,7 @@ public static void main(String[] args) throws Exception {
allResolvedPaths.add(sourceFile);
Path targetFile = TARGET_ROOT_DIRECTORY.resolve(simpleInclude);
Files.createDirectories(targetFile.getParent());
copyAsciidoc(sourceFile, targetFile, downstreamGuides);
copyAsciidoc(sourceFile, targetFile, downstreamGuides, titlesByReference, linkRewritingErrors);
}
for (Path include : includes) {
Path sourceFile = INCLUDES_PATH.resolve(include);
Expand All @@ -181,7 +203,7 @@ public static void main(String[] args) throws Exception {
allResolvedPaths.add(sourceFile);
Path targetFile = TARGET_INCLUDES_DIRECTORY.resolve(include);
Files.createDirectories(targetFile.getParent());
copyAsciidoc(sourceFile, targetFile, downstreamGuides);
copyAsciidoc(sourceFile, targetFile, downstreamGuides, titlesByReference, linkRewritingErrors);
}
for (Path generatedFile : generatedFiles) {
Path sourceFile = GENERATED_FILES_PATH.resolve(generatedFile);
Expand All @@ -194,7 +216,7 @@ public static void main(String[] args) throws Exception {
allResolvedPaths.add(sourceFile);
Path targetFile = TARGET_GENERATED_DIRECTORY.resolve(generatedFile);
Files.createDirectories(targetFile.getParent());
copyAsciidoc(sourceFile, targetFile, downstreamGuides);
copyAsciidoc(sourceFile, targetFile, downstreamGuides, titlesByReference, linkRewritingErrors);
}
for (Path image : images) {
Path sourceFile = IMAGES_PATH.resolve(image);
Expand All @@ -213,6 +235,24 @@ public static void main(String[] args) throws Exception {
Files.writeString(TARGET_LISTING,
allResolvedPaths.stream().map(p -> p.toString()).collect(Collectors.joining("\n")));

if (!linkRewritingErrors.isEmpty()) {
System.out.println();
System.out.println("################################################");
System.out.println("# Errors occurred while transforming references");
System.out.println("################################################");
System.out.println();

for (Entry<String, List<String>> errorEntry : linkRewritingErrors.entrySet()) {
System.out.println("- " + errorEntry.getKey());
for (String error : errorEntry.getValue()) {
System.out.println(" . " + error);
}
}

System.out.println();
System.exit(1);
}

LOG.info("Downstream documentation tree is available in: " + TARGET_ROOT_DIRECTORY);
LOG.info("Downstream documentation listing is available in: " + TARGET_LISTING);
} catch (IOException e) {
Expand Down Expand Up @@ -292,7 +332,9 @@ private static void deleteDirectory(Path directory) throws IOException {
.forEach(File::delete);
}

private static void copyAsciidoc(Path sourceFile, Path targetFile, Set<String> downstreamGuides) throws IOException {
private static void copyAsciidoc(Path sourceFile, Path targetFile, Set<String> downstreamGuides,
Map<String, String> titlesByReference,
Map<String, List<String>> linkRewritingErrors) throws IOException {
List<String> guideLines = Files.readAllLines(sourceFile);

StringBuilder rewrittenGuide = new StringBuilder();
Expand Down Expand Up @@ -332,7 +374,8 @@ private static void copyAsciidoc(Path sourceFile, Path targetFile, Set<String> d

if (currentBuffer.length() > 0) {
rewrittenGuide.append(
rewriteLinks(currentBuffer.toString(), downstreamGuides));
rewriteLinks(sourceFile.getFileName().toString(), currentBuffer.toString(), downstreamGuides,
titlesByReference, linkRewritingErrors));
currentBuffer.setLength(0);
}
rewrittenGuide.append(line + "\n");
Expand All @@ -343,8 +386,9 @@ private static void copyAsciidoc(Path sourceFile, Path targetFile, Set<String> d
}

if (currentBuffer.length() > 0) {
rewrittenGuide
.append(rewriteLinks(currentBuffer.toString(), downstreamGuides));
rewrittenGuide.append(
rewriteLinks(sourceFile.getFileName().toString(), currentBuffer.toString(), downstreamGuides,
titlesByReference, linkRewritingErrors));
}

String rewrittenGuideWithoutTabs = rewrittenGuide.toString().trim();
Expand All @@ -357,8 +401,36 @@ private static void copyAsciidoc(Path sourceFile, Path targetFile, Set<String> d
Files.writeString(targetFile, rewrittenGuideWithoutTabs.trim());
}

private static String rewriteLinks(String content, Set<String> downstreamGuides) {
private static String rewriteLinks(String fileName,
String content,
Set<String> downstreamGuides,
Map<String, String> titlesByReference,
Map<String, List<String>> errors) {
content = XREF_PATTERN.matcher(content).replaceAll(mr -> {
String reference = getQualifiedReference(fileName, mr.group(1));
String title = titlesByReference.get(reference);
if (title == null || title.isBlank()) {
addError(errors, fileName, "Unable to find title for: " + mr.group() + " [" + reference + "]");
title = "~~ unknown title ~~";
}
return "xref:" + trimReference(mr.group(1)) + "[" + title.trim() + "]";
});

content = ANGLE_BRACKETS_WITHOUT_DESCRIPTION_PATTERN.matcher(content).replaceAll(mr -> {
String reference = getQualifiedReference(fileName, mr.group(1));
String title = titlesByReference.get(reference);
if (title == null || title.isBlank()) {
addError(errors, fileName, "Unable to find title for: " + mr.group() + " [" + reference + "]");
title = "~~ unknown title ~~";
}
return "xref:" + trimReference(mr.group(1)) + "[" + title.trim() + "]";
});

content = ANGLE_BRACKETS_WITH_DESCRIPTION_PATTERN.matcher(content).replaceAll(mr -> {
return "xref:" + trimReference(mr.group(1)) + "[" + mr.group(2).trim() + "]";
});

content = XREF_GUIDE_PATTERN.matcher(content).replaceAll(mr -> {
if (downstreamGuides.contains(mr.group(1) + ADOC_SUFFIX)) {
return mr.group(0);
}
Expand All @@ -369,6 +441,57 @@ private static String rewriteLinks(String content, Set<String> downstreamGuides)
return content;
}

private static String trimReference(String reference) {
reference = normalizeAdoc(reference);

if (reference.startsWith("#")) {
return reference.substring(1);
}

if (reference.contains(".adoc")) {
return reference;
}

if (reference.contains("#")) {
int hashIndex = reference.indexOf('#');
return reference.substring(0, hashIndex) + ".adoc" + reference.substring(hashIndex);
}

return reference;
}

private static String getQualifiedReference(String fileName, String reference) {
reference = normalizeAdoc(reference);

if (reference.startsWith("#")) {
return fileName + reference;
}

if (reference.contains(".adoc")) {
return reference;
}

if (reference.contains("#")) {
int hashIndex = reference.indexOf('#');
return reference.substring(0, hashIndex) + ".adoc" + reference.substring(hashIndex);
}

return fileName + "#" + reference;
}

private static String normalizeAdoc(String adoc) {
if (adoc.startsWith("./")) {
return adoc.substring(2);
}

return adoc;
}

private static void addError(Map<String, List<String>> errors, String fileName, String error) {
errors.computeIfAbsent(fileName, f -> new ArrayList<>())
.add(error);
}

public static class GuideContent {

public Path guide;
Expand Down
Loading

0 comments on commit 101e391

Please sign in to comment.