Skip to content

Fixes #13274: Allow cygwin-paths on Windows #13297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
382071f
feat: add method to convert Cygwin-style paths to Windows format
kvitorr Jun 10, 2025
93087c9
feat: apply the convertCygwinPathToWinds on the path to prevent the I…
kvitorr Jun 10, 2025
568bb34
Merge branch 'main' into fix-for-issue-13274
kvitorr Jun 10, 2025
2f87d70
fix: remove FileUtil method
kvitorr Jun 10, 2025
0f5cce4
Merge branch 'fix-for-issue-13274' of https://github.com/kvitorr/jabr…
kvitorr Jun 10, 2025
c343086
feat: add Cygwin file path /mtn/
kvitorr Jun 10, 2025
773a4eb
feat: add test to Cygwin file path /mtn/
kvitorr Jun 10, 2025
2334c9e
feat: add entry on CHANGELOG.md and weave the method into JabKit.
kvitorr Jun 10, 2025
37c6203
feat: remove convertCygwinPathToWindows
kvitorr Jun 12, 2025
19d92f3
feat: create converters and add new package to java modules
kvitorr Jun 12, 2025
e6aea3b
feat: apply converters to --inputs and --outputs
kvitorr Jun 12, 2025
025a2eb
style: remove blank space
kvitorr Jun 12, 2025
70cc464
Merge branch 'JabRef:main' into fix-for-issue-13274
kvitorr Jun 12, 2025
249f38f
feat: change the DataType from String and File to Path
kvitorr Jun 14, 2025
5241f3c
feat: remove code duplication
kvitorr Jun 14, 2025
385ab81
fix: correct the style
kvitorr Jun 14, 2025
2c40476
feat: create a new method to remove code duplication, create constant…
kvitorr Jun 14, 2025
fde43a6
feat: correct the unit tests to the changes made in the class
kvitorr Jun 14, 2025
35a84ec
Merge branch 'fix-for-issue-13274' of https://github.com/kvitorr/jabr…
kvitorr Jun 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions jabkit/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

requires info.picocli;
opens org.jabref.cli;
opens org.jabref.cli.converter;

requires transitive org.jspecify;
requires java.prefs;
Expand All @@ -20,9 +21,8 @@
requires org.tinylog.api;
requires org.tinylog.api.slf4j;
requires org.tinylog.impl;

requires java.xml;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not modify lines of code not related to the PR if not 90% sure

Here, the empty line does make sence to separate blocks. Please undo this change.


// region: other libraries (alphabetically)
requires io.github.adr;
// endregion
Expand Down
3 changes: 2 additions & 1 deletion jabkit/src/main/java/org/jabref/cli/CheckConsistency.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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")
Expand Down
5 changes: 3 additions & 2 deletions jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change theese to Paths. Artifact from initial implementation.


@Option(names = {"--output-format"}, description = "Output format: txt or csv")
Expand Down
6 changes: 4 additions & 2 deletions jabkit/src/main/java/org/jabref/cli/Convert.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to Path


@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")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of PathConverter for the output file path conversion is not optimal. Consider using Path.of() for better readability and maintainability.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange that one os PathConverter and the other one is StringPathConverter.

OK, this seems to be generated with AI. Please, instruct the AI to avoid code duplication.

PathConverter is the nicer name, remove the other things.

While scrolling through, you should have seen this for yourself @kvitorr!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this could be artifacts from initial implementation. Should all be Paths.

Copy link
Author

@kvitorr kvitorr Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@koppor yes, I am using AI to understand the code... I have never worked with picocli before... The converters were made taking into account the type of data that was returned by --input and --output options... So, if there was code duplication it was because there was no consensus on the return type of these variables. I didn't feel comfortable changing the type without knowing the consequences, so I uploaded the changes and waited for the code review.

private Path outputFile;

@Option(names = {"--output-format"}, description = "Output format")
Expand Down
5 changes: 3 additions & 2 deletions jabkit/src/main/java/org/jabref/cli/Pseudonymize.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description should use 'BibTeX' as the spelling for BibTeX in Java strings, as per the guidelines. This ensures consistency across the codebase.

private String inputFile;

@Option(names = {"--output"}, description = "Output pseudo-bib file")
@Option(names = {"--output"}, converter = StringPathConverter.class, description = "Output pseudo-bib file")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description should use 'BibTeX' as the spelling for BibTeX in Java strings, as per the guidelines. This ensures consistency across the codebase.

private String outputFile;

@Option(names = {"--key"}, description = "Output pseudo-keys file")
Expand Down
6 changes: 4 additions & 2 deletions jabkit/src/main/java/org/jabref/cli/Search.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.")
Expand Down
Original file line number Diff line number Diff line change
@@ -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<File> {

@Override
public File convert(String filePath) {
String normalizedPath = FileUtil.convertCygwinPathToWindows(filePath);
return new File(normalizedPath);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do only use Path. If sthg needs to be converted, do it inline. File should represent an actual File. Path (nio) represents just the path. Unify the different Converter classes. This is going to be a maintenance hell later.

18 changes: 18 additions & 0 deletions jabkit/src/main/java/org/jabref/cli/converter/PathConverter.java
Original file line number Diff line number Diff line change
@@ -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<Path> {

@Override
public Path convert(String value) {
String normalizedPath = FileUtil.convertCygwinPathToWindows(value);
return Paths.get(normalizedPath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path.of

}
}
Original file line number Diff line number Diff line change
@@ -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<String> {

@Override
public String convert(String filePath) {
return FileUtil.convertCygwinPathToWindows(filePath);
}
}
36 changes: 36 additions & 0 deletions jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -589,4 +590,39 @@ 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add something like "plainly converted if on non-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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return Path object

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("/", "\\\\");
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;
}
}
17 changes: 17 additions & 0 deletions jablib/src/test/java/org/jabref/logic/util/io/FileUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -518,4 +519,20 @@ 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",
"/mnt/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));
}
}
Loading