Skip to content

Commit dccf3e5

Browse files
committed
Add support for wildcard within directory name
1 parent b7e8025 commit dccf3e5

File tree

2 files changed

+79
-15
lines changed

2 files changed

+79
-15
lines changed

src/main/java/com/anacoders/cookbook/CreateYamlFilesByPattern.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,14 @@ public String getDisplayName() {
4242

4343
@Override
4444
public String getDescription() {
45-
return "Creates YAML files in directories matching a glob-like pattern. Supports * (single segment) and ** (zero or more segments). " +
45+
return "Creates YAML files in directories matching a glob-like pattern. Supports * (single segment or within segment) and ** (zero or more segments). " +
4646
"Files are only created if they don't already exist. Example: 'projects/*/config.yaml' creates config.yaml in each direct child of projects/. " +
47+
"Example: 'projects/project-*/config.yaml' matches directories like 'project-a', 'project-b'. " +
4748
"Example: 'src/**/config/app.yaml' matches any 'config' directory under src at any depth.";
4849
}
4950

5051
@Option(displayName = "File pattern",
51-
description = "Pattern with wildcards for where to create files. Use * for single directory wildcard, ** for recursive. " +
52+
description = "Pattern with wildcards for where to create files. Use * for single directory wildcard or within segment (e.g., 'project-*'), ** for recursive. " +
5253
"Example: 'projects/*/config.yaml' creates config.yaml in each direct child of projects/",
5354
example = "projects/*/config.yaml")
5455
String filePattern;
@@ -174,6 +175,14 @@ private boolean matchSegments(List<String> pathSegs, List<String> patternSegs, i
174175
pathIdx++;
175176
patternIdx++;
176177

178+
} else if (pattern.contains("*") || pattern.contains("?")) {
179+
// Glob pattern within segment (e.g., "project-*")
180+
if (!matchGlobPattern(pathSegs.get(pathIdx), pattern)) {
181+
return false;
182+
}
183+
pathIdx++;
184+
patternIdx++;
185+
177186
} else {
178187
// Literal segment must match exactly
179188
if (!pattern.equals( pathSegs.get( pathIdx ) )) {
@@ -192,6 +201,32 @@ private boolean matchSegments(List<String> pathSegs, List<String> patternSegs, i
192201
return patternIdx == patternSegs.size() && pathIdx == pathSegs.size();
193202
}
194203

204+
/**
205+
* Matches a path segment against a glob pattern that may contain wildcards.
206+
* Supports * for matching any sequence of characters within a segment.
207+
* Supports ? for matching any single character.
208+
* Examples: "project-*" matches "project-a", "project-foo", etc.
209+
*/
210+
private boolean matchGlobPattern(String segment, String pattern) {
211+
// Convert glob pattern to regex
212+
StringBuilder regex = new StringBuilder("^");
213+
for (int i = 0; i < pattern.length(); i++) {
214+
char c = pattern.charAt(i);
215+
if (c == '*') {
216+
regex.append(".*");
217+
} else if (c == '?') {
218+
regex.append(".");
219+
} else if ("[]{}().+^$|\\".indexOf(c) != -1) {
220+
// Escape regex special characters
221+
regex.append("\\").append(c);
222+
} else {
223+
regex.append(c);
224+
}
225+
}
226+
regex.append("$");
227+
return segment.matches(regex.toString());
228+
}
229+
195230
private Set<Path> resolveTargetFilePaths(Set<Path> existingDirectories) {
196231
List<String> patternSegs = splitPattern(filePattern);
197232
if (patternSegs.isEmpty()) {
@@ -220,3 +255,4 @@ private Set<Path> resolveTargetFilePaths(Set<Path> existingDirectories) {
220255
return targets;
221256
}
222257
}
258+

src/test/java/com/anacoders/cookbook/CreateYamlFilesByPatternTest.java

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,19 @@
1414
* limitations under the License.
1515
*/
1616
package com.anacoders.cookbook;
17-
1817
import org.junit.jupiter.api.Test;
1918
import org.openrewrite.DocumentExample;
2019
import org.openrewrite.test.RecipeSpec;
2120
import org.openrewrite.test.RewriteTest;
22-
2321
import static org.openrewrite.yaml.Assertions.yaml;
24-
2522
class CreateYamlFilesByPatternTest implements RewriteTest {
26-
2723
@Override
2824
public void defaults(RecipeSpec spec) {
2925
spec.recipe(new CreateYamlFilesByPattern(
3026
"projects/*/config.yaml",
3127
"apiVersion: v1\nkind: Config\nmetadata:\n name: example"
3228
));
3329
}
34-
3530
@DocumentExample
3631
@Test
3732
void createsFilesInMatchingDirectoriesAndLeavesExistingFilesUntouched() {
@@ -92,7 +87,6 @@ void createsFilesInMatchingDirectoriesAndLeavesExistingFilesUntouched() {
9287
)
9388
);
9489
}
95-
9690
@Test
9791
void doesNothingWhenNoMatchingDirectories() {
9892
rewriteRun(
@@ -109,7 +103,6 @@ void doesNothingWhenNoMatchingDirectories() {
109103
)
110104
);
111105
}
112-
113106
@Test
114107
void worksWithNestedAndDeeplyNestedDirectories() {
115108
rewriteRun(
@@ -172,7 +165,6 @@ void worksWithNestedAndDeeplyNestedDirectories() {
172165
)
173166
);
174167
}
175-
176168
@Test
177169
void handlesMultipleWildcardsWithStarAndDoubleStar() {
178170
rewriteRun(
@@ -232,7 +224,6 @@ void handlesMultipleWildcardsWithStarAndDoubleStar() {
232224
)
233225
);
234226
}
235-
236227
@Test
237228
void skipsPartiallyMatchingDirectories() {
238229
rewriteRun(
@@ -264,7 +255,6 @@ void skipsPartiallyMatchingDirectories() {
264255
)
265256
);
266257
}
267-
268258
@Test
269259
void supportsDoubleStarRecursiveMatching() {
270260
rewriteRun(
@@ -315,7 +305,6 @@ void supportsDoubleStarRecursiveMatching() {
315305
)
316306
);
317307
}
318-
319308
@Test
320309
void exactPathAtRoot() {
321310
rewriteRun(
@@ -332,7 +321,6 @@ void exactPathAtRoot() {
332321
)
333322
);
334323
}
335-
336324
@Test
337325
void multipleDoubleStarsWithZeroSegmentMatching() {
338326
rewriteRun(
@@ -383,7 +371,6 @@ void multipleDoubleStarsWithZeroSegmentMatching() {
383371
)
384372
);
385373
}
386-
387374
@Test
388375
void filenameWithDots() {
389376
rewriteRun(
@@ -406,4 +393,45 @@ void filenameWithDots() {
406393
)
407394
);
408395
}
396+
@Test
397+
void wildcardWithinDirectoryName() {
398+
rewriteRun(
399+
spec -> spec.recipe(new CreateYamlFilesByPattern(
400+
"projects/project-*/config.yaml",
401+
"content: value"
402+
.expectedCyclesThatMakeChanges(2)),
403+
yaml(
404+
"""
405+
existing: file
406+
""",
407+
s -> s.path("projects/project-a/existing.yaml")
408+
),
409+
yaml(
410+
"""
411+
existing: file
412+
""",
413+
s -> s.path("projects/project-b/existing.yaml")
414+
),
415+
yaml(
416+
"""
417+
existing: file
418+
""",
419+
s -> s.path("projects/other/existing.yaml")
420+
),
421+
yaml(
422+
doesNotExist(),
423+
"""
424+
content: value
425+
""",
426+
s -> s.path("projects/project-a/config.yaml")
427+
),
428+
yaml(
429+
doesNotExist(),
430+
"""
431+
content: value
432+
""",
433+
s -> s.path("projects/project-b/config.yaml")
434+
)
435+
);
436+
}
409437
}

0 commit comments

Comments
 (0)