From 382071fb5db59ffb903059bc49b65a0730bc06fa Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Mon, 9 Jun 2025 22:40:12 -0300 Subject: [PATCH 01/15] feat: add method to convert Cygwin-style paths to Windows format Created the method convertCygwinPathToWindows in the FileUtil class. It transforms a file path from Cygwin-style to Windows-style only if the operating system is Windows. The method follows this logic: 1. If the filePath is null or the OS is not Windows, return it unchanged. 2. If the path starts with /cygdrive/{drive} or /{drive}, replace it with {driveLetterUpperCase}:\ and convert forward slashes to backslashes. Based on research, the two common Cygwin-style formats are: - /cygdrive/{drive}/some/path - /c/some/path If none of theses patters match, or if the OS is not Windows, the original path is returned. Additionally, unit tests for this method were added to FileUtilTest class OBS: I need help to run the second test when Windows is disabled... I am getting an error because the test is not recognized... --- .../org/jabref/logic/util/io/FileUtil.java | 31 +++++++++++++++++++ .../jabref/logic/util/io/FileUtilTest.java | 15 +++++++++ 2 files changed, 46 insertions(+) diff --git a/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java b/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java index 9093acfe570..13762836533 100644 --- a/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java +++ b/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java @@ -25,6 +25,7 @@ import org.jabref.logic.FilePreferences; import org.jabref.logic.citationkeypattern.BracketedPattern; import org.jabref.logic.layout.format.RemoveLatexCommandsFormatter; +import org.jabref.logic.os.OS; import org.jabref.logic.util.StandardFileType; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; @@ -589,4 +590,34 @@ public static String shortenFileName(String fileName, Integer maxLength) { public static boolean isCharLegal(char c) { return Arrays.binarySearch(ILLEGAL_CHARS, c) < 0; } + + /** + * Converts a Cygwin-style file path to a Windows-style path, if the operating system is Windows. + * + * Supported formats: + * - /cygdrive/c/Users/... → C:\Users\... + * - /c/Users/... → C:\Users\... + * + * @param filePath the input file path + * @return the converted path if running on Windows and path is in Cygwin format; otherwise, returns the original path + */ + public static String convertCygwinPathToWindows(String filePath) { + if (!OS.WINDOWS || filePath == null) { + return filePath; + } + + if (filePath.startsWith("/cygdrive/") && filePath.length() > 10) { + String driveLetter = filePath.substring(10, 11).toUpperCase(); + String path = filePath.substring(11).replace("/", "\\\\"); + return driveLetter + ":" + path; + } + + if (filePath.matches("^/[a-zA-Z]/.*")) { + String driveLetter = filePath.substring(1, 2).toUpperCase(); + String path = filePath.substring(2).replace("/", "\\\\"); + return driveLetter + ":" + path; + } + + return filePath; + } } diff --git a/jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java b/jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java index 6d63a6bd98f..33e77480c89 100644 --- a/jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java +++ b/jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -518,4 +519,18 @@ void illegalPaths(String fileName) { void shortenFileName(String expected, String fileName, Integer maxLength) { assertEquals(expected, FileUtil.shortenFileName(fileName, maxLength)); } + + @EnabledOnOs(value = org.junit.jupiter.api.condition.OS.WINDOWS) + @ParameterizedTest + @ValueSource(strings = {"/c/Users/username/Downloads/test.bib", "/cygdrive/c/Users/username/Downloads/test.bib"}) + void convertCygwinPathToWindowsShouldConvertToWindowsFormatWhenRunningOnWindows(String filePath) { + assertEquals("C:\\\\Users\\\\username\\\\Downloads\\\\test.bib", FileUtil.convertCygwinPathToWindows(filePath)); + } + + @DisabledOnOs(value = org.junit.jupiter.api.condition.OS.WINDOWS, disabledReason = "Test in others operational systems") + @ParameterizedTest + @ValueSource(strings = {"/home/username/Downloads/test.bib"}) + void convertCygwinPathToWindowsShouldReturnOriginalFilePathWhenRunningOnWindows(String filePath) { + assertEquals(filePath, FileUtil.convertCygwinPathToWindows(filePath)); + } } From 93087c92517ef442000932e132b0369e9e7afb2b Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Mon, 9 Jun 2025 22:43:03 -0300 Subject: [PATCH 02/15] feat: apply the convertCygwinPathToWinds on the path to prevent the ImportException I need help to understand if I really need to do this change... it was not clear to me on the issue description. (it is my first open source contribuition) --- jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java b/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java index 74fc19fe677..02896e8c53b 100644 --- a/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -84,7 +84,7 @@ protected static Optional importFile(String importArguments, LOGGER.debug("Importing file {}", importArguments); String[] data = importArguments.split(","); - String address = data[0]; + String address = FileUtil.convertCygwinPathToWindows(data[0]); Path file; if (address.startsWith("http://") || address.startsWith("https://") || address.startsWith("ftp://")) { // Download web resource to temporary file From 2f87d705aa79527994b02f83403eaca0fa250f83 Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Mon, 9 Jun 2025 23:42:34 -0300 Subject: [PATCH 03/15] fix: remove FileUtil method Do not know if I need to do that. --- jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java b/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java index 02896e8c53b..74fc19fe677 100644 --- a/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -84,7 +84,7 @@ protected static Optional importFile(String importArguments, LOGGER.debug("Importing file {}", importArguments); String[] data = importArguments.split(","); - String address = FileUtil.convertCygwinPathToWindows(data[0]); + String address = data[0]; Path file; if (address.startsWith("http://") || address.startsWith("https://") || address.startsWith("ftp://")) { // Download web resource to temporary file From c34308667999f05509640aae9550def55afdc1e4 Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Tue, 10 Jun 2025 09:19:35 -0300 Subject: [PATCH 04/15] feat: add Cygwin file path /mtn/ --- .../org/jabref/logic/util/io/FileUtil.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java b/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java index 13762836533..6a92c17b15b 100644 --- a/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java +++ b/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java @@ -591,21 +591,26 @@ public static boolean isCharLegal(char c) { return Arrays.binarySearch(ILLEGAL_CHARS, c) < 0; } - /** - * Converts a Cygwin-style file path to a Windows-style path, if the operating system is Windows. - * - * Supported formats: - * - /cygdrive/c/Users/... → C:\Users\... - * - /c/Users/... → C:\Users\... - * - * @param filePath the input file path - * @return the converted path if running on Windows and path is in Cygwin format; otherwise, returns the original path - */ + /// Converts a Cygwin-style file path to a Windows-style path if the operating system is Windows. + /// + /// Supported formats: + /// - /cygdrive/c/Users/... → C:\Users\... + /// - /mnt/c/Users/... → C:\Users\... + /// - /c/Users/... → C:\Users\... + /// + /// @param filePath the input file path + /// @return the converted path if running on Windows and path is in Cygwin format; otherwise, returns the original path public static String convertCygwinPathToWindows(String filePath) { if (!OS.WINDOWS || filePath == null) { return filePath; } + if (filePath.startsWith("/mnt/") && filePath.length() > 5) { + String driveLetter = filePath.substring(5, 6).toUpperCase(); + String path = filePath.substring(6).replace("/", "\\\\"); + return driveLetter + ":" + path; + } + if (filePath.startsWith("/cygdrive/") && filePath.length() > 10) { String driveLetter = filePath.substring(10, 11).toUpperCase(); String path = filePath.substring(11).replace("/", "\\\\"); From 773a4ebf79e6290f20fb29962ff3bd5be0302533 Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Tue, 10 Jun 2025 09:20:00 -0300 Subject: [PATCH 05/15] feat: add test to Cygwin file path /mtn/ --- .../src/test/java/org/jabref/logic/util/io/FileUtilTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java b/jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java index 33e77480c89..2960ad99a87 100644 --- a/jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java +++ b/jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java @@ -522,7 +522,9 @@ void shortenFileName(String expected, String fileName, Integer maxLength) { @EnabledOnOs(value = org.junit.jupiter.api.condition.OS.WINDOWS) @ParameterizedTest - @ValueSource(strings = {"/c/Users/username/Downloads/test.bib", "/cygdrive/c/Users/username/Downloads/test.bib"}) + @ValueSource(strings = {"/c/Users/username/Downloads/test.bib", + "/cygdrive/c/Users/username/Downloads/test.bib", + "/mnt/c/Users/username/Downloads/test.bib"}) void convertCygwinPathToWindowsShouldConvertToWindowsFormatWhenRunningOnWindows(String filePath) { assertEquals("C:\\\\Users\\\\username\\\\Downloads\\\\test.bib", FileUtil.convertCygwinPathToWindows(filePath)); } From 2334c9e01f13ea07b276ddb4298e8a6411264783 Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Tue, 10 Jun 2025 13:01:10 -0300 Subject: [PATCH 06/15] feat: add entry on CHANGELOG.md and weave the method into JabKit. --- CHANGELOG.md | 1 + jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d171dc7e6ad..b4b716e7550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added - We introduced a settings parameters to manage citations' relations local storage time-to-live with a default value set to 30 days. [#11189](https://github.com/JabRef/jabref/issues/11189) +- We added support for Cygwin-file paths on a Windows Operating System. [#13274](https://github.com/JabRef/jabref/issues/13274) ### Changed diff --git a/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java b/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java index 74fc19fe677..02896e8c53b 100644 --- a/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -84,7 +84,7 @@ protected static Optional importFile(String importArguments, LOGGER.debug("Importing file {}", importArguments); String[] data = importArguments.split(","); - String address = data[0]; + String address = FileUtil.convertCygwinPathToWindows(data[0]); Path file; if (address.startsWith("http://") || address.startsWith("https://") || address.startsWith("ftp://")) { // Download web resource to temporary file From 37c6203b03cc4265d9f321bf9ef1248593309e48 Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Wed, 11 Jun 2025 22:37:56 -0300 Subject: [PATCH 07/15] feat: remove convertCygwinPathToWindows --- jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java b/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java index 02896e8c53b..74fc19fe677 100644 --- a/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -84,7 +84,7 @@ protected static Optional importFile(String importArguments, LOGGER.debug("Importing file {}", importArguments); String[] data = importArguments.split(","); - String address = FileUtil.convertCygwinPathToWindows(data[0]); + String address = data[0]; Path file; if (address.startsWith("http://") || address.startsWith("https://") || address.startsWith("ftp://")) { // Download web resource to temporary file From 19d92f39cfa197b5352c9bef9c2db69b1faff921 Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Wed, 11 Jun 2025 22:48:25 -0300 Subject: [PATCH 08/15] feat: create converters and add new package to java modules --- jabkit/src/main/java/module-info.java | 3 ++- .../cli/converter/FilePathConverter.java | 17 +++++++++++++++++ .../jabref/cli/converter/PathConverter.java | 18 ++++++++++++++++++ .../cli/converter/StringPathConverter.java | 15 +++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 jabkit/src/main/java/org/jabref/cli/converter/FilePathConverter.java create mode 100644 jabkit/src/main/java/org/jabref/cli/converter/PathConverter.java create mode 100644 jabkit/src/main/java/org/jabref/cli/converter/StringPathConverter.java diff --git a/jabkit/src/main/java/module-info.java b/jabkit/src/main/java/module-info.java index 68a32d97644..0aa1ec226e3 100644 --- a/jabkit/src/main/java/module-info.java +++ b/jabkit/src/main/java/module-info.java @@ -3,6 +3,7 @@ requires info.picocli; opens org.jabref.cli; + opens org.jabref.cli.converter; requires transitive org.jspecify; requires java.prefs; @@ -22,7 +23,7 @@ requires org.tinylog.impl; requires java.xml; - + // region: other libraries (alphabetically) requires io.github.adr; // endregion diff --git a/jabkit/src/main/java/org/jabref/cli/converter/FilePathConverter.java b/jabkit/src/main/java/org/jabref/cli/converter/FilePathConverter.java new file mode 100644 index 00000000000..e67e7c13585 --- /dev/null +++ b/jabkit/src/main/java/org/jabref/cli/converter/FilePathConverter.java @@ -0,0 +1,17 @@ +package org.jabref.cli.converter; + +import java.io.File; + +import org.jabref.logic.util.io.FileUtil; + +import picocli.CommandLine; + +/// Converts Cygwin-style paths to File objects using platform-specific formatting. +public class FilePathConverter implements CommandLine.ITypeConverter { + + @Override + public File convert(String filePath) { + String normalizedPath = FileUtil.convertCygwinPathToWindows(filePath); + return new File(normalizedPath); + } +} diff --git a/jabkit/src/main/java/org/jabref/cli/converter/PathConverter.java b/jabkit/src/main/java/org/jabref/cli/converter/PathConverter.java new file mode 100644 index 00000000000..f6d36eae9f2 --- /dev/null +++ b/jabkit/src/main/java/org/jabref/cli/converter/PathConverter.java @@ -0,0 +1,18 @@ +package org.jabref.cli.converter; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.jabref.logic.util.io.FileUtil; + +import picocli.CommandLine; + +/// Converts Cygwin-style paths to Path objects using platform-specific formatting. +public class PathConverter implements CommandLine.ITypeConverter { + + @Override + public Path convert(String value) { + String normalizedPath = FileUtil.convertCygwinPathToWindows(value); + return Paths.get(normalizedPath); + } +} diff --git a/jabkit/src/main/java/org/jabref/cli/converter/StringPathConverter.java b/jabkit/src/main/java/org/jabref/cli/converter/StringPathConverter.java new file mode 100644 index 00000000000..17f7c249eba --- /dev/null +++ b/jabkit/src/main/java/org/jabref/cli/converter/StringPathConverter.java @@ -0,0 +1,15 @@ +package org.jabref.cli.converter; + +import org.jabref.logic.util.io.FileUtil; + +import picocli.CommandLine; + + /// Converts Cygwin-style paths to the standard format of the operating system. + /// Especially useful on Windows to handle paths like /c/Users/... -> C:\Users\... +public class StringPathConverter implements CommandLine.ITypeConverter { + + @Override + public String convert(String filePath) { + return FileUtil.convertCygwinPathToWindows(filePath); + } +} From e6aea3bae3af7ea4b547192c1cb5151a8affa6fb Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Wed, 11 Jun 2025 22:48:46 -0300 Subject: [PATCH 09/15] feat: apply converters to --inputs and --outputs --- jabkit/src/main/java/org/jabref/cli/CheckConsistency.java | 3 ++- jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java | 5 +++-- jabkit/src/main/java/org/jabref/cli/Convert.java | 6 ++++-- jabkit/src/main/java/org/jabref/cli/Pseudonymize.java | 5 +++-- jabkit/src/main/java/org/jabref/cli/Search.java | 6 ++++-- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckConsistency.java b/jabkit/src/main/java/org/jabref/cli/CheckConsistency.java index 5afff87aa70..9d38a4968f8 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckConsistency.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckConsistency.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Optional; +import org.jabref.cli.converter.StringPathConverter; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; import org.jabref.logic.quality.consistency.BibliographyConsistencyCheck; @@ -33,7 +34,7 @@ class CheckConsistency implements Runnable { @Mixin private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); - @Option(names = {"--input"}, description = "Input BibTeX file", required = true) + @Option(names = {"--input"}, converter = StringPathConverter.class, description = "Input BibTeX file", required = true) private String inputFile; @Option(names = {"--output-format"}, description = "Output format: txt or csv", defaultValue = "txt") diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index 8f4bef85c42..648a0e5c205 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -2,6 +2,7 @@ import java.io.File; +import org.jabref.cli.converter.FilePathConverter; import org.jabref.logic.l10n.Localization; import static picocli.CommandLine.Command; @@ -15,10 +16,10 @@ class CheckIntegrity implements Runnable { @Mixin private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); - @Parameters(index = "0", description = "BibTeX file to check", arity = "0..1") + @Parameters(index = "0", converter = FilePathConverter.class, description = "BibTeX file to check", arity = "0..1") private File inputFile; - @Option(names = {"--input"}, description = "Input BibTeX file") + @Option(names = {"--input"}, converter = FilePathConverter.class, description = "Input BibTeX file") private File inputOption; @Option(names = {"--output-format"}, description = "Output format: txt or csv") diff --git a/jabkit/src/main/java/org/jabref/cli/Convert.java b/jabkit/src/main/java/org/jabref/cli/Convert.java index 6071d5076c6..ba4954aa9fa 100644 --- a/jabkit/src/main/java/org/jabref/cli/Convert.java +++ b/jabkit/src/main/java/org/jabref/cli/Convert.java @@ -8,6 +8,8 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; +import org.jabref.cli.converter.PathConverter; +import org.jabref.cli.converter.StringPathConverter; import org.jabref.logic.exporter.Exporter; import org.jabref.logic.exporter.ExporterFactory; import org.jabref.logic.exporter.SaveException; @@ -36,13 +38,13 @@ public class Convert implements Runnable { @Mixin private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); - @Option(names = {"--input"}, description = "Input file", required = true) + @Option(names = {"--input"}, converter = StringPathConverter.class, description = "Input file", required = true) private String inputFile; @Option(names = {"--input-format"}, description = "Input format") private String inputFormat; - @Option(names = {"--output"}, description = "Output file") + @Option(names = {"--output"}, converter = PathConverter.class, description = "Output file") private Path outputFile; @Option(names = {"--output-format"}, description = "Output format") diff --git a/jabkit/src/main/java/org/jabref/cli/Pseudonymize.java b/jabkit/src/main/java/org/jabref/cli/Pseudonymize.java index 22146798821..5e8cfd0fed7 100644 --- a/jabkit/src/main/java/org/jabref/cli/Pseudonymize.java +++ b/jabkit/src/main/java/org/jabref/cli/Pseudonymize.java @@ -5,6 +5,7 @@ import java.nio.file.Path; import java.util.Optional; +import org.jabref.cli.converter.StringPathConverter; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; import org.jabref.logic.pseudonymization.Pseudonymization; @@ -34,10 +35,10 @@ public class Pseudonymize implements Runnable { private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); @ADR(45) - @Option(names = {"--input"}, description = "BibTeX file to be pseudonymized", required = true) + @Option(names = {"--input"}, converter = StringPathConverter.class, description = "BibTeX file to be pseudonymized", required = true) private String inputFile; - @Option(names = {"--output"}, description = "Output pseudo-bib file") + @Option(names = {"--output"}, converter = StringPathConverter.class, description = "Output pseudo-bib file") private String outputFile; @Option(names = {"--key"}, description = "Output pseudo-keys file") diff --git a/jabkit/src/main/java/org/jabref/cli/Search.java b/jabkit/src/main/java/org/jabref/cli/Search.java index 4be5fba1a02..5999c0e0e5a 100644 --- a/jabkit/src/main/java/org/jabref/cli/Search.java +++ b/jabkit/src/main/java/org/jabref/cli/Search.java @@ -8,6 +8,8 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; +import org.jabref.cli.converter.PathConverter; +import org.jabref.cli.converter.StringPathConverter; import org.jabref.logic.exporter.Exporter; import org.jabref.logic.exporter.ExporterFactory; import org.jabref.logic.exporter.SaveException; @@ -46,10 +48,10 @@ class Search implements Runnable { @Option(names = {"--query"}, description = "Search query", required = true) private String query; - @Option(names = {"--input"}, description = "Input BibTeX file", required = true) + @Option(names = {"--input"}, converter = StringPathConverter.class, description = "Input BibTeX file", required = true) private String inputFile; - @Option(names = {"--output"}, description = "Output file") + @Option(names = {"--output"}, converter = PathConverter.class, description = "Output file") private Path outputFile; @Option(names = {"--output-format"}, description = "Output format: bib, txt, etc.") From 025a2eb6f4adb2d2cade1f8ff23d6ce13b3b8da2 Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Wed, 11 Jun 2025 22:50:40 -0300 Subject: [PATCH 10/15] style: remove blank space --- jabkit/src/main/java/module-info.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jabkit/src/main/java/module-info.java b/jabkit/src/main/java/module-info.java index 0aa1ec226e3..685c3567b56 100644 --- a/jabkit/src/main/java/module-info.java +++ b/jabkit/src/main/java/module-info.java @@ -21,7 +21,6 @@ requires org.tinylog.api; requires org.tinylog.api.slf4j; requires org.tinylog.impl; - requires java.xml; // region: other libraries (alphabetically) From 249f38fc429b13e24d94baf9f70a3f9a9a584579 Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Fri, 13 Jun 2025 21:51:21 -0300 Subject: [PATCH 11/15] feat: change the DataType from String and File to Path --- .../java/org/jabref/cli/CheckConsistency.java | 7 ++++--- .../java/org/jabref/cli/CheckIntegrity.java | 12 +++++------ .../src/main/java/org/jabref/cli/Convert.java | 9 ++++---- .../java/org/jabref/cli/Pseudonymize.java | 21 +++++++++---------- .../src/main/java/org/jabref/cli/Search.java | 9 ++++---- 5 files changed, 28 insertions(+), 30 deletions(-) diff --git a/jabkit/src/main/java/org/jabref/cli/CheckConsistency.java b/jabkit/src/main/java/org/jabref/cli/CheckConsistency.java index 9d38a4968f8..e0d0d364729 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckConsistency.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckConsistency.java @@ -3,10 +3,11 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; +import java.nio.file.Path; import java.util.List; import java.util.Optional; -import org.jabref.cli.converter.StringPathConverter; +import org.jabref.cli.converter.CygWinPathConverter; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; import org.jabref.logic.quality.consistency.BibliographyConsistencyCheck; @@ -34,8 +35,8 @@ class CheckConsistency implements Runnable { @Mixin private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); - @Option(names = {"--input"}, converter = StringPathConverter.class, description = "Input BibTeX file", required = true) - private String inputFile; + @Option(names = {"--input"}, converter = CygWinPathConverter.class, description = "Input BibTeX file", required = true) + private Path inputFile; @Option(names = {"--output-format"}, description = "Output format: txt or csv", defaultValue = "txt") private String outputFormat; diff --git a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java index 648a0e5c205..7186de34f56 100644 --- a/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java +++ b/jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java @@ -1,8 +1,8 @@ package org.jabref.cli; -import java.io.File; +import java.nio.file.Path; -import org.jabref.cli.converter.FilePathConverter; +import org.jabref.cli.converter.CygWinPathConverter; import org.jabref.logic.l10n.Localization; import static picocli.CommandLine.Command; @@ -16,11 +16,11 @@ class CheckIntegrity implements Runnable { @Mixin private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); - @Parameters(index = "0", converter = FilePathConverter.class, description = "BibTeX file to check", arity = "0..1") - private File inputFile; + @Parameters(index = "0", converter = CygWinPathConverter.class, description = "BibTeX file to check", arity = "0..1") + private Path inputFile; - @Option(names = {"--input"}, converter = FilePathConverter.class, description = "Input BibTeX file") - private File inputOption; + @Option(names = {"--input"}, converter = CygWinPathConverter.class, description = "Input BibTeX file") + private Path inputOption; @Option(names = {"--output-format"}, description = "Output format: txt or csv") private String outputFormat = "txt"; // FixMe: Default value? diff --git a/jabkit/src/main/java/org/jabref/cli/Convert.java b/jabkit/src/main/java/org/jabref/cli/Convert.java index ba4954aa9fa..f2f2700462b 100644 --- a/jabkit/src/main/java/org/jabref/cli/Convert.java +++ b/jabkit/src/main/java/org/jabref/cli/Convert.java @@ -8,8 +8,7 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; -import org.jabref.cli.converter.PathConverter; -import org.jabref.cli.converter.StringPathConverter; +import org.jabref.cli.converter.CygWinPathConverter; import org.jabref.logic.exporter.Exporter; import org.jabref.logic.exporter.ExporterFactory; import org.jabref.logic.exporter.SaveException; @@ -38,13 +37,13 @@ public class Convert implements Runnable { @Mixin private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); - @Option(names = {"--input"}, converter = StringPathConverter.class, description = "Input file", required = true) - private String inputFile; + @Option(names = {"--input"}, converter = CygWinPathConverter.class, description = "Input file", required = true) + private Path inputFile; @Option(names = {"--input-format"}, description = "Input format") private String inputFormat; - @Option(names = {"--output"}, converter = PathConverter.class, description = "Output file") + @Option(names = {"--output"}, converter = CygWinPathConverter.class, description = "Output file") private Path outputFile; @Option(names = {"--output-format"}, description = "Output format") diff --git a/jabkit/src/main/java/org/jabref/cli/Pseudonymize.java b/jabkit/src/main/java/org/jabref/cli/Pseudonymize.java index 5e8cfd0fed7..ffda758c648 100644 --- a/jabkit/src/main/java/org/jabref/cli/Pseudonymize.java +++ b/jabkit/src/main/java/org/jabref/cli/Pseudonymize.java @@ -5,7 +5,7 @@ import java.nio.file.Path; import java.util.Optional; -import org.jabref.cli.converter.StringPathConverter; +import org.jabref.cli.converter.CygWinPathConverter; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; import org.jabref.logic.pseudonymization.Pseudonymization; @@ -35,11 +35,11 @@ public class Pseudonymize implements Runnable { private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions(); @ADR(45) - @Option(names = {"--input"}, converter = StringPathConverter.class, description = "BibTeX file to be pseudonymized", required = true) - private String inputFile; + @Option(names = {"--input"}, converter = CygWinPathConverter.class, description = "BibTeX file to be pseudonymized", required = true) + private Path inputPath; - @Option(names = {"--output"}, converter = StringPathConverter.class, description = "Output pseudo-bib file") - private String outputFile; + @Option(names = {"--output"}, converter = CygWinPathConverter.class, description = "Output pseudo-bib file") + private Path outputFile; @Option(names = {"--key"}, description = "Output pseudo-keys file") private String keyFile; @@ -49,24 +49,23 @@ public class Pseudonymize implements Runnable { @Override public void run() { - Path inputPath = Path.of(inputFile); - String fileName = FileUtil.getBaseName(inputFile); - Path pseudoBibPath = resolveOutputPath(outputFile, inputPath, fileName + PSEUDO_SUFFIX + BIB_EXTENSION); + String fileName = FileUtil.getBaseName(inputPath); + Path pseudoBibPath = resolveOutputPath(outputFile.toString(), inputPath, fileName + PSEUDO_SUFFIX + BIB_EXTENSION); Path pseudoKeyPath = resolveOutputPath(keyFile, inputPath, fileName + PSEUDO_SUFFIX + CSV_EXTENSION); Optional parserResult = ArgumentProcessor.importFile( - inputFile, + inputPath, "bibtex", argumentProcessor.cliPreferences, sharedOptions.porcelain); if (parserResult.isEmpty()) { - System.out.println(Localization.lang("Unable to open file '%0'.", inputFile)); + System.out.println(Localization.lang("Unable to open file '%0'.", inputPath)); return; } if (parserResult.get().isInvalid()) { - System.out.println(Localization.lang("Input file '%0' is invalid and could not be parsed.", inputFile)); + System.out.println(Localization.lang("Input file '%0' is invalid and could not be parsed.", inputPath)); return; } diff --git a/jabkit/src/main/java/org/jabref/cli/Search.java b/jabkit/src/main/java/org/jabref/cli/Search.java index 5999c0e0e5a..1b77e4f7592 100644 --- a/jabkit/src/main/java/org/jabref/cli/Search.java +++ b/jabkit/src/main/java/org/jabref/cli/Search.java @@ -8,8 +8,7 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; -import org.jabref.cli.converter.PathConverter; -import org.jabref.cli.converter.StringPathConverter; +import org.jabref.cli.converter.CygWinPathConverter; import org.jabref.logic.exporter.Exporter; import org.jabref.logic.exporter.ExporterFactory; import org.jabref.logic.exporter.SaveException; @@ -48,10 +47,10 @@ class Search implements Runnable { @Option(names = {"--query"}, description = "Search query", required = true) private String query; - @Option(names = {"--input"}, converter = StringPathConverter.class, description = "Input BibTeX file", required = true) - private String inputFile; + @Option(names = {"--input"}, converter = CygWinPathConverter.class, description = "Input BibTeX file", required = true) + private Path inputFile; - @Option(names = {"--output"}, converter = PathConverter.class, description = "Output file") + @Option(names = {"--output"}, converter = CygWinPathConverter.class, description = "Output file") private Path outputFile; @Option(names = {"--output-format"}, description = "Output format: bib, txt, etc.") From 5241f3c558e593577c7a2fd023b9795df6a4ddc3 Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Fri, 13 Jun 2025 21:52:20 -0300 Subject: [PATCH 12/15] feat: remove code duplication --- .../cli/converter/CygWinPathConverter.java | 16 ++++++++++++++++ .../cli/converter/FilePathConverter.java | 17 ----------------- .../jabref/cli/converter/PathConverter.java | 18 ------------------ .../cli/converter/StringPathConverter.java | 15 --------------- 4 files changed, 16 insertions(+), 50 deletions(-) create mode 100644 jabkit/src/main/java/org/jabref/cli/converter/CygWinPathConverter.java delete mode 100644 jabkit/src/main/java/org/jabref/cli/converter/FilePathConverter.java delete mode 100644 jabkit/src/main/java/org/jabref/cli/converter/PathConverter.java delete mode 100644 jabkit/src/main/java/org/jabref/cli/converter/StringPathConverter.java diff --git a/jabkit/src/main/java/org/jabref/cli/converter/CygWinPathConverter.java b/jabkit/src/main/java/org/jabref/cli/converter/CygWinPathConverter.java new file mode 100644 index 00000000000..5c242d75ec0 --- /dev/null +++ b/jabkit/src/main/java/org/jabref/cli/converter/CygWinPathConverter.java @@ -0,0 +1,16 @@ +package org.jabref.cli.converter; + +import java.nio.file.Path; + +import org.jabref.logic.util.io.FileUtil; + +import picocli.CommandLine; + +/// Converts Cygwin-style paths to Path objects using platform-specific formatting. +public class CygWinPathConverter implements CommandLine.ITypeConverter { + + @Override + public Path convert(String path) { + return FileUtil.convertCygwinPathToWindows(path); + } +} diff --git a/jabkit/src/main/java/org/jabref/cli/converter/FilePathConverter.java b/jabkit/src/main/java/org/jabref/cli/converter/FilePathConverter.java deleted file mode 100644 index e67e7c13585..00000000000 --- a/jabkit/src/main/java/org/jabref/cli/converter/FilePathConverter.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.jabref.cli.converter; - -import java.io.File; - -import org.jabref.logic.util.io.FileUtil; - -import picocli.CommandLine; - -/// Converts Cygwin-style paths to File objects using platform-specific formatting. -public class FilePathConverter implements CommandLine.ITypeConverter { - - @Override - public File convert(String filePath) { - String normalizedPath = FileUtil.convertCygwinPathToWindows(filePath); - return new File(normalizedPath); - } -} diff --git a/jabkit/src/main/java/org/jabref/cli/converter/PathConverter.java b/jabkit/src/main/java/org/jabref/cli/converter/PathConverter.java deleted file mode 100644 index f6d36eae9f2..00000000000 --- a/jabkit/src/main/java/org/jabref/cli/converter/PathConverter.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.jabref.cli.converter; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.jabref.logic.util.io.FileUtil; - -import picocli.CommandLine; - -/// Converts Cygwin-style paths to Path objects using platform-specific formatting. -public class PathConverter implements CommandLine.ITypeConverter { - - @Override - public Path convert(String value) { - String normalizedPath = FileUtil.convertCygwinPathToWindows(value); - return Paths.get(normalizedPath); - } -} diff --git a/jabkit/src/main/java/org/jabref/cli/converter/StringPathConverter.java b/jabkit/src/main/java/org/jabref/cli/converter/StringPathConverter.java deleted file mode 100644 index 17f7c249eba..00000000000 --- a/jabkit/src/main/java/org/jabref/cli/converter/StringPathConverter.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.jabref.cli.converter; - -import org.jabref.logic.util.io.FileUtil; - -import picocli.CommandLine; - - /// Converts Cygwin-style paths to the standard format of the operating system. - /// Especially useful on Windows to handle paths like /c/Users/... -> C:\Users\... -public class StringPathConverter implements CommandLine.ITypeConverter { - - @Override - public String convert(String filePath) { - return FileUtil.convertCygwinPathToWindows(filePath); - } -} From 385ab8122b952e85d33d269634b5462bd7a58d17 Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Fri, 13 Jun 2025 21:52:47 -0300 Subject: [PATCH 13/15] fix: correct the style --- jabkit/src/main/java/module-info.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabkit/src/main/java/module-info.java b/jabkit/src/main/java/module-info.java index 685c3567b56..58897c8b573 100644 --- a/jabkit/src/main/java/module-info.java +++ b/jabkit/src/main/java/module-info.java @@ -21,8 +21,8 @@ requires org.tinylog.api; requires org.tinylog.api.slf4j; requires org.tinylog.impl; - requires java.xml; + requires java.xml; // region: other libraries (alphabetically) requires io.github.adr; // endregion From 2c40476912485b59cdce7189a4b4a39b04e81779 Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Fri, 13 Jun 2025 21:54:34 -0300 Subject: [PATCH 14/15] feat: create a new method to remove code duplication, create constants that represents the cygwin-paths known and the /c/ pattern --- .../org/jabref/logic/util/io/FileUtil.java | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java b/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java index 6a92c17b15b..b2049156a61 100644 --- a/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java +++ b/jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java @@ -50,6 +50,9 @@ public class FileUtil { private static final String ELLIPSIS = "..."; private static final int ELLIPSIS_LENGTH = ELLIPSIS.length(); private static final RemoveLatexCommandsFormatter REMOVE_LATEX_COMMANDS_FORMATTER = new RemoveLatexCommandsFormatter(); + private static final String CYGDRIVE_PREFIX = "/cygdrive/"; + private static final String MNT_PREFIX = "/mnt/"; + private static final Pattern ROOT_DRIVE_PATTERN = Pattern.compile("^/[a-zA-Z]/.*"); /** * MUST ALWAYS BE A SORTED ARRAY because it is used in a binary search @@ -600,29 +603,37 @@ public static boolean isCharLegal(char c) { /// /// @param filePath the input file path /// @return the converted path if running on Windows and path is in Cygwin format; otherwise, returns the original path - public static String convertCygwinPathToWindows(String filePath) { - if (!OS.WINDOWS || filePath == null) { - return filePath; + public static Path convertCygwinPathToWindows(String filePath) { + if (filePath == null) { + return null; } - if (filePath.startsWith("/mnt/") && filePath.length() > 5) { - String driveLetter = filePath.substring(5, 6).toUpperCase(); - String path = filePath.substring(6).replace("/", "\\\\"); - return driveLetter + ":" + path; + if (!OS.WINDOWS) { + return Path.of(filePath); } - if (filePath.startsWith("/cygdrive/") && filePath.length() > 10) { - String driveLetter = filePath.substring(10, 11).toUpperCase(); - String path = filePath.substring(11).replace("/", "\\\\"); - return driveLetter + ":" + path; + if (filePath.startsWith(MNT_PREFIX) && filePath.length() > 5) { + return buildWindowsPathWithDriveLetterIndex(filePath, 5); } - if (filePath.matches("^/[a-zA-Z]/.*")) { - String driveLetter = filePath.substring(1, 2).toUpperCase(); - String path = filePath.substring(2).replace("/", "\\\\"); - return driveLetter + ":" + path; + if (filePath.startsWith(CYGDRIVE_PREFIX) && filePath.length() > 10) { + return buildWindowsPathWithDriveLetterIndex(filePath, 10); } - return filePath; + if (ROOT_DRIVE_PATTERN.matcher(filePath).matches()) { + return buildWindowsPathWithDriveLetterIndex(filePath, 1); + } + + return Path.of(filePath); + } + + /// Builds a Windows-style path from a Cygwin-style path using a known prefix index. + /// @param path the input file path + /// @param letterIndex the index driver letter, zero-based indexing + /// @return a windows-style path + private static Path buildWindowsPathWithDriveLetterIndex(String path, int letterIndex) { + String driveLetter = path.substring(letterIndex, letterIndex + 1).toUpperCase(); + String windowsPath = path.substring(letterIndex + 1).replace("/", "\\\\"); + return Path.of(driveLetter + ":" + windowsPath); } } From fde43a6e9c37e85d695dacd570448ee2e023c624 Mon Sep 17 00:00:00 2001 From: Vitor Araujo Date: Fri, 13 Jun 2025 21:55:05 -0300 Subject: [PATCH 15/15] feat: correct the unit tests to the changes made in the class --- .../src/test/java/org/jabref/logic/util/io/FileUtilTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java b/jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java index 2960ad99a87..ee3fd7873e5 100644 --- a/jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java +++ b/jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java @@ -526,13 +526,13 @@ void shortenFileName(String expected, String fileName, Integer maxLength) { "/cygdrive/c/Users/username/Downloads/test.bib", "/mnt/c/Users/username/Downloads/test.bib"}) void convertCygwinPathToWindowsShouldConvertToWindowsFormatWhenRunningOnWindows(String filePath) { - assertEquals("C:\\\\Users\\\\username\\\\Downloads\\\\test.bib", FileUtil.convertCygwinPathToWindows(filePath)); + assertEquals(Path.of("C:\\\\Users\\\\username\\\\Downloads\\\\test.bib"), FileUtil.convertCygwinPathToWindows(filePath)); } @DisabledOnOs(value = org.junit.jupiter.api.condition.OS.WINDOWS, disabledReason = "Test in others operational systems") @ParameterizedTest @ValueSource(strings = {"/home/username/Downloads/test.bib"}) void convertCygwinPathToWindowsShouldReturnOriginalFilePathWhenRunningOnWindows(String filePath) { - assertEquals(filePath, FileUtil.convertCygwinPathToWindows(filePath)); + assertEquals(Path.of(filePath), FileUtil.convertCygwinPathToWindows(filePath)); } }