io.quarkus.docs.generation.QuarkusBuildItemDoc Maven / Gradle / Ivy
package io.quarkus.docs.generation;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import org.eclipse.collections.api.multimap.Multimap;
import org.eclipse.collections.api.multimap.MutableMultimap;
import org.eclipse.collections.api.tuple.Pair;
import org.eclipse.collections.impl.factory.Multimaps;
import org.eclipse.collections.impl.tuple.Tuples;
import org.jboss.forge.roaster.Roaster;
import org.jboss.forge.roaster.model.JavaDocCapable;
import org.jboss.forge.roaster.model.source.FieldSource;
import org.jboss.forge.roaster.model.source.JavaClassSource;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import io.fabric8.maven.Maven;
public class QuarkusBuildItemDoc {
public Path outputFile;
public List paths;
private PrintStream out = System.out;
// target/asciidoc/generated/config/quarkus-all-build-items.adoc core/deployment core/test-extension extensions
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.err.println("Must specify output file (first) followed by at least one source directory");
System.exit(1);
}
QuarkusBuildItemDoc buildItemDoc = new QuarkusBuildItemDoc();
buildItemDoc.outputFile = Path.of(args[0]);
buildItemDoc.paths = Arrays.stream(args).skip(1)
.map(Path::of)
.collect(Collectors.toList());
try {
buildItemDoc.run();
} catch (Exception e) {
System.err.println("Exception occurred while trying to collect build item documentation");
e.printStackTrace();
System.exit(1);
}
}
public void run() throws Exception {
if (outputFile != null) {
Files.createDirectories(outputFile.getParent());
out = new PrintStream(Files.newOutputStream(outputFile));
}
final Multimap> multimap = collect();
Map names = extractNames(Paths.get("."), multimap.keySet());
// Print Core first
{
printTableHeader(names.remove("Core"));
for (Pair source : multimap.get("Core")) {
printTableRow(source);
}
printTableFooter();
}
names.forEach((key, name) -> {
printTableHeader(name);
for (Pair source : multimap.get(key)) {
printTableRow(source);
}
printTableFooter();
});
}
private String getJavaDoc(JavaDocCapable> source) {
if (!source.hasJavaDoc()) {
return "No Javadoc found";
}
return source.getJavaDoc().getFullText();
}
private Multimap> collect() throws IOException {
MutableMultimap> multimap = Multimaps.mutable.sortedSet
.with(Comparator.comparing(o -> o.getTwo().getName()));
for (Path path : paths) {
Files.walkFileTree(path, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.toString().endsWith("BuildItem.java")) {
process(multimap, file);
}
return FileVisitResult.CONTINUE;
}
});
}
return multimap;
}
private void process(MutableMultimap> multimap, Path path) throws IOException {
JavaClassSource source = Roaster.parse(JavaClassSource.class, path.toFile());
// Ignore deprecated annotations and non-public classes
if (!source.hasAnnotation(Deprecated.class) && source.isPublic()) {
String name;
Path pom = findPom(path);
if (pom != null) {
name = Maven.readModel(pom).getName();
} else {
String pathString = path.toString();
int spiIdx = pathString.indexOf("/spi/src");
int runtimeIdx = pathString.indexOf("/runtime/src");
int deploymentIdx = pathString.indexOf("/deployment/src");
int idx = Math.max(Math.max(spiIdx, runtimeIdx), deploymentIdx);
int extensionsIdx = pathString.indexOf("extensions/");
int startIdx = 0;
if (extensionsIdx != -1) {
startIdx = extensionsIdx + 11;
}
if (idx == -1) {
name = pathString.substring(startIdx, pathString.indexOf("/", startIdx + 1));
} else {
name = pathString.substring(startIdx, idx);
}
}
// sanitize name
name = name.replace("Quarkus - ", "")
.replace(" - Deployment", "");
Pair pair = Tuples.pair(path, source);
multimap.put(name, pair);
}
}
private Path findPom(Path path) {
Path pom = null;
Path parent = path;
while (pom == null && (parent = parent.getParent()) != null) {
Path resolve = parent.resolve("pom.xml");
if (Files.exists(resolve)) {
pom = resolve;
}
}
return pom;
}
private Map extractNames(Path root, Iterable extensionDirs) throws IOException {
Map names = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
for (String extension : extensionDirs) {
Path yamlPath = root
.resolve("extensions/" + extension + "/runtime/src/main/resources/META-INF/quarkus-extension.yaml");
if (Files.exists(yamlPath)) {
try (InputStream is = Files.newInputStream(yamlPath)) {
Map map = yaml.load(is);
names.put(extension, map.get("name"));
}
} else {
names.put(extension, extension);
}
}
return names;
}
private void printTableHeader(String title) {
out.println("== " + title);
out.println("[.configuration-reference,cols=2*]");
out.println("|===");
out.println("h|Class Name\nh|Attributes \n\n");
}
private void printTableRow(Pair pair) {
//TODO: Use tagged version?
Path root = Paths.get(".").toAbsolutePath().normalize();
String link = "https://github.com/quarkusio/quarkus/blob/main/" + root.relativize(pair.getOne().normalize());
JavaClassSource source = pair.getTwo();
String className = source.getQualifiedName();
String attributes = buildAttributes(source);
String description = getJavaDoc(source);
String baseBuildItemText = source.isAbstract()
? "icon:building[title=Non-instantiatable Build Item (can be inherited from)]"
: "";
String linkToClass = String.format("%s[`%s`, window=\"_blank\"]", link, className);
out.println(String.format("\n\na|%s %s\n[.description]\n--\n%s\n-- a|%s",
baseBuildItemText,
linkToClass,
javadocToAsciidoc(description),
attributes));
}
private String buildAttributes(JavaClassSource source) {
StringBuilder sb = new StringBuilder();
for (FieldSource field : source.getFields()) {
if (field.isStatic()) {
continue;
}
sb.append(String.format("`%s %s` \n\n%s\n\n",
field.getType().getQualifiedNameWithGenerics(),
field.getName(),
javadocToAsciidoc(getJavaDoc(field))));
}
return sb.length() == 0 ? "None" : sb.toString();
}
private void printTableFooter() {
out.println("|===");
}
private String javadocToAsciidoc(String content) {
return content
.replaceAll(" *", "\n")
.replaceAll("
*", "\n")
.replaceAll("
*", "\n")
.replaceAll("\\{?@(link|see|code) ([^}]*)}", "`$2`")
.replaceAll("", "```\n")
.replaceAll("
", "\n```")
.replaceAll("", "\n[discrete]\n== ")
.replaceAll("
*", "\n\n")
.replaceAll("?i>", "_")
.replaceAll("?ul> *", "\n")
.replaceAll("", "\n* ")
.replaceAll(" *", "\n\n")
.replaceAll("?tt>", "`")
.replaceAll("([^<]*)", "$1[$2,window=_blank]");
}
}