You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by pp...@apache.org on 2020/09/24 15:51:00 UTC

[camel-quarkus] 01/02: Generate partials instead of pages for the individual Camel bits #1795

This is an automated email from the ASF dual-hosted git repository.

ppalaga pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git

commit 38625a9f2ff362dbe2e5304e281ecaf3ac6b6316
Author: Peter Palaga <pp...@redhat.com>
AuthorDate: Wed Sep 23 13:34:31 2020 +0200

    Generate partials instead of pages for the individual Camel bits #1795
---
 catalog/pom.xml                                    |   1 +
 docs/README.adoc                                   |  14 ++
 docs/antora-playbook-dev.yml                       |  47 +++++
 docs/modules/ROOT/pages/reference/components.adoc  |  12 +-
 docs/modules/ROOT/pages/reference/dataformats.adoc |  12 +-
 docs/modules/ROOT/pages/reference/languages.adoc   |  12 +-
 docs/modules/ROOT/pages/reference/others.adoc      |   6 +
 docs/package.json                                  |   2 +-
 .../quarkus/maven/AbstractDocGeneratorMojo.java    |   2 +-
 .../quarkus/maven/CheckExtensionPagesMojo.java     | 196 +++++++++++++++++++++
 .../org/apache/camel/quarkus/maven/CqCatalog.java  | 120 ++++++++++++-
 .../org/apache/camel/quarkus/maven/CqUtils.java    |  50 ++++++
 .../quarkus/maven/UpdateExtensionDocPageMojo.java  |  36 +++-
 .../doc-templates/extension-doc-page.adoc          |   2 +-
 .../doc-templates/extensions-camel-bits.adoc       |   1 -
 15 files changed, 494 insertions(+), 19 deletions(-)

diff --git a/catalog/pom.xml b/catalog/pom.xml
index ad96517..95f2f1d 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -4127,6 +4127,7 @@
                         <!-- prepare the catalog and update doc files, etc. -->
                         <goals>
                             <goal>prepare-catalog-quarkus</goal>
+                            <goal>check-extension-pages</goal>
                         </goals>
                         <phase>process-resources</phase>
                         <configuration>
diff --git a/docs/README.adoc b/docs/README.adoc
index fd3b823..7d227a3 100644
--- a/docs/README.adoc
+++ b/docs/README.adoc
@@ -1,5 +1,19 @@
 = Apache Camel extensions for Quarkus Documentation
 
+== Antora playbooks
+
+There are two Antora playbooks available in the project: link:antora-playbook.yml[antora-playbook.yml] and
+link:antora-playbook-dev.yml[antora-playbook-dev.yml].
+
+link:antora-playbook.yml[antora-playbook.yml] is optimized for fast `xref` verification on the CI - it is used under
+the hood by `exec-maven-plugin` when `mvn verify` is invoked. However, the site it produces is rather
+incomplete.
+
+link:antora-playbook-dev.yml[antora-playbook-dev.yml] aims at building a more complete site. It pulls Camel and Camel
+Spring Boot Antora docs so that the lists of Camel components (available under Camel Quarkus reference docs) are built
+too.
+
+
 == Environment Setup
 
 To setup the environment you need to execute the following command once (and every time you change yarn dependencies):
diff --git a/docs/antora-playbook-dev.yml b/docs/antora-playbook-dev.yml
new file mode 100644
index 0000000..fd2e4f4
--- /dev/null
+++ b/docs/antora-playbook-dev.yml
@@ -0,0 +1,47 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+site:
+  title: Apache Camel extensions for Quarkus
+  url: https://camel.apache.org
+  start_page: camel-quarkus::index.adoc
+content:
+  sources:
+  - url: ./../
+    branches: HEAD
+    start_path: docs
+  - url: ./../../../camel/camel
+    branches: HEAD
+    start_path: docs/components
+  - url: git@github.com:apache/camel-spring-boot.git
+    branches: master
+    start_path: docs
+
+asciidoc:
+  extensions:
+    - "@djencks/asciidoctor-antora-indexer"
+  attributes:
+    eip-vc: latest@components
+
+ui:
+  bundle:
+    url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/master/raw/build/ui-bundle.zip?job=bundle-stable
+    snapshot: true
+output:
+  dir: ./target/site
+urls:
+  redirect_facility: httpd
diff --git a/docs/modules/ROOT/pages/reference/components.adoc b/docs/modules/ROOT/pages/reference/components.adoc
index c2cebf9..7242aab 100644
--- a/docs/modules/ROOT/pages/reference/components.adoc
+++ b/docs/modules/ROOT/pages/reference/components.adoc
@@ -1,13 +1,19 @@
 = Camel components supported on Quarkus
