You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by kw...@apache.org on 2026/03/28 09:25:45 UTC

(maven-doxia-sitetools) branch master updated: Allow to set an alternative source directory (for editing) (#625)

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

kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-doxia-sitetools.git


The following commit(s) were added to refs/heads/master by this push:
     new 081a0e8  Allow to set an alternative source directory (for editing) (#625)
081a0e8 is described below

commit 081a0e848e0a1a07545fcb8dc75b9311354cb097
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Sat Mar 28 10:25:40 2026 +0100

    Allow to set an alternative source directory (for editing) (#625)
    
    Always check if a file is existing before returning true for isEditable
    and non-null for getDoxiaSourcePath()/getDoxiaSourcePath(String)
    
    This closes #277
---
 .../doxia/siterenderer/DefaultSiteRenderer.java    |  30 +--
 .../siterenderer/DocumentRenderingContext.java     | 206 ++++++++++++++++++---
 .../doxia/siterenderer/SiteRenderingContext.java   |  38 +++-
 .../siterenderer/DefaultSiteRendererTest.java      |  10 +-
 .../siterenderer/DocumentRenderingContextTest.java | 116 ++++++++++++
 .../doxia/siterenderer/RenderingContextTest.java   |  76 --------
 6 files changed, 352 insertions(+), 124 deletions(-)

diff --git a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java
index 82ed93e..861cfea 100644
--- a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java
+++ b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java
@@ -198,12 +198,11 @@ public class DefaultSiteRenderer implements Renderer {
 
                     addModuleFiles(
                             siteRenderingContext.getRootDirectory(),
+                            siteDirectory,
                             moduleBasedir,
                             module,
                             excludes,
-                            files,
-                            siteDirectory.isEditable(),
-                            siteDirectory.isSkipDuplicates());
+                            files);
                 }
             }
         }
