From a5e113c7bd46b47525baf7bad43d0206de7a4e5f Mon Sep 17 00:00:00 2001 From: Kateryna Oblakevych Date: Sat, 16 Dec 2023 08:03:25 +0200 Subject: [PATCH] fix: brackets in the folder names --- .../cli/properties/helper/FileMatcher.java | 16 +++++ .../crowdin/cli/utils/PlaceholderUtil.java | 16 +++-- .../actions/UploadSourcesActionTest.java | 71 ++++++++++++++++++- 3 files changed, 96 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/crowdin/cli/properties/helper/FileMatcher.java b/src/main/java/com/crowdin/cli/properties/helper/FileMatcher.java index 1490ad279..93021b45f 100644 --- a/src/main/java/com/crowdin/cli/properties/helper/FileMatcher.java +++ b/src/main/java/com/crowdin/cli/properties/helper/FileMatcher.java @@ -6,6 +6,9 @@ import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.PathMatcher; +import java.util.regex.Pattern; + +import static com.crowdin.cli.utils.Utils.isWindows; /** * File matcher for Crowdin CLI's documented syntax. @@ -13,6 +16,13 @@ class FileMatcher implements PathMatcher { private final PathMatcher delegate; + public static final String ESCAPE_BRACKET_OPEN = isWindows() ? "^[" : "\\["; + public static final String ESCAPE_BRACKET_CLOSE = isWindows() ? "^]" : "\\]"; + public static final String ESCAPE_BRACKET_OPEN_REGEX = "\\\\["; + public static final String ESCAPE_BRACKET_CLOSE_REGEX = "\\\\]"; + private static final String ESCAPE_BRACKET_OPEN_PLACEHOLDER = "ESCAPE_BRACKET_OPEN_PLACEHOLDER"; + private static final String ESCAPE_BRACKET_CLOSE_PLACEHOLDER = "ESCAPE_BRACKET_CLOSE_PLACEHOLDER"; + FileMatcher(String pattern, String basePath) { // Making matchers match the full path. @@ -24,11 +34,17 @@ class FileMatcher implements PathMatcher { pattern = pattern.replace(Utils.PATH_SEPARATOR_REGEX + Utils.PATH_SEPARATOR_REGEX, Utils.PATH_SEPARATOR_REGEX); } } + pattern = pattern.replaceAll(Pattern.quote(ESCAPE_BRACKET_OPEN), ESCAPE_BRACKET_OPEN_PLACEHOLDER); + pattern = pattern.replaceAll(Pattern.quote(ESCAPE_BRACKET_CLOSE), ESCAPE_BRACKET_CLOSE_PLACEHOLDER); + pattern = pattern.replaceAll("\\\\+", Utils.PATH_SEPARATOR_REGEX + Utils.PATH_SEPARATOR_REGEX); pattern = pattern.replaceAll("/+", "/"); pattern = pattern.replaceAll("\\{\\{+", "\\\\{\\\\{"); pattern = pattern.replaceAll("}}+", "\\\\}\\\\}"); + pattern = pattern.replaceAll(ESCAPE_BRACKET_OPEN_PLACEHOLDER, ESCAPE_BRACKET_OPEN_REGEX); + pattern = pattern.replaceAll(ESCAPE_BRACKET_CLOSE_PLACEHOLDER, ESCAPE_BRACKET_CLOSE_REGEX); + // We *could* implement exactly what's documented. The idea would be to implement something like // Java's Globs.toRegexPattern but supporting only the documented syntax. Instead, we will use // the real globber. diff --git a/src/main/java/com/crowdin/cli/utils/PlaceholderUtil.java b/src/main/java/com/crowdin/cli/utils/PlaceholderUtil.java index dc559807f..bc1110a51 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%"; @@ -47,8 +49,12 @@ public class PlaceholderUtil { private static final String QUESTION_MARK = "?"; private static final String DOT = "."; private static final String DOT_PLUS = ".+"; - private static final String SET_OPEN_BRACKET = "["; - private static final String SET_CLOSE_BRACKET = "]"; + private static final String SQUARE_BRACKET_OPEN = "["; + private static final String SQUARE_BRACKET_CLOSE = "]"; + public static final String REGEX_SQUARE_BRACKET_OPEN = "\\["; + public static final String ESCAPE_SQUARE_BRACKET_OPEN = isWindows()? "^[" : "\\\\["; + public static final String ESCAPE_SQUARE_BRACKET_CLOSE = isWindows()? "^]" : "\\\\]"; + public static final String ROUND_BRACKET_OPEN = "("; public static final String ROUND_BRACKET_CLOSE = ")"; public static final String ESCAPE_ROUND_BRACKET_OPEN = "\\("; @@ -192,10 +198,12 @@ public String replaceFileDependentPlaceholders(String toFormat, File file) { prefix = prefix.length() > 1 && file.getPath().contains(prefix) ? StringUtils.substringBefore(fileParent, Utils.noSepAtStart(prefix)) : ""; String doubleAsterisks = StringUtils.removeStart(Utils.noSepAtStart(StringUtils.removeStart(fileParent, prefix)), Utils.noSepAtEnd(Utils.noSepAtStart(StringUtils.substringBefore(toFormat, "**")))); + doubleAsterisks = doubleAsterisks.replaceAll(REGEX_SQUARE_BRACKET_OPEN, ESCAPE_SQUARE_BRACKET_OPEN); + doubleAsterisks = doubleAsterisks.replaceAll(SQUARE_BRACKET_CLOSE, ESCAPE_SQUARE_BRACKET_CLOSE); toFormat = toFormat.replace("**", doubleAsterisks); } - toFormat = toFormat.replaceAll("[\\\\/]+", Utils.PATH_SEPARATOR_REGEX); + toFormat = toFormat.replaceAll("[" + Utils.PATH_SEPARATOR_REGEX + "]+", Utils.PATH_SEPARATOR_REGEX); return StringUtils.removeStart(toFormat, Utils.PATH_SEPARATOR); } @@ -228,7 +236,7 @@ public List formatForRegex(List toFormat, boolean onProjectLangs } public static String formatSourcePatternForRegex(String toFormat) { - if(Utils.isWindows()){ + if(isWindows()){ toFormat = toFormat .replace("\\", "\\\\"); } diff --git a/src/test/java/com/crowdin/cli/commands/actions/UploadSourcesActionTest.java b/src/test/java/com/crowdin/cli/commands/actions/UploadSourcesActionTest.java index fc2519891..af3b07cb6 100644 --- a/src/test/java/com/crowdin/cli/commands/actions/UploadSourcesActionTest.java +++ b/src/test/java/com/crowdin/cli/commands/actions/UploadSourcesActionTest.java @@ -27,11 +27,14 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.util.*; +import java.util.stream.Stream; +import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -517,6 +520,68 @@ public void testUploadOneSourceWithDestAndDeleteObsoleteOption_Project() throws verifyNoMoreInteractions(client); } + @ParameterizedTest + @MethodSource + public void testUploadOneSourceWithAsteriskSourceBracketsDirAndIgnore_Project(String folderName) throws ResponseException { + project.addFile(Utils.normalizePath(folderName + "/first.md"), "Hello, World!"); + project.addFile(Utils.normalizePath(folderName + "/1.md"), "Hello, World!"); + String translationPattern = Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%"; + NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder + .minimalBuiltPropertiesBean(Utils.normalizePath("**/*"), translationPattern, Arrays.asList("**/[0-9].*")) + .setBasePath(project.getBasePath()); + PropertiesWithFiles pb = pbBuilder.build(); + pb.setPreserveHierarchy(true); + ProjectClient client = mock(ProjectClient.class); + when(client.downloadFullProject()) + .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build()); + + AddDirectoryRequest addDirectoryRequest = new AddDirectoryRequest() {{ + setName(folderName); + }}; + Directory directory = DirectoryBuilder.standard().setProjectId(Long.parseLong(pb.getProjectId())) + .setIdentifiers(folderName, 201L, null, null).build(); + when(client.addDirectory(eq(addDirectoryRequest))) + .thenReturn(directory); + + when(client.uploadStorage(eq("first.md"), any())) + .thenReturn(1L); + + NewAction action = new UploadSourcesAction(null, true, false, true, false, false); + action.act(Outputter.getDefault(), pb, client); + + verify(client).downloadFullProject(); + verify(client).listLabels(); + verify(client).uploadStorage(eq("first.md"), any()); + verify(client).addDirectory(eq(addDirectoryRequest)); + AddFileRequest addFileRequest = new AddFileRequest() {{ + setName("first.md"); + setStorageId(1L); + setDirectoryId(201L); + setImportOptions(new OtherFileImportOptions() {{ + setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); + }} + ); + setExportOptions(new GeneralFileExportOptions() {{ + setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); + }} + ); + }}; + verify(client).addSource(eq(addFileRequest)); + verifyNoMoreInteractions(client); + } + + static Stream testUploadOneSourceWithAsteriskSourceBracketsDirAndIgnore_Project() { + return Stream.of( + arguments("[en]"), + arguments("t[en]"), + arguments("[en]t"), + arguments("t[en]t"), + arguments("t[en]"), + arguments("[en]t"), + arguments("tent") + ); + } + @Test public void testUpdateOneUploadOneSource_Project() throws ResponseException { project.addFile(Utils.normalizePath("first.po"), "Hello, World!");