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

kwin pushed a commit to branch feature/support-git-mv
in repository https://gitbox.apache.org/repos/asf/maven-doxia-converter.git

commit 236f93febd44beefb33c709d0931a8b8854b8e1c
Author: Konrad Windszus <k...@apache.org>
AuthorDate: Mon Nov 11 19:13:16 2024 +0100

    [DOXIATOOLS-90] Optionally first rename input to output, commit and then
    replace with converted markup
    
    This allows to keep the git history of converted input files (in case
    those are part of a git repository)
---
 .../java/org/apache/maven/doxia/Converter.java     |   8 ++
 .../org/apache/maven/doxia/DefaultConverter.java   | 104 +++++++++++++++++++--
 .../org/apache/maven/doxia/cli/CLIManager.java     |   6 ++
 .../org/apache/maven/doxia/cli/ConverterCli.java   |  17 +++-
 .../maven/doxia/wrapper/InputFileWrapper.java      |  38 +-------
 .../apache/maven/doxia/DefaultConverterTest.java   |  40 ++++++++
 6 files changed, 170 insertions(+), 43 deletions(-)

diff --git a/src/main/java/org/apache/maven/doxia/Converter.java 
b/src/main/java/org/apache/maven/doxia/Converter.java
index 360632a..63dae08 100644
--- a/src/main/java/org/apache/maven/doxia/Converter.java
+++ b/src/main/java/org/apache/maven/doxia/Converter.java
@@ -29,6 +29,12 @@ import org.apache.maven.doxia.wrapper.OutputStreamWrapper;
  * @author <a href="mailto:vincent.sive...@gmail.com";>Vincent Siveton</a>
  */
 public interface Converter {
+    enum PostProcess {
+        NONE,
+        REMOVE_AFTER_CONVERSION,
+        GIT_MV_INPUT_TO_OUTPUT,
+    }
+
     /**
      * @param input an input file wrapper, not null.
      * @param output an output file wrapper, not null.
@@ -55,4 +61,6 @@ public interface Converter {
      * @param formatOutput <code>true</code> to format the generated files, 
<code>false</code> otherwise.
      */
     void setFormatOutput(boolean formatOutput);
+
+    void setPostProcess(PostProcess postProcess);
 }
diff --git a/src/main/java/org/apache/maven/doxia/DefaultConverter.java 
b/src/main/java/org/apache/maven/doxia/DefaultConverter.java
index 250d968..033a8ce 100644
--- a/src/main/java/org/apache/maven/doxia/DefaultConverter.java
+++ b/src/main/java/org/apache/maven/doxia/DefaultConverter.java
@@ -30,12 +30,16 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.Reader;
 import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Scanner;
 
 import com.ibm.icu.text.CharsetDetector;
 import com.ibm.icu.text.CharsetMatch;
@@ -204,6 +208,8 @@ public class DefaultConverter implements Converter {
         }
     }
 
+    private PostProcess postProcess = PostProcess.NONE;
+
     /** Flag to format the generated files, actually only for XML based sinks. 
*/
     private boolean formatOutput;
 