@@ -228,32 +227,27 @@ public class DefaultSiteRenderer implements Renderer {
      * Populates the files map with {@link DocumentRenderer}s per output name in parameter {@code files} for all files in the moduleBasedir matching the module extensions,
      * taking care of duplicates if needed.
      *
-     * @param rootDir
+     * @param siteRootDirectory
+     * @param siteDirectory
      * @param moduleBasedir
      * @param module
      * @param excludes
      * @param files
-     * @param editable
-     * @param skipDuplicates
      * @throws IOException
      * @throws RendererException
      */
     private void addModuleFiles(
-            File rootDir,
+            File siteRootDirectory,
+            SiteDirectory siteDirectory,
             File moduleBasedir,
             ParserModule module,
             String excludes,
-            Map<String, DocumentRenderer> files,
-            boolean editable,
-            boolean skipDuplicates)
+            Map<String, DocumentRenderer> files)
             throws IOException, RendererException {
         if (!moduleBasedir.exists() || ArrayUtils.isEmpty(module.getExtensions())) {
             return;
         }
 
-        String moduleRelativePath =
-                PathTool.getRelativeFilePath(rootDir.getAbsolutePath(), moduleBasedir.getAbsolutePath());
-
         List<String> allFiles = FileUtils.getFileNames(moduleBasedir, "**/*", excludes, false);
 
         for (String extension : module.getExtensions()) {
@@ -268,14 +262,20 @@ public class DefaultSiteRenderer implements Renderer {
 
             for (String doc : docs) {
                 DocumentRenderingContext docRenderingContext = new DocumentRenderingContext(
-                        moduleBasedir, moduleRelativePath, doc, module.getParserId(), extension, editable);
+                        moduleBasedir,
+                        doc,
+                        module.getParserId(),
+                        extension,
+                        siteRootDirectory,
+                        siteDirectory.getPath(),
+                        siteDirectory.getEditableSourceDirectories());
 
                 // TODO: DOXIA-111: we need a general filter here that knows how to alter the context
                 if (endsWithIgnoreCase(doc, ".vm")) {
                     docRenderingContext.setAttribute("velocity", "true");
                 }
 
-                if (!checkForDuplicate(docRenderingContext, files, skipDuplicates)) {
+                if (!checkForDuplicate(docRenderingContext, files, siteDirectory.isSkipDuplicates())) {
                     String key = docRenderingContext.getOutputName();
                     files.put(key, new DoxiaDocumentRenderer(docRenderingContext));
                 }
diff --git a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DocumentRenderingContext.java b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DocumentRenderingContext.java
index b176c83..8d2be1e 100644
--- a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DocumentRenderingContext.java
+++ b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DocumentRenderingContext.java
@@ -19,8 +19,11 @@
 package org.apache.maven.doxia.siterenderer;
 
 import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
 import org.codehaus.plexus.util.PathTool;
 
@@ -32,26 +35,59 @@ import org.codehaus.plexus.util.PathTool;
  * @since 1.5 (was since 1.1 in o.a.m.d.sink.render)
  */
 public class DocumentRenderingContext {
-    private final File basedir;
 
-    private final String basedirRelativePath;
+    /** absolute path to the source base directory (not null, pseudo value when not a Doxia source), this is parser format specific. Must start with {@link #siteRootDirectory}. */
+    private final File basedir;
 
+    /** {@link #basedir} relative path to the document's source including {@link #extension}. May be {@code null} if not rendered from a Doxia source */
     private final String inputPath;
 
+    /** same as {@link #inputPath} but with extension replaced with {@code .html}, this is parser format specific */
     private final String outputPath;
 
+    /** the Doxia module parser id associated to this document, may be null if document not rendered from a Doxia source. */
     private final String parserId;
 
-    private final String relativePath;
-
+    /** the source document filename extension, may be null if document not rendered from a Doxia source. */
     private final String extension;
 
     private Map<String, String> attributes;
 
-    private final boolean editable;
+    /**
+     * The absolute paths of directories which may contain the original editable source.
+     * If empty document is not editable.
+     */
+    private final Collection<File> sourceDirectories;
+
+    /** The project's build directory, may be {@code null} rendered from a Doxia source) */
+    private final File rootDirectory;
+
+    /** The site's root directory, must be below {@link #rootDirectory}, may be {@code null} if not rendered from a Doxia source */
+    private final File siteRootDirectory;
 
+    /** optional descriptive text of the plugin which generated the output (usually Maven coordinates). Only set when document is not based on a Doxia source. */
     private final String generator;
 
+    static File stripSuffixFromPath(File file, String suffix) {
+        File relevantFile = file;
+        if (suffix == null || suffix.isEmpty()) {
+            return relevantFile;
+        }
+        File currentSuffixPart = new File(suffix);
+        // compare elements from end, suffix should be a suffix of file
+        do {
+            if (currentSuffixPart.getName().equals(relevantFile.getName())) {
+                relevantFile = relevantFile.getParentFile();
+                if (relevantFile == null) {
+                    throw new IllegalArgumentException("Suffix " + suffix + " has more elements than file " + file);
+                }
+            } else {
+                throw new IllegalArgumentException("Suffix " + suffix + " is not a suffix of file " + file);
+            }
+        } while ((currentSuffixPart = currentSuffixPart.getParentFile()) != null);
+        return relevantFile;
+    }
+
     /**
      * <p>
      * Constructor for rendering context when document is not rendered from a Doxia markup source.
@@ -64,9 +100,20 @@ public class DocumentRenderingContext {
      * @since 1.8
      */
     public DocumentRenderingContext(File basedir, String document, String generator) {
-        this(basedir, null, document, null, null, false, generator);
+        this(basedir, document, null, null, null, null, Collections.emptySet(), generator);
     }
 
+    /**
+     *
+     * @param basedir
+     * @param basedirRelativePath
+     * @param document
+     * @param parserId
+     * @param extension
+     * @param editable
+     * @deprecated Use {@link #DocumentRenderingContext(File, String, String, String, File, File, Collection, String)} instead.
+     */
+    @Deprecated
     public DocumentRenderingContext(
             File basedir,
             String basedirRelativePath,
@@ -79,20 +126,50 @@ public class DocumentRenderingContext {
 
     /**
      * <p>
-     * Constructor for document rendering context.
+     * Constructor for rendering context when document is a Doxia markup source.
      * </p>
+     * Same as {@link #DocumentRenderingContext(File, String, String, String, File, File, Collection, String)} with {@code generator} set to {@code null}.
      *
      * @param basedir the source base directory (not null, pseudo value when not a Doxia source).
-     * @param basedirRelativePath the relative path from root (null if not Doxia source)
      * @param document the source document path.
-     * @param parserId the Doxia module parser id associated to this document, may be null if document not rendered from
+     * @param parserId the Doxia module parser id associated with this document, may be null if document not rendered from
      *            a Doxia source.
      * @param extension the source document filename extension, may be null if document not rendered from
      *            a Doxia source.
-     * @param editable is the document editable as source, i.e. not generated?
-     * @param generator the generator (in general a reporting goal: <code>groupId:artifactId:version:goal</code>)
+     * @param rootDirectory the absolute project's root directory (not null), usually {@code project.basedir}, must be an ancestor of {@code siteRootDirectory}
+     * @param siteRootDirectory the absolute site's root directory (not null), must start with {@code rootDirectory}, often ends with {@code /src/site}
+     * @param sourceDirectories the absolute paths of directories which may contain the original editable source.
+     * @since 2.1
+     */
+    public DocumentRenderingContext(
+            File basedir,
+            String document,
+            String parserId,
+            String extension,
+            File rootDirectory,
+            File siteRootDirectory,
+            Collection<File> sourceDirectories) {
+        this(basedir, document, parserId, extension, rootDirectory, siteRootDirectory, sourceDirectories, null);
+    }
+
+    /**
+     * <p>
+     * Constructor for document rendering context.
+     * </p>
+     *
+     * @param basedir the absolute source base directory (not null, pseudo value when not a Doxia source).
+     * @param basedirRelativePath the relative path of {@code #basedir} from project root (null if not Doxia source)
+     * @param document the source document path.
+     * @param parserId the Doxia module parser id associated with this document, may be null if document not rendered from
+     *            a Doxia source.
+     * @param extension the source document filename extension, may be null if document not rendered from
+     *            a Doxia source.
+     * @param editable {@code true} if the document is editable as source, i.e. not generated, {@code false} otherwise.
+     * @param generator the generator of this document (in general a reporting goal: <code>groupId:artifactId:version:goal</code>), not set when document is based on a Doxia source.
      * @since 1.8
+     * @deprecated Use {@link #DocumentRenderingContext(File, String, String, String, File, File, Collection, String)} instead.
      */
+    @Deprecated
     public DocumentRenderingContext(
             File basedir,
             String basedirRelativePath,
@@ -101,6 +178,46 @@ public class DocumentRenderingContext {
             String extension,
             boolean editable,
             String generator) {
+        this(
+                basedir,
+                document,
+                parserId,
+                extension,
+                stripSuffixFromPath(basedir, basedirRelativePath),
+                // assume that site root is the parent of basedir (i.e. module specific source directory is directly
+                // below site root)
+                basedir.getParentFile(),
+                editable ? Collections.singleton(basedir) : Collections.emptySet(),
+                generator);
+    }
+
+    /**
+     * <p>
+     * Constructor for document rendering context.
+     * </p>
+     *
+     * @param basedir the source base directory (not null, pseudo value when not a Doxia source).
+     * @param document the source document path.
+     * @param parserId the Doxia module parser id associated with this document, may be null if document not rendered from
+     *            a Doxia source.
+     * @param extension the source document filename extension, may be null if document not rendered from
+     *            a Doxia source.
+     * @param rootDirectory the absolute project's root directory (not null), usually {@code project.basedir}, must be an ancestor of {@code siteRootDirectory}
+     * @param siteRootDirectory the absolute site's root directory (not null), must start with {@code rootDirectory}, often ends with {@code /src/site}
+     * @param sourceDirectories the absolute paths of directories which may contain the original editable source.
+     * @param generator the generator (in general a reporting goal: <code>groupId:artifactId:version:goal</code>)
+     * @since 2.1
+     */
+    @SuppressWarnings("checkstyle:parameternumber")
+    public DocumentRenderingContext(
+            File basedir,
+            String document,
+            String parserId,
+            String extension,
+            File rootDirectory,
+            File siteRootDirectory,
+            Collection<File> sourceDirectories,
+            String generator) {
         this.basedir = basedir;
         this.parserId = parserId;
         this.extension = extension;
@@ -110,10 +227,23 @@ public class DocumentRenderingContext {
         document = document.replace('\\', '/');
         this.inputPath = document;
 
+        if (rootDirectory == null) {
+            if (siteRootDirectory != null) {
+                throw new IllegalArgumentException("Root directory must not be null when site root directory is set");
+            }
+        } else {
+            Objects.requireNonNull(
+                    siteRootDirectory, "Site root directory must not be null when root directory is set");
+            if (!siteRootDirectory.getPath().startsWith(rootDirectory.getPath())) {
+                throw new IllegalArgumentException("Site root directory " + siteRootDirectory
+                        + " must start with root directory " + rootDirectory);
+            }
+        }
+        this.rootDirectory = rootDirectory;
+        this.siteRootDirectory = siteRootDirectory;
         if (extension != null && !extension.isEmpty()) {
-            this.basedirRelativePath = basedirRelativePath.replace('\\', '/');
             // document comes from a Doxia source: see DoxiaDocumentRenderer
-            this.editable = editable;
+            this.sourceDirectories = sourceDirectories;
 
             // here we know the parserId and extension, we can play with this to get output name from document:
             // - index.xml -> index.html
@@ -126,13 +256,9 @@ public class DocumentRenderingContext {
             this.outputPath = filePathWithoutExt + ".html";
         } else {
             // document does not come from a Doxia source but direct Sink API, so no file extension to strip
-            this.basedirRelativePath = null;
-            this.editable = false;
+            this.sourceDirectories = Collections.emptySet();
             this.outputPath = document + ".html";
         }
-
-        this.relativePath = PathTool.getRelativePath(basedir.getPath(), new File(basedir, inputPath).getPath())
-                .replace('\\', '/');
     }
 
     /**
@@ -189,12 +315,13 @@ public class DocumentRenderingContext {
     }
 
     /**
-     * Get the relative path to site root.
+     * Get the relative path of the parent directory of this document to site root.
      *
      * @return the relative path to site root
      */
     public String getRelativePath() {
-        return relativePath;
+        return PathTool.getRelativePath(basedir.getPath(), new File(basedir, inputPath).getPath())
+                .replace('\\', '/');
     }
 
     /**
@@ -233,7 +360,7 @@ public class DocumentRenderingContext {
      * @since 1.8
      */
     public boolean isEditable() {
-        return editable;
+        return getDoxiaSourcePath() != null;
     }
 
     /**
@@ -257,27 +384,54 @@ public class DocumentRenderingContext {
     }
 
     /**
-     * Get the relative path of basedir (when a Doxia source)
+     * Get the project root relative path of basedir (when a Doxia source). For example {@code src/site/markdown}.
      *
      * @return the relative path of basedir when a Doxia source, or <code>null</code> if not a Doxia source
      * @since 1.8
      */
     public String getBasedirRelativePath() {
-        return basedirRelativePath;
+        if (!isDoxiaSource()) {
+            return null;
+        }
+        return PathTool.getRelativeFilePath(rootDirectory.getPath(), basedir.getPath());
+    }
+
+    /**
+     * Get the site root relative path of basedir (when a Doxia source). For example {@code markdown}.
+     *
+     * @return the relative path of basedir when a Doxia source, or <code>null</code> if not a Doxia source
+     */
+    private String getBasedirRelativePathAgainstSiteRoot() {
+        if (!isDoxiaSource()) {
+            return null;
+        }
+        return PathTool.getRelativeFilePath(siteRootDirectory.getPath(), basedir.getPath());
     }
 
     /**
-     * Get the relative path to Doxia source from build root.
+     * Get the relative path to Doxia source from build root. The file separators in the returned path are {@code /} regardless of the platform..
      *
      * @return the relative path to Doxia source from build root, or <code>null</code> if not a Doxia source
      * @since 1.8
      */
     public String getDoxiaSourcePath() {
-        return isDoxiaSource() ? (basedirRelativePath + '/' + inputPath) : null;
+        if (!isDoxiaSource()) {
+            return null;
+        } else {
+            for (File sourceDirectory : sourceDirectories) {
+                File sourceFile = new File(sourceDirectory, getBasedirRelativePathAgainstSiteRoot() + '/' + inputPath);
+                if (sourceFile.exists()) {
+                    return PathTool.getRelativeFilePath(rootDirectory.getPath(), sourceFile.getPath())
+                            .replace('\\', '/');
+                }
+            }
+        }
+        return null;
     }
 
     /**
-     * Get url of the Doxia source calculate from given base url.
+     * Get absolute url of the Doxia source calculate from given base url.
+     * Used from Skins to render an edit button.
      *
      * @param base the base url to use
      * @return the resulting url
diff --git a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java
index 64db4c7..6b461f9 100644
--- a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java
+++ b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/SiteRenderingContext.java
@@ -20,8 +20,10 @@ package org.apache.maven.doxia.siterenderer;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -44,6 +46,7 @@ public class SiteRenderingContext {
         private File path;
         private boolean editable;
         private boolean skipDuplicates;
+        private final Collection<File> editableSourceDirectories = new LinkedHashSet<>();
 
         public SiteDirectory(File path, boolean editable) {
             this(path, editable, false);
@@ -51,8 +54,8 @@ public class SiteRenderingContext {
 
         /**
          *
-         * @param path
-         * @param editable
+         * @param path path to the site directory containing Doxia sources files, expected to have a Doxia Site layout, i.e. one directory per Doxia parser module
+         * @param editable {@code true} if the site directory is expected to be editable
          * @param skipDuplicates flag indicating if duplicates in this directory should be skipped ({@code true}) or lead to an exception ({@code false})
          * @since 2.1
          */
@@ -73,6 +76,37 @@ public class SiteRenderingContext {
         public boolean isSkipDuplicates() {
             return skipDuplicates;
         }
+
+        /**
+         * Add an alternative source directory to this site directory.
+         * This will implicitly turn it into an editable site directory. Multiple source directories can be used, the first one containing a file with a given name will be used for that file,
+         * the others will be ignored. This allows to have a main source directory and an optional overlay directory with custom files.
+         * Only necessary to call if the source directory is different from the site directory, otherwise the site directory will be used as source directory.
+         * @param sourceDirectory
+         * @since 2.1
+         */
+        public void addAlternativeEditableSourceDirectory(File sourceDirectory) {
+            editableSourceDirectories.add(sourceDirectory);
+            editable = true;
+        }
+
+        /**
+         * Get the source directories for this site directory. If no alternative source directory has been added via {@link #addAlternativeEditableSourceDirectory(File)}
+         * the site directory itself ({@link #getPath()}) will be returned as the only source directory.
+         * If the site directory is not editable, an empty collection will be returned.
+         * @return the source directories for this site directory
+         * @since 2.1
+         */
+        public Collection<File> getEditableSourceDirectories() {
+            if (!editable) {
+                return Collections.emptyList();
+            }
+            if (editableSourceDirectories.isEmpty()) {
+                return Collections.singleton(path);
+            } else {
+                return Collections.unmodifiableCollection(editableSourceDirectories);
+            }
+        }
     }
 
     private String inputEncoding = ReaderFactory.FILE_ENCODING;
diff --git a/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java
index d41ed81..10796ff 100644
--- a/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java
+++ b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java
@@ -181,8 +181,8 @@ public class DefaultSiteRendererTest {
         SiteRenderer siteRenderer = container.lookup(SiteRenderer.class);
         ReflectionUtils.setVariableValueInObject(siteRenderer, "doxia", doxiaSpy);
 
-        DocumentRenderingContext docRenderingContext =
-                new DocumentRenderingContext(testBasedir, "", testDocument, "xdoc", "", false);
+        DocumentRenderingContext docRenderingContext = new DocumentRenderingContext(
+                testBasedir, "src/test/resources/site/xdoc", testDocument, "xdoc", "", false);
 
         try {
             siteRenderer.renderDocument(null, docRenderingContext, new SiteRenderingContext());
@@ -212,8 +212,8 @@ public class DefaultSiteRendererTest {
         SiteRenderer siteRenderer = container.lookup(SiteRenderer.class);
         ReflectionUtils.setVariableValueInObject(siteRenderer, "doxia", doxiaSpy);
 
-        DocumentRenderingContext docRenderingContext =
-                new DocumentRenderingContext(testBasedir, "", testDocumentName, "xdoc", "", false);
+        DocumentRenderingContext docRenderingContext = new DocumentRenderingContext(
+                testBasedir, "src/test/resources/site/xdoc", testDocumentName, "xdoc", "", false);
 
         try {
             siteRenderer.renderDocument(null, docRenderingContext, new SiteRenderingContext());
@@ -335,7 +335,7 @@ public class DefaultSiteRendererTest {
 
         siteRenderingContext.setTemplateName("org/apache/maven/doxia/siterenderer/velocity-toolmanager.vm");
         DocumentRenderingContext docRenderingContext =
-                new DocumentRenderingContext(new File(""), "document.html", "generator");
+                new DocumentRenderingContext(new File("."), "document.html", "generator");
         SiteRendererSink sink = new SiteRendererSink(docRenderingContext);
         siteRenderer.mergeDocumentIntoSite(writer, sink, siteRenderingContext);
 
diff --git a/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DocumentRenderingContextTest.java b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DocumentRenderingContextTest.java
new file mode 100644
index 0000000..e455b0d
--- /dev/null
+++ b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DocumentRenderingContextTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.maven.doxia.siterenderer;
+
+import java.io.File;
+import java.util.Collections;
+
+import org.codehaus.plexus.testing.PlexusExtension;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class DocumentRenderingContextTest {
+
+    @Test
+    void stripSuffixFromPath() {
+        assertEquals(
+                new File("test/base/some/suffix"),
+                DocumentRenderingContext.stripSuffixFromPath(new File("test/base/some/suffix"), null));
+        assertEquals(
+                new File("test/base/some/suffix"),
+                DocumentRenderingContext.stripSuffixFromPath(new File("test/base/some/suffix"), ""));
+        assertEquals(
+                new File("test/base/some"),
+                DocumentRenderingContext.stripSuffixFromPath(new File("test/base/some/suffix"), "suffix"));
+        assertEquals(
+                new File("test/base"),
+                DocumentRenderingContext.stripSuffixFromPath(new File("test/base/some/suffix"), "some/suffix"));
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> DocumentRenderingContext.stripSuffixFromPath(new File("test/base/some/suffix"), "other/suffix"));
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> DocumentRenderingContext.stripSuffixFromPath(new File("some/suffix"), "test/base/some/suffix"));
+    }
+
+    @Test
+    void getDoxiaSource() {
+        // non Doxia markup source
+        DocumentRenderingContext renderingContext =
+                new DocumentRenderingContext(new File("test/base"), "test/base/some/file.html", "generator");
+        assertNull(renderingContext.getDoxiaSourcePath());
+        File rootDirectory = PlexusExtension.getTestFile("");
+        File siteDirectory = PlexusExtension.getTestFile("target/test-classes/site");
+        File sourceDirectory = PlexusExtension.getTestFile("src/test/resources/site");
+        renderingContext = new DocumentRenderingContext(
+                new File(siteDirectory, "markdown"),
+                "mermaid.md",
+                "markdown",
+                "md",
+                rootDirectory,
+                siteDirectory,
+                Collections.singleton(sourceDirectory));
+        assertEquals("src/test/resources/site/markdown/mermaid.md", renderingContext.getDoxiaSourcePath());
+    }
+
+    /**
+     * Test getRelativePath() with various file paths.
+     *
+     * @throws java.lang.Exception if any.
+     */
+    @Test
+    void filePathWithDot() throws Exception {
+        File baseDir = new File("." + File.separatorChar + "test" + File.separatorChar + "resources");
+
+        String document = "file.with.dot.in.name.xml";
+        String baseDirRelativePath = "test" + File.separatorChar + "resources";
+        DocumentRenderingContext docRenderingContext =
+                new DocumentRenderingContext(baseDir, baseDirRelativePath, document, "", "xml", false);
+        assertEquals("file.with.dot.in.name.html", docRenderingContext.getOutputPath());
+        assertEquals(".", docRenderingContext.getRelativePath());
+
+        document = "file.with.dot.in.name";
+        docRenderingContext = new DocumentRenderingContext(baseDir, document, "generator"); // not Doxia source
+        assertEquals("file.with.dot.in.name.html", docRenderingContext.getOutputPath());
+        assertEquals(".", docRenderingContext.getRelativePath());
+
+        document = "index.xml.vm";
+        docRenderingContext = new DocumentRenderingContext(baseDir, baseDirRelativePath, document, "", "xml", false);
+        assertEquals("index.html", docRenderingContext.getOutputPath());
+        assertEquals(".", docRenderingContext.getRelativePath());
+
+        document = "download.apt.vm";
+        docRenderingContext = new DocumentRenderingContext(baseDir, baseDirRelativePath, document, "", "apt", false);
+        assertEquals("download.html", docRenderingContext.getOutputPath());
+        assertEquals(".", docRenderingContext.getRelativePath());
+
+        document = "path/file.apt";
+        docRenderingContext = new DocumentRenderingContext(baseDir, baseDirRelativePath, document, "", "apt", false);
+        assertEquals("path/file.html", docRenderingContext.getOutputPath());
+        assertEquals("..", docRenderingContext.getRelativePath());
+
+        document = "path/file";
+        docRenderingContext = new DocumentRenderingContext(baseDir, document, "generator"); // not Doxia source
+        assertEquals("path/file.html", docRenderingContext.getOutputPath());
+        assertEquals("..", docRenderingContext.getRelativePath());
+    }
+}
diff --git a/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/RenderingContextTest.java b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/RenderingContextTest.java
deleted file mode 100644
index 5e8fae5..0000000
--- a/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/RenderingContextTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.maven.doxia.siterenderer;
-
-import java.io.File;
-
-import org.codehaus.plexus.testing.PlexusTest;
-import org.junit.jupiter.api.Test;
-
-import static org.codehaus.plexus.testing.PlexusExtension.getBasedir;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-/**
- * @author <a href="mailto:olamy@apache.org">olamy</a>
- * @since 20 oct. 07
- */
-@PlexusTest
-class RenderingContextTest {
-
-    /**
-     * Test getRelativePath() with various file paths.
-     *
-     * @throws java.lang.Exception if any.
-     */
-    @Test
-    void filePathWithDot() throws Exception {
-        File baseDir = new File(getBasedir() + File.separatorChar + "test" + File.separatorChar + "resources");
-
-        String document = "file.with.dot.in.name.xml";
-        DocumentRenderingContext docRenderingContext =
-                new DocumentRenderingContext(baseDir, "test", document, "", "xml", false);
-        assertEquals("file.with.dot.in.name.html", docRenderingContext.getOutputPath());
-        assertEquals(".", docRenderingContext.getRelativePath());
-
-        document = "file.with.dot.in.name";
-        docRenderingContext = new DocumentRenderingContext(baseDir, document, "generator"); // not Doxia source
-        assertEquals("file.with.dot.in.name.html", docRenderingContext.getOutputPath());
-        assertEquals(".", docRenderingContext.getRelativePath());
-
-        document = "index.xml.vm";
-        docRenderingContext = new DocumentRenderingContext(baseDir, "test", document, "", "xml", false);
-        assertEquals("index.html", docRenderingContext.getOutputPath());
-        assertEquals(".", docRenderingContext.getRelativePath());
-
-        document = "download.apt.vm";
-        docRenderingContext = new DocumentRenderingContext(baseDir, "test", document, "", "apt", false);
-        assertEquals("download.html", docRenderingContext.getOutputPath());
-        assertEquals(".", docRenderingContext.getRelativePath());
-
-        document = "path/file.apt";
-        docRenderingContext = new DocumentRenderingContext(baseDir, "test", document, "", "apt", false);
-        assertEquals("path/file.html", docRenderingContext.getOutputPath());
-        assertEquals("..", docRenderingContext.getRelativePath());
-
-        document = "path/file";
-        docRenderingContext = new DocumentRenderingContext(baseDir, document, "generator"); // not Doxia source
-        assertEquals("path/file.html", docRenderingContext.getOutputPath());
-        assertEquals("..", docRenderingContext.getRelativePath());
-    }
-}