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

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

commit 3baee06f2a7cd31bb61ade60c7771c37e38c3f6f
Author: Konrad Windszus <[email protected]>
AuthorDate: Wed Mar 11 14:45:38 2026 +0100

    Allow to set an alternative source directory (for editing)
    
    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    |  25 ++--
 .../siterenderer/DocumentRenderingContext.java     | 127 ++++++++++++++++++---
 .../doxia/siterenderer/SiteRenderingContext.java   |  34 ++++++
 .../siterenderer/DefaultSiteRendererTest.java      |   6 +-
 .../siterenderer/DocumentRenderingContextTest.java |  51 +++++++++
 .../doxia/siterenderer/RenderingContextTest.java   |   9 +-
 6 files changed, 213 insertions(+), 39 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..8cfe368 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);
                 }
             }
         }
@@ -229,31 +228,26 @@ public class DefaultSiteRenderer implements Renderer {
      * taking care of duplicates if needed.
      *
      * @param rootDir
+     * @param siteDirectory
      * @param moduleBasedir
      * @param module
      * @param excludes
      * @param files
-     * @param editable
-     * @param skipDuplicates
      * @throws IOException
      * @throws RendererException
      */
     private void addModuleFiles(
             File rootDir,
+            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,19 @@ 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,
+                        rootDir,
+                        siteDirectory.getSourceDirectories());
 
                 // 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..97893f0 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,6 +19,8 @@
 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;
 
@@ -32,26 +34,55 @@ 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 */
+    private final File basedir;
 
+    /** {@link #basedir} relative path to the document's source including 
{@link #extension}. May be {@code null} if not based by 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 (never null) */
+    private final File rootDirectory;
 
+    /** 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 +95,10 @@ 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, basedir, Collections.emptySet(), 
generator);
     }
 
+    @Deprecated
     public DocumentRenderingContext(
             File basedir,
             String basedirRelativePath,
@@ -77,6 +109,16 @@ public class DocumentRenderingContext {
         this(basedir, basedirRelativePath, document, parserId, extension, 
editable, null);
     }
 
+    public DocumentRenderingContext(
+            File basedir,
+            String document,
+            String parserId,
+            String extension,
+            File rootDirectory,
+            Collection<File> sourceDirectories) {
+        this(basedir, document, parserId, extension, rootDirectory, 
sourceDirectories, null);
+    }
+
     /**
      * <p>
      * Constructor for document rendering context.
@@ -101,6 +143,42 @@ public class DocumentRenderingContext {
             String extension,
             boolean editable,
             String generator) {
+        this(
+                basedir,
+                document,
+                parserId,
+                extension,
+                stripSuffixFromPath(basedir, basedirRelativePath),
+                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 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
+     *            a Doxia source.
+     * @param extension the source document filename extension, may be null if 
document not rendered from
+     *            a Doxia source.
+     * @param rootDirectory the project's root directory (not null)
+     * @param sourceDirectories the absolute paths of directories which may 
contain the original editable 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>)
+     * @since 2.1
+     */
+    public DocumentRenderingContext(
+            File basedir,
+            String document,
+            String parserId,
+            String extension,
+            File rootDirectory,
+            Collection<File> sourceDirectories,
+            String generator) {
         this.basedir = basedir;
         this.parserId = parserId;
         this.extension = extension;
@@ -110,10 +188,10 @@ public class DocumentRenderingContext {
         document = document.replace('\\', '/');
         this.inputPath = document;
 
+        this.rootDirectory = rootDirectory;
         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 +204,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 +263,12 @@ public class DocumentRenderingContext {
     }
 
     /**
-     * Get the relative path to site root.
+     * Get the relative path of the parent folder 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());
     }
 
     /**
@@ -233,7 +307,7 @@ public class DocumentRenderingContext {
      * @since 1.8
      */
     public boolean isEditable() {
-        return editable;
+        return getDoxiaSourcePath() != null;
     }
 
     /**
@@ -263,7 +337,10 @@ public class DocumentRenderingContext {
      * @since 1.8
      */
     public String getBasedirRelativePath() {
-        return basedirRelativePath;
+        if (!isDoxiaSource()) {
+            return null;
+        }
+        return PathTool.getRelativeFilePath(rootDirectory.getPath(), 
basedir.getPath());
     }
 
     /**
@@ -273,11 +350,23 @@ public class DocumentRenderingContext {
      * @since 1.8
      */
     public String getDoxiaSourcePath() {
-        return isDoxiaSource() ? (basedirRelativePath + '/' + inputPath) : 
null;
+        if (!isDoxiaSource()) {
+            return null;
+        } else {
+            // legacy fuzzy logic if no source directories are specified
+            for (File sourceDirectory : sourceDirectories) {
+                File sourceFile = new File(sourceDirectory, 
getBasedirRelativePath() + '/' + inputPath);
+                if (sourceFile.exists()) {
+                    return PathTool.getRelativePath(rootDirectory.getPath(), 
sourceFile.getPath());
+                }
+            }
+        }
+        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..20c584f 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.HashSet;
 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> sourceDirectories = new HashSet<>();
 
         public SiteDirectory(File path, boolean editable) {
             this(path, editable, false);
@@ -73,6 +76,37 @@ public class SiteRenderingContext {
         public boolean isSkipDuplicates() {
             return skipDuplicates;
         }
+
+        /**
+         * Add a 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 addAlternativeSourceDirectory(File sourceDirectory) {
+            sourceDirectories.add(sourceDirectory);
+            editable = true;
+        }
+
+        /**
+         * Get the source directories for this site directory. If no 
alternative source directory has been added via {@link 
#addAlternativeSourceDirectory(File)}
+         * the site directory itself 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> getSourceDirectories() {
+            if (!editable) {
+                return Collections.emptyList();
+            }
+            if (sourceDirectories.isEmpty()) {
+                return Collections.singleton(path);
+            } else {
+                return sourceDirectories;
+            }
+        }
     }
 
     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..62c0978 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());
@@ -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..085d01a
--- /dev/null
+++ 
b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DocumentRenderingContextTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+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"));
+    }
+}
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
index 5e8fae5..4b53899 100644
--- 
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
@@ -43,8 +43,9 @@ class RenderingContextTest {
         File baseDir = new File(getBasedir() + 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, "test", document, "", 
"xml", false);
+                new DocumentRenderingContext(baseDir, baseDirRelativePath, 
document, "", "xml", false);
         assertEquals("file.with.dot.in.name.html", 
docRenderingContext.getOutputPath());
         assertEquals(".", docRenderingContext.getRelativePath());
 
@@ -54,17 +55,17 @@ class RenderingContextTest {
         assertEquals(".", docRenderingContext.getRelativePath());
 
         document = "index.xml.vm";
-        docRenderingContext = new DocumentRenderingContext(baseDir, "test", 
document, "", "xml", false);
+        docRenderingContext = new DocumentRenderingContext(baseDir, 
baseDirRelativePath, document, "", "xml", false);
         assertEquals("index.html", docRenderingContext.getOutputPath());
         assertEquals(".", docRenderingContext.getRelativePath());
 
         document = "download.apt.vm";
-        docRenderingContext = new DocumentRenderingContext(baseDir, "test", 
document, "", "apt", false);
+        docRenderingContext = new DocumentRenderingContext(baseDir, 
baseDirRelativePath, document, "", "apt", false);
         assertEquals("download.html", docRenderingContext.getOutputPath());
         assertEquals(".", docRenderingContext.getRelativePath());
 
         document = "path/file.apt";
-        docRenderingContext = new DocumentRenderingContext(baseDir, "test", 
document, "", "apt", false);
+        docRenderingContext = new DocumentRenderingContext(baseDir, 
baseDirRelativePath, document, "", "apt", false);
         assertEquals("path/file.html", docRenderingContext.getOutputPath());
         assertEquals("..", docRenderingContext.getRelativePath());
 

Reply via email to