|
12 | 12 | import com.dtsx.docs.core.runner.tests.strategies.test.CompilesTestStrategy; |
13 | 13 | import com.dtsx.docs.core.runner.tests.strategies.test.SnapshotTestStrategy; |
14 | 14 | import com.dtsx.docs.lib.CliLogger; |
| 15 | +import lombok.SneakyThrows; |
15 | 16 | import lombok.val; |
16 | 17 | import org.apache.commons.lang3.tuple.Pair; |
17 | 18 |
|
18 | 19 | import java.io.IOException; |
| 20 | +import java.nio.file.FileSystems; |
19 | 21 | import java.nio.file.Files; |
20 | 22 | import java.nio.file.Path; |
| 23 | +import java.nio.file.PathMatcher; |
21 | 24 | import java.util.*; |
22 | 25 | import java.util.function.Predicate; |
23 | 26 | import java.util.regex.Pattern; |
@@ -66,10 +69,8 @@ public static TestPlan buildPlan(TestCtx ctx) { |
66 | 69 | CliLogger.println(true, "@|bold Building test plan...|@"); |
67 | 70 | CliLogger.println(true, "@!->!@ Found " + testRoots.size() + " test roots"); |
68 | 71 |
|
69 | | - // Build gitignore predicate once at the start |
70 | | - val gitignorePredicate = buildGitignorePredicate(ctx.examplesFolder()); |
71 | | - |
72 | 72 | val builder = new Builder(); |
| 73 | + val gitignorePredicate = buildGitignorePredicate(ctx.examplesFolder()); |
73 | 74 |
|
74 | 75 | for (val rootPath : testRoots) { |
75 | 76 | mkTestRoot(ctx, rootPath, gitignorePredicate).ifPresent(builder::addRoot); |
@@ -137,72 +138,70 @@ private static List<Path> findTestRoots(Path examplesFolder) { |
137 | 138 | /// - `**/obj/*` - matches obj directory at any depth |
138 | 139 | /// - `target/*` - matches target directory at root level |
139 | 140 | /// |
140 | | - /// @param examplesFolder the examples folder to look for .gitignore |
141 | 141 | /// @return a predicate that returns true if a path should be ignored |
142 | | - private static Predicate<Path> buildGitignorePredicate(Path examplesFolder) { |
143 | | - val gitignorePath = examplesFolder.resolve(".gitignore"); |
| 142 | + @SneakyThrows |
| 143 | + private static Predicate<Path> buildGitignorePredicate(Path rootFolder) { |
| 144 | + val gitignorePath = rootFolder.resolve(".gitignore"); |
144 | 145 |
|
145 | 146 | if (!Files.exists(gitignorePath)) { |
146 | | - // No .gitignore file, don't ignore anything |
147 | | - return path -> false; |
| 147 | + return _ -> false; |
148 | 148 | } |
149 | 149 |
|
150 | | - try { |
151 | | - val patterns = Files.readAllLines(gitignorePath).stream() |
152 | | - .map(String::trim) |
153 | | - .filter(line -> !line.isEmpty() && !line.startsWith("#")) |
154 | | - .map(TestPlanBuilder::gitignorePatternToRegex) |
155 | | - .map(Pattern::compile) |
156 | | - .collect(Collectors.toList()); |
157 | | - |
158 | | - return path -> { |
159 | | - val pathStr = path.toString(); |
160 | | - return patterns.stream().anyMatch(pattern -> pattern.matcher(pathStr).find()); |
161 | | - }; |
162 | | - } catch (IOException e) { |
163 | | - CliLogger.println(false, "@|yellow Warning: Failed to read .gitignore file, ignoring it|@"); |
164 | | - return path -> false; |
| 150 | + val lines = Files.readAllLines(gitignorePath); |
| 151 | + |
| 152 | + val fs = FileSystems.getDefault(); |
| 153 | + |
| 154 | + val excludeMatchers = new ArrayList<PathMatcher>(); |
| 155 | + val includeMatchers = new ArrayList<PathMatcher>(); |
| 156 | + |
| 157 | + for (var line : lines) { |
| 158 | + line = line.trim(); |
| 159 | + |
| 160 | + if (line.isEmpty() || line.startsWith("#")) { |
| 161 | + continue; |
| 162 | + } |
| 163 | + |
| 164 | + val negated = line.startsWith("!"); |
| 165 | + |
| 166 | + if (negated) { |
| 167 | + line = line.substring(1); |
| 168 | + } |
| 169 | + |
| 170 | + if (line.endsWith("/")) { |
| 171 | + line = line + "**"; |
| 172 | + } |
| 173 | + |
| 174 | + if (!line.startsWith("/")) { |
| 175 | + line = "**/" + line; |
| 176 | + } else { |
| 177 | + line = line.substring(1); |
| 178 | + } |
| 179 | + |
| 180 | + val matcher = fs.getPathMatcher("glob:" + line); |
| 181 | + |
| 182 | + if (negated) { |
| 183 | + includeMatchers.add(matcher); |
| 184 | + } else { |
| 185 | + excludeMatchers.add(matcher); |
| 186 | + } |
165 | 187 | } |
166 | | - } |
167 | 188 |
|
168 | | - /// Converts a gitignore pattern to a regex pattern. |
169 | | - /// |
170 | | - /// Handles common gitignore patterns: |
171 | | - /// - `**` matches any number of directories |
172 | | - /// - `*` matches any characters except directory separator |
173 | | - /// - Literal directory separators |
174 | | - /// |
175 | | - /// @param gitignorePattern the gitignore pattern (e.g., "**/bin/*") |
176 | | - /// @return a regex pattern string |
177 | | - private static String gitignorePatternToRegex(String gitignorePattern) { |
178 | | - // Escape special regex characters except * and / |
179 | | - String regex = gitignorePattern |
180 | | - .replace("\\", "\\\\") |
181 | | - .replace(".", "\\.") |
182 | | - .replace("+", "\\+") |
183 | | - .replace("?", "\\?") |
184 | | - .replace("|", "\\|") |
185 | | - .replace("(", "\\(") |
186 | | - .replace(")", "\\)") |
187 | | - .replace("[", "\\[") |
188 | | - .replace("]", "\\]") |
189 | | - .replace("{", "\\{") |
190 | | - .replace("}", "\\}") |
191 | | - .replace("^", "\\^") |
192 | | - .replace("$", "\\$"); |
193 | | - |
194 | | - // Convert gitignore wildcards to regex |
195 | | - regex = regex.replace("**/", "(.*/)?"); // ** matches any number of directories |
196 | | - regex = regex.replace("**", ".*"); // ** at end matches anything |
197 | | - regex = regex.replace("*", "[^/\\\\]*"); // * matches anything except path separator |
198 | | - |
199 | | - // Handle both forward and backward slashes |
200 | | - regex = regex.replace("/", "[/\\\\]"); |
201 | | - |
202 | | - return regex; |
| 189 | + return (path) -> { |
| 190 | + val relative = rootFolder.relativize(path); |
| 191 | + var ignored = false; |
| 192 | + |
| 193 | + for (val matcher : excludeMatchers) { |
| 194 | + ignored = ignored || matcher.matches(relative); |
| 195 | + } |
| 196 | + |
| 197 | + for (val matcher : includeMatchers) { |
| 198 | + ignored = ignored && !matcher.matches(relative); |
| 199 | + } |
| 200 | + |
| 201 | + return ignored; |
| 202 | + }; |
203 | 203 | } |
204 | 204 |
|
205 | | - /// Creates a test root from a directory containing a `meta.yml` file. |
206 | 205 | /// |
207 | 206 | /// Steps: |
208 | 207 | /// 1. Parse `meta.yml` configuration |
@@ -271,7 +270,6 @@ private static TreeMap<ClientLanguage, Set<Path>> findFilesToTestInRoot(Path roo |
271 | 270 | return; |
272 | 271 | } |
273 | 272 |
|
274 | | - // Skip files matching .gitignore patterns |
275 | 273 | if (gitignorePredicate.test(child)) { |
276 | 274 | return; |
277 | 275 | } |
|
0 commit comments