+:indexer-version: latest
+:indexer-component: components
+:indexer-module: ROOT
+:indexer-rel-filter: *-component.adoc
+:indexer-human-readable-kind: Component
+:indexer-human-readable-kind-plural: components
 
-There are indexCount:[relative=reference/components/\*.adoc] components (indexCount:[relative=reference/components/\*.adoc,attributes='cq-deprecated=true'] deprecated, indexCount:[relative=reference/components/\*.adoc,attributes='cq-native-supported=false'] JVM only)
+There are indexCount:[version="{indexer-version}",component="{indexer-component}",module="{indexer-module}",relative="{indexer-rel-filter}",attributes=cq-artifact-id] {indexer-human-readable-kind-plural} (indexCount:[version="{indexer-version}",component="{indexer-component}",module="{indexer-module}",relative="{indexer-rel-filter}",attributes='cq-artifact-id,cq-deprecated=true'] deprecated, indexCount:[version="{indexer-version}",component="{indexer-component}",module="{indexer-module}" [...]
 
 [.counted-table,width="100%",cols="4,1,1,1,1,5",options="header"]
 |===
-| Component | Artifact | JVM +
+| {indexer-human-readable-kind} | Artifact | JVM +
 since | Native +
 since | Support +
 level | Description
 |===
 
-indexTable::[relative='reference/components/*.adoc',cells="=`xref:reference/extensions/$\{cqArtifactIdBase}.adoc[$\{cqCamelPartTitle}]`,cq-artifact-id,cq-jvm-since,cq-native-since,cq-status,cq-camel-part-description"]
+indexTable::[version="{indexer-version}",component="{indexer-component}",module="{indexer-module}",relative="{indexer-rel-filter}",attributes=cq-artifact-id,cells="=`xref:reference/extensions/$\{cqArtifactIdBase}.adoc[$\{cqCamelPartTitle}]`,cq-artifact-id,cq-jvm-since,cq-native-since,cq-status,cq-camel-part-description"]
diff --git a/docs/modules/ROOT/pages/reference/dataformats.adoc b/docs/modules/ROOT/pages/reference/dataformats.adoc
index 27db993..c47e163 100644
--- a/docs/modules/ROOT/pages/reference/dataformats.adoc
+++ b/docs/modules/ROOT/pages/reference/dataformats.adoc
@@ -1,13 +1,19 @@
 = Camel data formats supported on Quarkus
+:indexer-version: latest
+:indexer-component: components
+:indexer-module: dataformats
+:indexer-rel-filter: *-dataformat.adoc
+:indexer-human-readable-kind: Data format
+:indexer-human-readable-kind-plural: data formats
 
-There are indexCount:[relative=reference/dataformats/\*.adoc] data formats (indexCount:[relative=reference/dataformats/\*.adoc,attributes='cq-deprecated=true'] deprecated, indexCount:[relative=reference/dataformats/\*.adoc,attributes='cq-native-supported=false'] JVM only)
+There are indexCount:[version="{indexer-version}",component="{indexer-component}",module="{indexer-module}",relative="{indexer-rel-filter}",attributes=cq-artifact-id] {indexer-human-readable-kind-plural} (indexCount:[version="{indexer-version}",component="{indexer-component}",module="{indexer-module}",relative="{indexer-rel-filter}",attributes='cq-artifact-id,cq-deprecated=true'] deprecated, indexCount:[version="{indexer-version}",component="{indexer-component}",module="{indexer-module}" [...]
 
 [.counted-table,width="100%",cols="4,1,1,1,1,5",options="header"]
 |===
-| Data format | Artifact | JVM +
+| {indexer-human-readable-kind} | Artifact | JVM +
 since | Native +
 since | Support +
 level | Description
 |===
 
-indexTable::[relative='reference/dataformats/*.adoc',cells="=`xref:reference/extensions/$\{cqArtifactIdBase}.adoc[$\{cqCamelPartTitle}]`,cq-artifact-id,cq-jvm-since,cq-native-since,cq-status,cq-camel-part-description"]
+indexTable::[version="{indexer-version}",component="{indexer-component}",module="{indexer-module}",relative="{indexer-rel-filter}",attributes=cq-artifact-id,cells="=`xref:reference/extensions/$\{cqArtifactIdBase}.adoc[$\{cqCamelPartTitle}]`,cq-artifact-id,cq-jvm-since,cq-native-since,cq-status,cq-camel-part-description"]
diff --git a/docs/modules/ROOT/pages/reference/languages.adoc b/docs/modules/ROOT/pages/reference/languages.adoc
index 89dd794..82eface 100644
--- a/docs/modules/ROOT/pages/reference/languages.adoc
+++ b/docs/modules/ROOT/pages/reference/languages.adoc
@@ -1,13 +1,19 @@
 = Camel languages supported on Quarkus
+:indexer-version: latest
+:indexer-component: components
+:indexer-module: languages
+:indexer-rel-filter: *-language.adoc
+:indexer-human-readable-kind: Language
+:indexer-human-readable-kind-plural: languages
 
-There are indexCount:[relative=reference/languages/\*.adoc] languages (indexCount:[relative=reference/languages/\*.adoc,attributes='cq-deprecated=true'] deprecated, indexCount:[relative=reference/languages/\*.adoc,attributes='cq-native-supported=false'] JVM only)
+There are indexCount:[version="{indexer-version}",component="{indexer-component}",module="{indexer-module}",relative="{indexer-rel-filter}",attributes=cq-artifact-id] {indexer-human-readable-kind-plural} (indexCount:[version="{indexer-version}",component="{indexer-component}",module="{indexer-module}",relative="{indexer-rel-filter}",attributes='cq-artifact-id,cq-deprecated=true'] deprecated, indexCount:[version="{indexer-version}",component="{indexer-component}",module="{indexer-module}" [...]
 
 [.counted-table,width="100%",cols="4,1,1,1,1,5",options="header"]
 |===
-| Language | Artifact | JVM +
+| {indexer-human-readable-kind} | Artifact | JVM +
 since | Native +
 since | Support +
 level | Description
 |===
 
-indexTable::[relative='reference/languages/*.adoc',cells="=`xref:reference/extensions/$\{cqArtifactIdBase}.adoc[$\{cqCamelPartTitle}]`,cq-artifact-id,cq-jvm-since,cq-native-since,cq-status,cq-camel-part-description"]
+indexTable::[version="{indexer-version}",component="{indexer-component}",module="{indexer-module}",relative="{indexer-rel-filter}",attributes=cq-artifact-id,cells="=`xref:reference/extensions/$\{cqArtifactIdBase}.adoc[$\{cqCamelPartTitle}]`,cq-artifact-id,cq-jvm-since,cq-native-since,cq-status,cq-camel-part-description"]
diff --git a/docs/modules/ROOT/pages/reference/others.adoc b/docs/modules/ROOT/pages/reference/others.adoc
index 60208fe..89a259a 100644
--- a/docs/modules/ROOT/pages/reference/others.adoc
+++ b/docs/modules/ROOT/pages/reference/others.adoc
@@ -1,4 +1,10 @@
 = Camel misc. components supported on Quarkus
+:indexer-version: latest
+:indexer-component: components
+:indexer-module: others
+:indexer-rel-filter: *.adoc
+:indexer-human-readable-kind: Misc. component
+:indexer-human-readable-kind-plural: misc. components
 
 There are indexCount:[relative=reference/others/\*.adoc] misc. components (indexCount:[relative=reference/others/\*.adoc,attributes='cq-deprecated=true'] deprecated, indexCount:[relative=reference/others/\*.adoc,attributes='cq-native-supported=false'] JVM only)
 
diff --git a/docs/package.json b/docs/package.json
index a11c4d7..133662a 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -17,7 +17,7 @@
   },
   "scripts": {
     "preview": "cd target/site && lite-server",
-    "build": "node_modules/.bin/antora antora-playbook.yml --stacktrace",
+    "build": "node_modules/.bin/antora antora-playbook-dev.yml --stacktrace",
     "dev": "yarn build && yarn preview",
     "checks": "yarn antora --generator @antora/xref-validator antora-playbook.yml"
   }
diff --git a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/AbstractDocGeneratorMojo.java b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/AbstractDocGeneratorMojo.java
index 2a92aa8..7407509 100644
--- a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/AbstractDocGeneratorMojo.java
+++ b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/AbstractDocGeneratorMojo.java
@@ -28,7 +28,7 @@ import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugins.annotations.Parameter;
 
 /**
- * Base for {@link UpdateDocExtensionsListMojo} and {@link UpdateExtensionDocPageMojo}.
+ * Base for {@link CheckExtensionPagesMojo} and {@link UpdateExtensionDocPageMojo}.
  */
 abstract class AbstractDocGeneratorMojo extends AbstractMojo {
     public static final String DEFAULT_TEMPLATES_URI_BASE = "classpath:/doc-templates";
diff --git a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CheckExtensionPagesMojo.java b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CheckExtensionPagesMojo.java
new file mode 100644
index 0000000..dd75fa7
--- /dev/null
+++ b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CheckExtensionPagesMojo.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.maven;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.camel.quarkus.maven.CqCatalog.Flavor;
+import org.apache.camel.quarkus.maven.CqCatalog.GavCqCatalog;
+import org.apache.camel.tooling.model.ArtifactModel;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+
+/**
+ * Performs the following tasks:
+ * <ul>
+ * <li>Deletes extension pages whose extensions do not exist anymore
+ * <li>Creates dummy partials for Camel bits that Camel Quarkus does not support, so that there are no warnings when
+ * they are included from the Camel component pages
+ * <li>Deletes Camel bit partials that do not exist anymore.
+ * <ul>
+ */
+@Mojo(name = "check-extension-pages", threadSafe = true)
+public class CheckExtensionPagesMojo extends AbstractDocGeneratorMojo {
+
+    private static final Pattern ADOC_ENDING_PATTERN = Pattern.compile("\\.adoc$");
+    private static final byte[] DUMMY_COMPONENT_FILE_COMMENT = "// Empty partial for a Camel bit unsupported by Camel Quarkus to avoid warnings when this file is included from a Camel page\n"
+            .getBytes(StandardCharsets.UTF_8);
+
+    /**
+     * The directory relative to which the catalog data is read.
+     */
+    @Parameter(defaultValue = "${project.build.directory}/classes", property = "camel-quarkus.catalogBaseDir")
+    File catalogBaseDir;
+
+    /**
+     * The path to the docs module base directory
+     */
+    @Parameter(defaultValue = "${maven.multiModuleProjectDirectory}/docs")
+    File docsBaseDir;
+
+    /**
+     * List of directories that contain extensions
+     */
+    @Parameter(property = "cq.extensionDirectories", required = true)
+    List<File> extensionDirectories;
+
+    /**
+     * A set of artifactIdBases that are not extensions and should be excluded from the catalog
+     */
+    @Parameter(property = "cq.skipArtifactIdBases")
+    Set<String> skipArtifactIdBases;
+
+    /**
+     * The version of Camel we depend on
+     */
+    @Parameter(property = "camel.version")
+    String camelVersion;
+
+    @Parameter(defaultValue = "${settings.localRepository}", readonly = true)
+    String localRepository;
+
+    /**
+     * Execute goal.
+     *
+     * @throws MojoExecutionException execution of the main class or one of the
+     *                                threads it generated failed.
+     * @throws MojoFailureException   something bad happened...
+     */
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        final Path docsBasePath = docsBaseDir.toPath();
+        if (skipArtifactIdBases == null) {
+            skipArtifactIdBases = Collections.emptySet();
+        }
+
+        camelBits(docsBasePath);
+        extensions(docsBasePath);
+    }
+
+    void camelBits(Path docsBasePath) {
+        final CqCatalog cqCatalog = new CqCatalog(catalogBaseDir.toPath(), Flavor.camelQuarkus);
+
+        final Path referenceDir = docsBasePath.resolve("modules/ROOT/partials/reference");
+        try (GavCqCatalog camelCatalog = GavCqCatalog.open(Paths.get(localRepository), Flavor.camel, camelVersion)) {
+
+            CqCatalog.kinds().forEach(kind -> {
+                final Set<String> cqNames = cqCatalog.models(kind)
+                        .filter(CqCatalog::isFirstScheme)
+                        .map(CqCatalog::toCamelDocsModel)
+                        .map(ArtifactModel::getName)
+                        .collect(Collectors.toSet());
+                final Set<String> camelNames = camelCatalog.models(kind)
+                        .filter(CqCatalog::isFirstScheme)
+                        .map(CqCatalog::toCamelDocsModel)
+                        .map(ArtifactModel::getName)
+                        .collect(Collectors.toSet());
+
+                final Path kindDir = referenceDir.resolve(CqUtils.kindPlural(kind));
+                try {
+                    Files.createDirectories(kindDir);
+                } catch (IOException e) {
+                    throw new RuntimeException("Could not create " + kindDir, e);
+                }
+                try (Stream<Path> kindFiles = Files.list(kindDir)) {
+                    kindFiles.forEach(kindFile -> {
+                        final String artifactIdBase = ADOC_ENDING_PATTERN.matcher(kindFile.getFileName().toString())
+                                .replaceAll("");
+                        if (cqNames.contains(artifactIdBase)) {
+                            /* Nothing to do, this should have been done by UpdateExtensionDocPageMojo */
+                        } else if (camelNames.contains(artifactIdBase)) {
+                            try {
+                                if (!Arrays.equals(DUMMY_COMPONENT_FILE_COMMENT, Files.readAllBytes(kindFile))) {
+                                    Files.write(kindFile, DUMMY_COMPONENT_FILE_COMMENT);
+                                }
+                            } catch (IOException e) {
+                                throw new RuntimeException("Could not read or write " + kindFile, e);
+                            }
+                        } else {
+                            try {
+                                Files.delete(kindFile);
+                            } catch (IOException e) {
+                                throw new RuntimeException("Could not delete " + kindFile, e);
+                            }
+                        }
+                    });
+                } catch (IOException e) {
+                    throw new RuntimeException("Could not list " + kindDir, e);
+                }
+
+                for (String name : camelNames) {
+                    final Path kindFile = kindDir.resolve(name + ".adoc");
+                    if (!Files.isRegularFile(kindFile)) {
+                        try {
+                            Files.write(kindFile, DUMMY_COMPONENT_FILE_COMMENT);
+                        } catch (IOException e) {
+                            throw new RuntimeException("Could not write " + kindFile, e);
+                        }
+                    }
+                }
+            });
+        }
+
+    }
+
+    void extensions(Path docsBasePath) {
+        final Set<String> artifactIdBases = extensionDirectories.stream()
+                .map(File::toPath)
+                .flatMap(CqUtils::findExtensionArtifactIdBases)
+                .collect(Collectors.toSet());
+
+        final Path docsExtensionsDir = docsBasePath.resolve("modules/ROOT/pages/reference/extensions");
+        try (Stream<Path> docPages = Files.list(docsExtensionsDir)) {
+            docPages
+                    .filter(docPagePath -> !artifactIdBases
+                            .contains(ADOC_ENDING_PATTERN.matcher(docPagePath.getFileName().toString()).replaceAll("")))
+                    .forEach(docPagePath -> {
+                        try {
+                            Files.delete(docPagePath);
+                        } catch (IOException e) {
+                            throw new RuntimeException("Could not delete " + docPagePath, e);
+                        }
+                    });
+        } catch (IOException e) {
+            throw new RuntimeException("Could not list " + docsExtensionsDir, e);
+        }
+    }
+
+}
diff --git a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqCatalog.java b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqCatalog.java
index de18bc2..ecc1ebb 100644
--- a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqCatalog.java
+++ b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqCatalog.java
@@ -18,6 +18,8 @@ package org.apache.camel.quarkus.maven;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -29,15 +31,50 @@ import java.util.stream.Stream;
 
 import org.apache.camel.catalog.CamelCatalog;
 import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.apache.camel.catalog.DefaultRuntimeProvider;
 import org.apache.camel.catalog.DefaultVersionManager;
 import org.apache.camel.catalog.Kind;
 import org.apache.camel.catalog.RuntimeProvider;
 import org.apache.camel.catalog.impl.CatalogHelper;
 import org.apache.camel.tooling.model.ArtifactModel;
 import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.tooling.model.DataFormatModel;
 
 public class CqCatalog {
 
+    public enum Flavor {
+        camel("org.apache.camel", "camel-catalog") {
+            @Override
+            public RuntimeProvider createRuntimeProvider(DefaultCamelCatalog c) {
+                return new DefaultRuntimeProvider(c);
+            }
+        },
+        camelQuarkus("org.apache.camel.quarkus", "camel-quarkus-catalog") {
+            @Override
+            public RuntimeProvider createRuntimeProvider(DefaultCamelCatalog c) {
+                return new CqRuntimeProvider(c);
+            }
+        };
+
+        private final String groupId;
+        private final String artifactId;
+
+        private Flavor(String groupId, String artifactId) {
+            this.groupId = groupId;
+            this.artifactId = artifactId;
+        }
+
+        public abstract RuntimeProvider createRuntimeProvider(DefaultCamelCatalog c);
+
+        public String getGroupId() {
+            return groupId;
+        }
+
+        public String getArtifactId() {
+            return artifactId;
+        }
+    }
+
     private final DefaultCamelCatalog catalog;
 
     private static final ThreadLocal<CqCatalog> threadLocalCamelCatalog = ThreadLocal.withInitial(CqCatalog::new);
@@ -46,10 +83,10 @@ public class CqCatalog {
         return threadLocalCamelCatalog.get();
     }
 
-    public CqCatalog(Path baseDir) {
+    public CqCatalog(Path baseDir, Flavor flavor) {
         super();
         final DefaultCamelCatalog c = new DefaultCamelCatalog(true);
-        c.setRuntimeProvider(new CqRuntimeProvider(c));
+        c.setRuntimeProvider(flavor.createRuntimeProvider(c));
         c.setVersionManager(new CqVersionManager(c, baseDir));
         this.catalog = c;
     }
@@ -113,6 +150,41 @@ public class CqCatalog {
         }
     }
 
+    public static boolean hasAlternativeScheme(ArtifactModel<?> model, String scheme) {
+        if (scheme.equals(model.getName())) {
+            return true;
+        } else if (model.getKind().equals("component")) {
+            final String altSchemes = ((ComponentModel) model).getAlternativeSchemes();
+            if (altSchemes == null || altSchemes.isEmpty()) {
+                return false;
+            } else {
+                return altSchemes.endsWith("," + scheme) || altSchemes.indexOf("," + scheme + ",") > 0;
+            }
+        } else {
+            return false;
+        }
+    }
+
+    public static ArtifactModel<?> findFirstSchemeModel(ArtifactModel<?> model, List<ArtifactModel<?>> models) {
+        if (model.getKind().equals("component")) {
+            final String altSchemes = ((ComponentModel) model).getAlternativeSchemes();
+            if (altSchemes == null || altSchemes.isEmpty()) {
+                return model;
+            } else {
+                final String scheme = model.getName();
+                return models.stream()
+                        .filter(m -> "component".equals(m.getKind()))
+                        .filter(CqCatalog::isFirstScheme)
+                        .filter(m -> CqCatalog.hasAlternativeScheme(m, scheme))
+                        .findFirst()
+                        .orElseThrow(() -> new IllegalStateException(
+                                "Could not find first scheme model for scheme " + scheme + " in " + models));
+            }
+        } else {
+            return model;
+        }
+    }
+
     public static List<ArtifactModel<?>> primaryModel(Stream<ArtifactModel<?>> input) {
         final List<ArtifactModel<?>> models = input
                 .filter(CqCatalog::isFirstScheme)
@@ -130,6 +202,21 @@ public class CqCatalog {
         return models;
     }
 
+    public static ArtifactModel<?> toCamelDocsModel(ArtifactModel<?> m) {
+        if ("imap".equals(m.getName())) {
+            final ComponentModel clone = (ComponentModel) CqUtils.cloneArtifactModel(m);
+            clone.setName("mail");
+            clone.setTitle("Mail");
+            return clone;
+        }
+        if (m.getName().startsWith("bindy")) {
+            final DataFormatModel clone = (DataFormatModel) CqUtils.cloneArtifactModel(m);
+            clone.setName("bindy");
+            return clone;
+        }
+        return m;
+    }
+
     static class CqVersionManager extends DefaultVersionManager {
         private final Path baseDir;
 
@@ -284,4 +371,33 @@ public class CqCatalog {
         }
     }
 
+    public static class GavCqCatalog extends CqCatalog implements AutoCloseable {
+
+        private final FileSystem jarFileSystem;
+
+        public static GavCqCatalog open(Path localRepository, Flavor flavor, String version) {
+            final Path jarPath = CqUtils.copyJar(localRepository, flavor.getGroupId(), flavor.getArtifactId(), version);
+            try {
+                final FileSystem fs = FileSystems.newFileSystem(jarPath, (ClassLoader) null);
+                return new GavCqCatalog(fs, flavor);
+            } catch (IOException e) {
+                throw new RuntimeException("Could not open file system " + jarPath, e);
+            }
+        }
+
+        GavCqCatalog(FileSystem jarFileSystem, Flavor flavor) {
+            super(jarFileSystem.getRootDirectories().iterator().next(), flavor);
+            this.jarFileSystem = jarFileSystem;
+        }
+
+        @Override
+        public void close() {
+            try {
+                jarFileSystem.close();
+            } catch (IOException e) {
+                throw new RuntimeException("Could not close catalog " + jarFileSystem, e);
+            }
+        }
+    }
+
 }
diff --git a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqUtils.java b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqUtils.java
index fa86f51..d744903 100644
--- a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqUtils.java
+++ b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/CqUtils.java
@@ -17,6 +17,9 @@
 package org.apache.camel.quarkus.maven;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -34,6 +37,11 @@ import freemarker.template.Configuration;
 import freemarker.template.TemplateExceptionHandler;
 import org.apache.camel.catalog.Kind;
 import org.apache.camel.tooling.model.ArtifactModel;
+import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.tooling.model.DataFormatModel;
+import org.apache.camel.tooling.model.JsonMapper;
+import org.apache.camel.tooling.model.LanguageModel;
+import org.apache.camel.tooling.model.OtherModel;
 import org.apache.maven.model.Model;
 import org.apache.maven.plugin.logging.Log;
 
@@ -196,4 +204,46 @@ public class CqUtils {
         return title.contains("(deprecated)") || models.stream().anyMatch(m -> m.isDeprecated());
     }
 
+    static Path copyJar(Path localRepository, String groupId, String artifactId, String version) {
+        final String relativeJarPath = groupId.replace('.', '/') + "/" + artifactId + "/" + version + "/" + artifactId + "-"
+                + version + ".jar";
+        final Path localPath = localRepository.resolve(relativeJarPath);
+        final boolean localExists = Files.exists(localPath);
+        final String remoteUri = "https://repository.apache.org/content/groups/public/" + relativeJarPath;
+        Path result;
+        try {
+            result = Files.createTempFile(null, localPath.getFileName().toString());
+            try (InputStream in = (localExists ? Files.newInputStream(localPath) : new URL(remoteUri).openStream());
+                    OutputStream out = Files.newOutputStream(result)) {
+                final byte[] buf = new byte[4096];
+                int len;
+                while ((len = in.read(buf)) >= 0) {
+                    out.write(buf, 0, len);
+                }
+            } catch (IOException e) {
+                throw new RuntimeException("Could not copy " + (localExists ? localPath : remoteUri) + " to " + result, e);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("Could not create temp file", e);
+        }
+        return result;
+    }
+
+    public static ArtifactModel<?> cloneArtifactModel(ArtifactModel<?> model) {
+        final Kind kind = Kind.valueOf(model.getKind());
+        switch (kind) {
+        case component:
+            return JsonMapper.generateComponentModel(JsonMapper.asJsonObject((ComponentModel) model));
+        case dataformat:
+            return JsonMapper.generateDataFormatModel(JsonMapper.asJsonObject((DataFormatModel) model));
+        case language:
+            return JsonMapper.generateLanguageModel(JsonMapper.asJsonObject((LanguageModel) model));
+        case other:
+            return JsonMapper.generateOtherModel(JsonMapper.asJsonObject((OtherModel) model));
+        default:
+            throw new IllegalArgumentException("Unexpected kind " + kind);
+        }
+
+    }
+
 }
diff --git a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/UpdateExtensionDocPageMojo.java b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/UpdateExtensionDocPageMojo.java
index 1c25b69..ad42b10 100644
--- a/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/UpdateExtensionDocPageMojo.java
+++ b/tooling/maven-plugin/src/main/java/org/apache/camel/quarkus/maven/UpdateExtensionDocPageMojo.java
@@ -36,7 +36,9 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import freemarker.template.Configuration;
 import freemarker.template.TemplateMethodModelEx;
+import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
+import freemarker.template.utility.DeepUnwrap;
 import io.quarkus.annotation.processor.Constants;
 import io.quarkus.annotation.processor.generate_doc.ConfigDocItem;
 import io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil;
@@ -128,11 +130,36 @@ public class UpdateExtensionDocPageMojo extends AbstractDocGeneratorMojo {
                 return CqUtils.humanReadableKind(Kind.valueOf(String.valueOf(arguments.get(0))));
             }
         });
+        model.put("camelBitLink", new TemplateMethodModelEx() {
+            @Override
+            public Object exec(List arguments) throws TemplateModelException {
+                if (arguments.size() != 2) {
+                    throw new TemplateModelException("Wrong argument count in camelBitLink()");
+                }
+                final ArtifactModel<?> model = (ArtifactModel<?>) DeepUnwrap.unwrap((TemplateModel) arguments.get(0));
+                final String kind = model.getKind();
+                if (CqCatalog.isFirstScheme(model)) {
+                    return camelBitLink(model);
+                } else {
+                    final List<ArtifactModel<?>> models = (List<ArtifactModel<?>>) DeepUnwrap
+                            .unwrap((TemplateModel) arguments.get(1));
+                    final ArtifactModel<?> firstModel = CqCatalog.findFirstSchemeModel(model, models);
+                    return camelBitLink(firstModel);
+                }
+            }
+
+            private String camelBitLink(ArtifactModel<?> model) {
+                model = CqCatalog.toCamelDocsModel(model);
+                final String kind = model.getKind();
+                return "https://camel.apache.org/components/latest/" + (!"component".equals(kind) ? kind + "s/" : "")
+                        + model.getName() + (!"other".equals(kind) ? "-" + kind : "") + ".html";
+            }
+        });
         model.put("toAnchor", new TemplateMethodModelEx() {
             @Override
             public Object exec(List arguments) throws TemplateModelException {
                 if (arguments.size() != 1) {
-                    throw new TemplateModelException("Wrong argument count in toCamelCase()");
+                    throw new TemplateModelException("Wrong argument count in toAnchor()");
                 }
                 String string = String.valueOf(arguments.get(0));
                 string = Normalizer.normalize(string, Normalizer.Form.NFKC)
@@ -203,7 +230,7 @@ public class UpdateExtensionDocPageMojo extends AbstractDocGeneratorMojo {
         } catch (IOException e) {
             throw new RuntimeException("Could not create directories " + docPagePath.getParent(), e);
         }
-        String pageText = "// Do not edit directly!\n// This file was generated by camel-quarkus-maven-plugin:update-extension-doc-page\n\n"
+        String pageText = "// Do not edit directly!\n// This file was generated by camel-quarkus-maven-plugin:update-extension-doc-page\n"
                 + evalTemplate(cfg, template, model, new StringWriter()).toString();
         try {
             Files.write(docPagePath, pageText.getBytes(charset));
@@ -223,9 +250,10 @@ public class UpdateExtensionDocPageMojo extends AbstractDocGeneratorMojo {
                     modelClone.put("camelPartTitle", m.getTitle());
                     modelClone.put("camelPartDescription", m.getDescription());
 
+                    final ArtifactModel<?> camelDocModel = CqCatalog.toCamelDocsModel(m);
                     final Path docPagePath = multiModuleProjectDirectoryPath
-                            .resolve("docs/modules/ROOT/pages/reference/" + CqUtils.kindPlural(kind) + "/"
-                                    + ext.getRuntimeArtifactIdBase() + ".adoc");
+                            .resolve("docs/modules/ROOT/partials/reference/" + CqUtils.kindPlural(kind) + "/"
+                                    + camelDocModel.getName() + ".adoc");
 
                     evalTemplate(charset, docPagePath, cfg, modelClone, "extensions-camel-bits.adoc");
 
diff --git a/tooling/maven-plugin/src/main/resources/doc-templates/extension-doc-page.adoc b/tooling/maven-plugin/src/main/resources/doc-templates/extension-doc-page.adoc
index 0ac63d9..9097d76 100644
--- a/tooling/maven-plugin/src/main/resources/doc-templates/extension-doc-page.adoc
+++ b/tooling/maven-plugin/src/main/resources/doc-templates/extension-doc-page.adoc
@@ -18,7 +18,7 @@
 == What's inside
 
 [#list models as model]
-* https://camel.apache.org/components/latest/[#if model.kind != "component" ][=model.kind]s/[/#if][=model.name][#if model.kind != "other" ]-[=model.kind][/#if].html[[=model.title][#if model.kind != "other" ] [=humanReadableKind(model.kind)][/#if]][#if model.kind == "component" ], URI syntax: `[=model.syntax]`[/#if]
+* [=camelBitLink(model, models)][[=model.title][#if model.kind != "other" ] [=humanReadableKind(model.kind)][/#if]][#if model.kind == "component" ], URI syntax: `[=model.syntax]`[/#if]
 [/#list]
 
 Please refer to the above link[#if models?size != 1]s[/#if] for usage and configuration details.
diff --git a/tooling/maven-plugin/src/main/resources/doc-templates/extensions-camel-bits.adoc b/tooling/maven-plugin/src/main/resources/doc-templates/extensions-camel-bits.adoc
index 5312efd..109fa96 100644
--- a/tooling/maven-plugin/src/main/resources/doc-templates/extensions-camel-bits.adoc
+++ b/tooling/maven-plugin/src/main/resources/doc-templates/extensions-camel-bits.adoc
@@ -1,4 +1,3 @@
-= [=camelPartTitle]
 :cq-artifact-id: camel-quarkus-[=artifactIdBase]
 :cq-artifact-id-base: [=artifactIdBase]
 :cq-native-supported: [=nativeSupported?then('true', 'false')]