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"); + }); + } +}