From 09a79dc065f99403a9ec4f79b5088dd498d74c98 Mon Sep 17 00:00:00 2001 From: Kateryna Oblakevych Date: Mon, 8 Jan 2024 12:17:38 +0200 Subject: [PATCH] fix: windows escape symbols (#684) --- .../actions/DownloadSourcesAction.java | 2 +- .../actions/ScreenshotUploadAction.java | 4 +- .../cli/commands/actions/StatusAction.java | 4 +- .../commands/functionality/SourcesUtils.java | 6 +- .../crowdin/cli/utils/PlaceholderUtil.java | 30 ++++--- .../java/com/crowdin/cli/utils/Utils.java | 11 ++- .../functionality/SourcesUtilsTest.java | 84 +++++++++++++++++++ .../java/com/crowdin/cli/utils/UtilsTest.java | 15 +++- 8 files changed, 132 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/crowdin/cli/commands/actions/DownloadSourcesAction.java b/src/main/java/com/crowdin/cli/commands/actions/DownloadSourcesAction.java index 43944a06f..db93f670b 100644 --- a/src/main/java/com/crowdin/cli/commands/actions/DownloadSourcesAction.java +++ b/src/main/java/com/crowdin/cli/commands/actions/DownloadSourcesAction.java @@ -232,7 +232,7 @@ private Map getReviewedSourceFiles(String baseTemp, Projec Utils.PATH_SEPARATOR + project.getSourceLanguageId() + "-REV" ); - return Utils.unixPath(path); + return Utils.toUnixPath(path); }, Function.identity() )); diff --git a/src/main/java/com/crowdin/cli/commands/actions/ScreenshotUploadAction.java b/src/main/java/com/crowdin/cli/commands/actions/ScreenshotUploadAction.java index 98b0c21b1..d99bd28d6 100644 --- a/src/main/java/com/crowdin/cli/commands/actions/ScreenshotUploadAction.java +++ b/src/main/java/com/crowdin/cli/commands/actions/ScreenshotUploadAction.java @@ -76,14 +76,14 @@ public void act(Outputter out, ProjectProperties properties, ClientScreenshot cl request.setBranchId(branch.getId()); } if (nonNull(pathToSourceFile)) { - final String normalizedPath = Utils.unixPath(Utils.sepAtStart(pathToSourceFile)); + final String normalizedPath = Utils.toUnixPath(Utils.sepAtStart(pathToSourceFile)); FileInfo fileInfo = project.getFileInfos().stream() .filter(f -> normalizedPath.equals(f.getPath())).findFirst() .orElseThrow(() -> new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.file_not_exists"), pathToSourceFile))); request.setFileId(fileInfo.getId()); } if (nonNull(directoryPath)) { - final String normalizedPath = Utils.unixPath(Utils.sepAtStart(directoryPath)); + final String normalizedPath = Utils.toUnixPath(Utils.sepAtStart(directoryPath)); Directory directory = project.getDirectories().values().stream() .filter(d -> normalizedPath.equals(d.getPath())).findFirst() .orElseThrow(() -> new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.dir_not_exists"), directoryPath))); diff --git a/src/main/java/com/crowdin/cli/commands/actions/StatusAction.java b/src/main/java/com/crowdin/cli/commands/actions/StatusAction.java index ad8eb7ebc..15e17c9a7 100644 --- a/src/main/java/com/crowdin/cli/commands/actions/StatusAction.java +++ b/src/main/java/com/crowdin/cli/commands/actions/StatusAction.java @@ -60,7 +60,7 @@ public void act(Outputter out, ProjectProperties pb, ProjectClient client) { List progresses; if (file != null) { - String filePath = Utils.unixPath(Utils.sepAtStart(file)); + String filePath = Utils.toUnixPath(Utils.sepAtStart(file)); Long fileId = project.getFileInfos().stream() .filter(f -> filePath.equals(f.getPath())) .findFirst() @@ -68,7 +68,7 @@ public void act(Outputter out, ProjectProperties pb, ProjectClient client) { .getId(); progresses = client.getFileProgress(fileId); } else if (directory != null) { - String directoryPath = Utils.unixPath(Utils.sepAtStart(directory)); + String directoryPath = Utils.toUnixPath(Utils.sepAtStart(directory)); Long directoryId = project.getDirectories().values().stream() .filter(d -> directoryPath.equals(d.getPath())) .findFirst() diff --git a/src/main/java/com/crowdin/cli/commands/functionality/SourcesUtils.java b/src/main/java/com/crowdin/cli/commands/functionality/SourcesUtils.java index fd551ccbc..87d2da1d0 100644 --- a/src/main/java/com/crowdin/cli/commands/functionality/SourcesUtils.java +++ b/src/main/java/com/crowdin/cli/commands/functionality/SourcesUtils.java @@ -35,10 +35,10 @@ public static Stream getFiles(String basePath, String sourcePattern, List< public static List filterProjectFiles( List filePaths, String sourcePattern, List ignorePatterns, boolean preserveHierarchy, PlaceholderUtil placeholderUtil ) { - filePaths = filePaths.stream().map((Utils.isWindows() ? Utils::windowsPath : Utils::unixPath)).map(Utils::noSepAtStart).collect(Collectors.toList()); - sourcePattern = Utils.noSepAtStart(Utils.isWindows() ? Utils.windowsPath(sourcePattern) : Utils.unixPath(sourcePattern)); + filePaths = filePaths.stream().map((Utils.isWindows() ? Utils::toWindowsPath : Utils::toUnixPath)).map(Utils::noSepAtStart).collect(Collectors.toList()); + sourcePattern = Utils.noSepAtStart(Utils.isWindows() ? Utils.toWindowsPath(sourcePattern) : Utils.toUnixPath(sourcePattern)); ignorePatterns = (ignorePatterns != null) - ? ignorePatterns.stream().map((Utils.isWindows() ? Utils::windowsPath : Utils::unixPath)).map(Utils::noSepAtStart).collect(Collectors.toList()) : Collections.emptyList(); + ? ignorePatterns.stream().map((Utils.isWindows() ? Utils::toWindowsPath : Utils::toUnixPath)).map(Utils::noSepAtStart).collect(Collectors.toList()) : Collections.emptyList(); Predicate sourcePredicate; Predicate ignorePredicate; diff --git a/src/main/java/com/crowdin/cli/utils/PlaceholderUtil.java b/src/main/java/com/crowdin/cli/utils/PlaceholderUtil.java index dc559807f..9257f9d55 100644 --- a/src/main/java/com/crowdin/cli/utils/PlaceholderUtil.java +++ b/src/main/java/com/crowdin/cli/utils/PlaceholderUtil.java @@ -12,6 +12,8 @@ import java.util.Set; import java.util.stream.Collectors; +import static com.crowdin.cli.utils.Utils.isWindows; + public class PlaceholderUtil { public static final String PLACEHOLDER_ANDROID_CODE = "%android_code%"; @@ -51,13 +53,16 @@ public class PlaceholderUtil { private static final String SET_CLOSE_BRACKET = "]"; public static final String ROUND_BRACKET_OPEN = "("; public static final String ROUND_BRACKET_CLOSE = ")"; - public static final String ESCAPE_ROUND_BRACKET_OPEN = "\\("; - public static final String ESCAPE_ROUND_BRACKET_CLOSE = "\\)"; - private static final String ESCAPE_DOT = "\\."; + public static final String ESCAPE_ROUND_BRACKET_OPEN = isWindows() ? "^(" : "\\("; + public static final String ESCAPE_ROUND_BRACKET_CLOSE = isWindows() ? "^)" : "\\)"; + private static final String ESCAPE_DOT = isWindows() ? "^." : "\\."; + private static final String ESCAPE_DOT_REGEX = "\\."; private static final String ESCAPE_DOT_PLACEHOLDER = "{ESCAPE_DOT}"; - private static final String ESCAPE_QUESTION = "\\?"; + private static final String ESCAPE_QUESTION = isWindows() ? "^?" : "\\?"; + private static final String ESCAPE_QUESTION_REGEX = "\\?"; private static final String ESCAPE_QUESTION_PLACEHOLDER = "{ESCAPE_QUESTION_MARK}"; - private static final String ESCAPE_ASTERISK = "\\*"; + private static final String ESCAPE_ASTERISK = isWindows() ? "^*" : "\\*"; + private static final String ESCAPE_ASTERISK_REGEX = "\\*"; private static final String ESCAPE_ASTERISK_PLACEHOLDER = "{ESCAPE_ASTERISK}"; private static final String ESCAPE_ASTERISK_REPLACEMENT_FROM = ".+" + Utils.PATH_SEPARATOR; private static final String ESCAPE_ASTERISK_REPLACEMENT_TO = "(.+" + Utils.PATH_SEPARATOR_REGEX + ")?"; @@ -228,13 +233,13 @@ public List formatForRegex(List toFormat, boolean onProjectLangs } public static String formatSourcePatternForRegex(String toFormat) { - if(Utils.isWindows()){ + if (isWindows()) { toFormat = toFormat - .replace("\\", "\\\\"); + .replace("\\", "\\\\"); } toFormat = toFormat .replace(ESCAPE_DOT, ESCAPE_DOT_PLACEHOLDER) - .replace(DOT, ESCAPE_DOT) + .replace(DOT, ESCAPE_DOT_REGEX) .replace(ESCAPE_DOT_PLACEHOLDER, ESCAPE_DOT) .replace(ESCAPE_QUESTION, ESCAPE_QUESTION_PLACEHOLDER) @@ -252,10 +257,15 @@ public static String formatSourcePatternForRegex(String toFormat) { toFormat = toFormat .replace(ROUND_BRACKET_OPEN, ESCAPE_ROUND_BRACKET_OPEN) - .replace(ROUND_BRACKET_CLOSE, ESCAPE_ROUND_BRACKET_CLOSE) - .replace(ESCAPE_ASTERISK_REPLACEMENT_FROM, ESCAPE_ASTERISK_REPLACEMENT_TO); + + if (isWindows()) { + toFormat = toFormat + .replace(ESCAPE_ASTERISK, ESCAPE_ASTERISK_REGEX) + .replace(ESCAPE_DOT, ESCAPE_DOT_REGEX) + .replace(ESCAPE_QUESTION, ESCAPE_QUESTION_REGEX); + } return toFormat .replace(PLACEHOLDER_FILE_EXTENSION, "[^/]+") .replace(PLACEHOLDER_FILE_NAME, "[^/]+") diff --git a/src/main/java/com/crowdin/cli/utils/Utils.java b/src/main/java/com/crowdin/cli/utils/Utils.java index 76b3a7b41..f1821a320 100755 --- a/src/main/java/com/crowdin/cli/utils/Utils.java +++ b/src/main/java/com/crowdin/cli/utils/Utils.java @@ -86,11 +86,16 @@ public static String buildUserAgent() { System.getProperty("os.version")); } - public static String unixPath(String path) { - return (path != null) ? path.replaceAll("[\\\\/]+", "/") : null; + public static String toUnixPath(String path) { + if (path == null) { + return null; + } + return isWindows() + ? path.replaceAll("[\\\\/]+", "/") + : path.replaceAll("\\\\{2,}", "/").replaceAll("/+", "/"); } - public static String windowsPath(String path) { + public static String toWindowsPath(String path) { return (path != null) ? path.replaceAll("[\\\\/]+", "\\\\") : null; } diff --git a/src/test/java/com/crowdin/cli/commands/functionality/SourcesUtilsTest.java b/src/test/java/com/crowdin/cli/commands/functionality/SourcesUtilsTest.java index 2dc591953..6a0709060 100644 --- a/src/test/java/com/crowdin/cli/commands/functionality/SourcesUtilsTest.java +++ b/src/test/java/com/crowdin/cli/commands/functionality/SourcesUtilsTest.java @@ -361,6 +361,90 @@ public void testFilterProjectFiles_dest() { assertThat(actual, containsInAnyOrder(expected)); } + @ParameterizedTest + @MethodSource + @DisabledOnOs({OS.LINUX, OS.MAC}) + public void testFilterProjectFiles_escapeSymbols_windows(List filePaths, String sourcePattern, List expected) { + List actual = SourcesUtils.filterProjectFiles( + filePaths, sourcePattern, Collections.emptyList(), true, PlaceholderUtilBuilder.STANDART.build("")); + assertEquals(expected.size(), actual.size()); + assertThat(actual, containsInAnyOrder(expected.toArray())); + } + + static Stream testFilterProjectFiles_escapeSymbols_windows() { + String file1 = "here\\file-1.po"; + String file1Symbol = "here\\file?1.po"; + String file2 = "here\\file-2.po"; + String file2Symbol = "here\\file*2.po"; + String file3 = "here\\file-3.po"; + String file3Symbol = "here\\file.3.po"; + List allFiles = Arrays.asList(file1, file1Symbol, file2, file2Symbol, file3, file3Symbol); + return Stream.of( + arguments( + allFiles, + "here\\file^?1.po", + Arrays.asList(file1Symbol) + ), + arguments( + allFiles, + "here\\file^*2.po", + Arrays.asList(file2Symbol) + ), + arguments( + allFiles, + "here\\file^.3.po", + Arrays.asList(file3Symbol) + ), + arguments( + allFiles, + "here\\*.po", + allFiles + ) + ); + } + + @ParameterizedTest + @MethodSource + @DisabledOnOs(OS.WINDOWS) + public void testFilterProjectFiles_escapeSymbols_unix(List filePaths, String sourcePattern, List expected) { + List actual = SourcesUtils.filterProjectFiles( + filePaths, sourcePattern, Collections.emptyList(), true, PlaceholderUtilBuilder.STANDART.build("")); + assertEquals(expected.size(), actual.size()); + assertThat(actual, containsInAnyOrder(expected.toArray())); + } + + static Stream testFilterProjectFiles_escapeSymbols_unix() { + String file1 = "here/file-1.po"; + String file1Symbol = "here/file?1.po"; + String file2 = "here/file-2.po"; + String file2Symbol = "here/file*2.po"; + String file3 = "here/file-3.po"; + String file3Symbol = "here/file.3.po"; + List allFiles = Arrays.asList(file1, file1Symbol, file2, file2Symbol, file3, file3Symbol); + return Stream.of( + arguments( + allFiles, + "here/file\\?1.po", + Arrays.asList(file1Symbol) + ), + arguments( + allFiles, + "here/file\\*2.po", + Arrays.asList(file2Symbol) + ), + arguments( + allFiles, + "here/file\\.3.po", + Arrays.asList(file3Symbol) + ), + arguments( + allFiles, + "here/*.po", + allFiles + ) + ); + } + @ParameterizedTest @MethodSource public void testContainsParameter(String sourcePattern, boolean expected) { diff --git a/src/test/java/com/crowdin/cli/utils/UtilsTest.java b/src/test/java/com/crowdin/cli/utils/UtilsTest.java index c909068d7..50401f8a0 100644 --- a/src/test/java/com/crowdin/cli/utils/UtilsTest.java +++ b/src/test/java/com/crowdin/cli/utils/UtilsTest.java @@ -3,6 +3,8 @@ import java.util.Optional; import org.apache.commons.lang3.SystemUtils; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -36,13 +38,20 @@ public void testBuildUserAgent() { } @Test - public void testUnixPath() { - assertEquals("/path/to/file", Utils.unixPath("\\path\\to\\file")); + @DisabledOnOs({OS.LINUX, OS.MAC}) + public void testUnixPath_windows() { + assertEquals("/path/to/file", Utils.toUnixPath("\\path\\to\\\\file")); + } + + @Test + @DisabledOnOs(OS.WINDOWS) + public void testUnixPath_unix() { + assertEquals("/path/to/file", Utils.toUnixPath("/path/to\\\\file")); } @Test public void testWindowsPath() { - assertEquals("\\path\\to\\file", Utils.windowsPath("/path/to/file")); + assertEquals("\\path\\to\\file", Utils.toWindowsPath("/path/to/file")); } @Test