@@ -213,6 +219,9 @@ public class DefaultConverter implements Converter {
     /** SLF4J logger */
     private static final Logger LOGGER = 
LoggerFactory.getLogger(DefaultConverter.class);
 
+    /** Map of temporary output files to their final output files */
+    private Map<Path, Path> outputRenameMap = new HashMap<>();
+
     /** {@inheritDoc} */
     @Override
     public void convert(InputFileWrapper input, OutputFileWrapper output)
@@ -225,11 +234,10 @@ public class DefaultConverter implements Converter {
         } catch (PlexusContainerException e) {
             throw new ConverterException("PlexusContainerException: " + 
e.getMessage(), e);
         }
-
+        outputRenameMap.clear();
         try {
             if (input.getFile().isFile()) {
                 convert(input.getFile(), input.getEncoding(), 
input.getFormat(), output);
-                cleanUp(input, input.getFile());
             } else {
                 List<File> files;
                 try {
@@ -250,20 +258,87 @@ public class DefaultConverter implements Converter {
                     File relativeOutputDirectory = new File(
                             
PathTool.getRelativeFilePath(input.getFile().getAbsolutePath(), f.getParent()));
                     convert(f, input.getEncoding(), input.getFormat(), output, 
relativeOutputDirectory);
-                    cleanUp(input, f);
                 }
             }
+            try {
+                postProcessAllFiles(output.getFormat());
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new ConverterException("Error post processing all files: 
" + e.getMessage(), e);
+            } catch (IOException e) {
+                throw new ConverterException("Error post processing all files: 
" + e.getMessage(), e);
+            }
         } finally {
             stopPlexusContainer();
         }
     }
 
-    private void cleanUp(InputFileWrapper input, File f) {
-        if (input.cleanUp(f)) {
-            LOGGER.info("Removed input file \"{}\" after successful 
conversion", f);
+    private void postProcessFile(File inputFile, File outputFile) throws 
IOException, InterruptedException {
+        switch (postProcess) {
+            case REMOVE_AFTER_CONVERSION:
+                Files.delete(inputFile.toPath());
+                LOGGER.info("Removed input file \"{}\" after successful 
conversion", inputFile);
+                break;
+            case GIT_MV_INPUT_TO_OUTPUT:
+                // first move rename output file to tmp file name
+                Path tmpOutputFile = 
outputFile.toPath().resolveSibling(outputFile.getName() + ".tmp");
+                Files.move(outputFile.toPath(), tmpOutputFile);
+                LOGGER.info(
+                        "Renamed output file \"{}\" to temp name \"{}\"", 
outputFile.getCanonicalPath(), tmpOutputFile.getFileName());
+                // rename all input files to have the proper extension (must 
be individually committed)
+                executeCommand("git", "mv", inputFile.getCanonicalPath(), 
outputFile.getCanonicalPath());
+                LOGGER.info(
+                        "Moved input file \"{}\" to output file \"{}\" 
(keeping the old content)",
+                        inputFile.getCanonicalPath(),
+                        outputFile.getCanonicalPath());
+                outputRenameMap.put(tmpOutputFile, 
outputFile.getCanonicalFile().toPath());
+                break;
+            default:
+                break;
+        }
+    }
+
+    private void postProcessAllFiles(DoxiaFormat outputFormat) throws 
IOException, InterruptedException {
+        if (postProcess == PostProcess.GIT_MV_INPUT_TO_OUTPUT) {
+            // first commit the move operation with original contents
+            executeCommand(
+                    "git",
+                    "commit",
+                    "-m",
+                    String.format("Move to match target converter format %s 
with doxia-converter", outputFormat));
+            for (Map.Entry<Path, Path> entry : outputRenameMap.entrySet()) {
+                // move back the converted file to the original output name 
(i.e. overwrite its old content)
+                Files.move(entry.getKey(), entry.getValue(), 
StandardCopyOption.REPLACE_EXISTING);
+                LOGGER.info(
+                        "Replaced output file \"{}\" with converted file 
\"{}\"",
+                        entry.getValue(),
+                        entry.getKey());
+            }
+        }
+    }
+
+    static void executeCommand(String... commandAndArgs) throws IOException, 
InterruptedException {
+        Process process = Runtime.getRuntime().exec(commandAndArgs);
+        int exitCode = process.waitFor();
+        if (exitCode != 0) {
+            logOutput(process.getInputStream(), "");
+            logOutput(process.getErrorStream(), "Error: ");
+            throw new IOException("Command " + String.join(" ", 
commandAndArgs) + " failed with exit code " + exitCode);
         }
     }
 
+    static void logOutput(InputStream inputStream, String prefix) throws 
InterruptedException {
+        Thread t = new Thread(() -> {
+            Scanner scanner = new Scanner(inputStream, "UTF-8");
+            while (scanner.hasNextLine()) {
+                LOGGER.error("{}{}", prefix, scanner.nextLine());
+            }
+            scanner.close();
+        });
+        t.start();
+        t.join();
+    }
+
     /** {@inheritDoc} */
     @Override
     public void convert(InputReaderWrapper input, OutputStreamWrapper output)
@@ -313,6 +388,11 @@ public class DefaultConverter implements Converter {
         this.formatOutput = formatOutput;
     }
 
+    @Override
+    public void setPostProcess(PostProcess postProcess) {
+        this.postProcess = postProcess;
+    }
+
     // ----------------------------------------------------------------------
     // Private methods
     // ----------------------------------------------------------------------
@@ -336,10 +416,11 @@ public class DefaultConverter implements Converter {
      * @param parserFormat  a not null supported format
      * @param output not null OutputFileWrapper object
      * @param relativeOutputDirectory the relative output directory (may be 
null, created if it does not exist yet)
+     * @return the output file
      * @throws ConverterException if any
      * @throws UnsupportedFormatException if any
      */
-    private void convert(
+    private File convert(
             File inputFile,
             String inputEncoding,
             DoxiaFormat parserFormat,
@@ -438,6 +519,15 @@ public class DefaultConverter implements Converter {
                 "Successfully converted file \"{}\" to \"{}\"",
                 inputFile.getAbsolutePath(),
                 outputFile.getAbsolutePath());
+        try {
+            postProcessFile(inputFile, outputFile);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new ConverterException("Error post processing files: " + 
e.getMessage(), e);
+        } catch (IOException e) {
+            throw new ConverterException("Error post processing files: " + 
e.getMessage(), e);
+        }
+        return outputFile;
     }
 
     /**
diff --git a/src/main/java/org/apache/maven/doxia/cli/CLIManager.java 
b/src/main/java/org/apache/maven/doxia/cli/CLIManager.java
index e8a0759..603a3c6 100644
--- a/src/main/java/org/apache/maven/doxia/cli/CLIManager.java
+++ b/src/main/java/org/apache/maven/doxia/cli/CLIManager.java
@@ -49,6 +49,8 @@ class CLIManager {
 
     static final String REMOVE_IN = "removeIn";
 
+    static final String GIT_MV_INPUT_TO_OUTPUT = "gitMvInputToOutput";
+
     /** out String */
     static final String OUT = "out";
 
@@ -99,6 +101,10 @@ class CLIManager {
                 .longOpt("removeInputAfterConversion")
                 .desc("Whether to remove the input file(s) after successful 
conversion")
                 .build());
+        OPTIONS.addOption(Option.builder(GIT_MV_INPUT_TO_OUTPUT)
+                .desc(
+                        "When this flag is set the input file(s) are first 
moved to the output file(s), then committed and afterwards replaced with 
conversion result to keep the Git history")
+                .build());
         OPTIONS.addOption(Option.builder(OUT)
                 .longOpt("output")
                 .desc("Output file or directory.")
diff --git a/src/main/java/org/apache/maven/doxia/cli/ConverterCli.java 
b/src/main/java/org/apache/maven/doxia/cli/ConverterCli.java
index 3ad605b..0cebc1b 100644
--- a/src/main/java/org/apache/maven/doxia/cli/ConverterCli.java
+++ b/src/main/java/org/apache/maven/doxia/cli/ConverterCli.java
@@ -31,6 +31,7 @@ import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.ParseException;
 import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.maven.doxia.Converter;
+import org.apache.maven.doxia.Converter.PostProcess;
 import org.apache.maven.doxia.ConverterException;
 import org.apache.maven.doxia.DefaultConverter;
 import org.apache.maven.doxia.UnsupportedFormatException;
@@ -114,6 +115,7 @@ public class ConverterCli {
 
         InputFileWrapper input;
         OutputFileWrapper output;
+        final PostProcess postProcess;
         try {
             String sourceFormat = commandLine.getOptionValue(CLIManager.FROM, 
CLIManager.AUTO_FORMAT);
             final DefaultConverter.DoxiaFormat parserFormat;
@@ -127,13 +129,23 @@ public class ConverterCli {
                 parserFormat = 
DefaultConverter.DoxiaFormat.valueOf(sourceFormat.toUpperCase());
             }
             String targetFormat = commandLine.getOptionValue(CLIManager.TO);
+            if (commandLine.hasOption(CLIManager.REMOVE_IN)
+                    && 
commandLine.hasOption(CLIManager.GIT_MV_INPUT_TO_OUTPUT)) {
+                throw new IllegalArgumentException(
+                        "Options 'removeIn' and 'gitMvInputToOutput' are 
mutually exclusive.");
+            } else if (commandLine.hasOption(CLIManager.REMOVE_IN)) {
+                postProcess = PostProcess.REMOVE_AFTER_CONVERSION;
+            } else if 
(commandLine.hasOption(CLIManager.GIT_MV_INPUT_TO_OUTPUT)) {
+                postProcess = PostProcess.GIT_MV_INPUT_TO_OUTPUT;
+            } else {
+                postProcess = PostProcess.NONE;
+            }
             final DefaultConverter.DoxiaFormat sinkFormat =
                     
DefaultConverter.DoxiaFormat.valueOf(targetFormat.toUpperCase());
             input = InputFileWrapper.valueOf(
                     commandLine.getOptionValue(CLIManager.IN),
                     parserFormat,
-                    commandLine.getOptionValue(CLIManager.INENCODING),
-                    commandLine.hasOption(CLIManager.REMOVE_IN));
+                    commandLine.getOptionValue(CLIManager.INENCODING));
             output = OutputFileWrapper.valueOf(
                     commandLine.getOptionValue(CLIManager.OUT),
                     sinkFormat,
@@ -152,6 +164,7 @@ public class ConverterCli {
 
         boolean format = commandLine.hasOption(CLIManager.FORMAT);
         converter.setFormatOutput(format);
+        converter.setPostProcess(postProcess);
 
         try {
             converter.convert(input, output);
diff --git a/src/main/java/org/apache/maven/doxia/wrapper/InputFileWrapper.java 
b/src/main/java/org/apache/maven/doxia/wrapper/InputFileWrapper.java
index 2b20c1c..a0dab50 100644
--- a/src/main/java/org/apache/maven/doxia/wrapper/InputFileWrapper.java
+++ b/src/main/java/org/apache/maven/doxia/wrapper/InputFileWrapper.java
@@ -18,7 +18,6 @@
  */
 package org.apache.maven.doxia.wrapper;
 
-import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.UnsupportedEncodingException;
 
@@ -36,11 +35,6 @@ public class InputFileWrapper extends AbstractFileWrapper {
 
     private final DefaultConverter.DoxiaFormat format;
 
-    /**
-     * If true, the related input file(s) will be removed after the conversion.
-     */
-    private final boolean removeAfterConversion;
-
     /**
      * Private constructor.
      *
@@ -50,13 +44,11 @@ public class InputFileWrapper extends AbstractFileWrapper {
      * @throws UnsupportedEncodingException if the encoding is unsupported.
      * @throws FileNotFoundException if the file for absolutePath is not found.
      */
-    private InputFileWrapper(
-            String absolutePath, DefaultConverter.DoxiaFormat format, String 
charsetName, boolean removeAfterConversion)
+    private InputFileWrapper(String absolutePath, DefaultConverter.DoxiaFormat 
format, String charsetName)
             throws UnsupportedEncodingException, FileNotFoundException {
         super(absolutePath, charsetName);
 
         this.format = format;
-        this.removeAfterConversion = removeAfterConversion;
         if (!getFile().exists()) {
             throw new FileNotFoundException("The file '" + 
getFile().getAbsolutePath() + "' doesn't exist.");
         }
@@ -72,42 +64,20 @@ public class InputFileWrapper extends AbstractFileWrapper {
      */
     public static InputFileWrapper valueOf(String absolutePath, 
DefaultConverter.DoxiaFormat format)
             throws UnsupportedEncodingException, FileNotFoundException {
-        return valueOf(absolutePath, format, WriterFactory.UTF_8, false);
+        return valueOf(absolutePath, format, WriterFactory.UTF_8);
     }
 
-    /**
-     * Performs potential clean up operations on the given file
-     * @param file the file to clean up, not null.
-     * @return {@code true} if the file was removed.
-     */
-    public boolean cleanUp(File file) {
-        if (!file.toString().startsWith(getFile().toString())) {
-            throw new IllegalStateException(
-                    "The given file " + file + " is not related to the input 
file: " + getFile());
-        }
-        if (removeAfterConversion) {
-            return file.delete();
-        }
-        return false;
-    }
-
-    public static InputFileWrapper valueOf(String absolutePath, 
DefaultConverter.DoxiaFormat format, String charsetName)
-            throws UnsupportedEncodingException, FileNotFoundException {
-        return valueOf(absolutePath, format, charsetName, false);
-    }
     /**
      * @param absolutePath for a wanted file or a wanted directory, not null.
      * @param format not null
      * @param charsetName could be null
-     * @param removeAfterConversion if true, the file will be removed after 
the conversion
      * @return a type safe input reader
      * @throws UnsupportedEncodingException if the encoding is unsupported.
      * @throws FileNotFoundException if the file for absolutePath is not found.
      */
-    public static InputFileWrapper valueOf(
-            String absolutePath, DefaultConverter.DoxiaFormat format, String 
charsetName, boolean removeAfterConversion)
+    public static InputFileWrapper valueOf(String absolutePath, 
DefaultConverter.DoxiaFormat format, String charsetName)
             throws UnsupportedEncodingException, FileNotFoundException {
-        return new InputFileWrapper(absolutePath, format, charsetName, 
removeAfterConversion);
+        return new InputFileWrapper(absolutePath, format, charsetName);
     }
 
     public DefaultConverter.DoxiaFormat getFormat() {
diff --git a/src/test/java/org/apache/maven/doxia/DefaultConverterTest.java 
b/src/test/java/org/apache/maven/doxia/DefaultConverterTest.java
new file mode 100644
index 0000000..8b785fd
--- /dev/null
+++ b/src/test/java/org/apache/maven/doxia/DefaultConverterTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class DefaultConverterTest {
+
+    @Test
+    void testExecuteCommand() throws IOException, InterruptedException {
+        DefaultConverter.executeCommand("git", "version");
+    }
+
+    @Test
+    void testExecuteInvalidCommand() throws IOException, InterruptedException {
+        assertThrows(IOException.class, () -> {
+            DefaultConverter.executeCommand("invalid command");
+        });
+    }
+}

Reply via email to