Skip to content

Commit 5a3cbe7

Browse files
authored
AppendToTextFile: support glob patterns for the relative file name (#7417)
Matching is now delegated to `PathMatcher` with `glob:` syntax, consistent with `DeleteSourceFiles`, `RenameFile`, `MoveFile`, and `SetFilePermissions`. When the pattern contains glob metacharacters no new file is generated, since a glob does not specify a concrete path to create.
1 parent 8198288 commit 5a3cbe7

2 files changed

Lines changed: 66 additions & 3 deletions

File tree

rewrite-core/src/main/java/org/openrewrite/text/AppendToTextFile.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
@EqualsAndHashCode(callSuper = false)
3535
public class AppendToTextFile extends ScanningRecipe<AtomicBoolean> {
3636
@Option(displayName = "Relative file name",
37-
description = "File name, using a relative path. If a non-plaintext file already exists at this location, then this recipe will do nothing.",
37+
description = "File name, using a relative path. May also be a glob expression (e.g. `**/*.txt`) to match one or more existing files; " +
38+
"when a glob is supplied no new file is created. If a non-plaintext file matches, that file is left unchanged.",
3839
example = "foo/bar/baz.txt")
3940
String relativeFileName;
4041

@@ -93,7 +94,7 @@ public TreeVisitor<?, ExecutionContext> getScanner(AtomicBoolean fileExists) {
9394
@Override
9495
public Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
9596
SourceFile sourceFile = (SourceFile) requireNonNull(tree);
96-
if (!fileExists.get() && sourceFile.getSourcePath().toString().equals(Paths.get(relativeFileName).toString())) {
97+
if (!fileExists.get() && matchesTarget(sourceFile.getSourcePath())) {
9798
fileExists.set(true);
9899
}
99100
return sourceFile;
@@ -103,6 +104,11 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
103104

104105
@Override
105106
public Collection<PlainText> generate(AtomicBoolean fileExists, Collection<SourceFile> generatedInThisCycle, ExecutionContext ctx) {
107+
// A glob can match multiple existing files but doesn't specify a concrete path to create.
108+
if (relativeFileName.matches(".*[*?\\[{].*")) {
109+
return emptyList();
110+
}
111+
106112
String maybeNewline = !Boolean.FALSE.equals(appendNewline) ? "\n" : "";
107113
String content = this.content + maybeNewline;
108114
String preamble = this.preamble != null ? this.preamble + maybeNewline : "";
@@ -132,7 +138,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor(AtomicBoolean fileExists) {
132138
@Override
133139
public Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
134140
SourceFile sourceFile = (SourceFile) requireNonNull(tree);
135-
if (sourceFile.getSourcePath().toString().equals(Paths.get(relativeFileName).toString())) {
141+
if (sourceFile instanceof PlainText && matchesTarget(sourceFile.getSourcePath())) {
136142
String maybeNewline = !Boolean.FALSE.equals(appendNewline) ? "\n" : "";
137143
String content = AppendToTextFile.this.content + maybeNewline;
138144
String preamble = AppendToTextFile.this.preamble != null ? AppendToTextFile.this.preamble + maybeNewline : "";
@@ -155,6 +161,10 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
155161
});
156162
}
157163

164+
private boolean matchesTarget(Path sourcePath) {
165+
return sourcePath.getFileSystem().getPathMatcher("glob:" + relativeFileName).matches(sourcePath);
166+
}
167+
158168
/**
159169
* Merges new content with existing content by appending only lines that are not already present.
160170
* Lines are compared after trimming whitespace.

rewrite-core/src/test/java/org/openrewrite/text/AppendToTextFileTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,59 @@ void leaveStrategyWithPartialExistingContent() {
316316
}
317317
}
318318

319+
@Nested
320+
class Glob {
321+
322+
@Test
323+
void globMatchesMultipleFiles() {
324+
rewriteRun(
325+
spec -> spec.recipe(new AppendToTextFile("**/*.txt", "content", null, true, AppendToTextFile.Strategy.Continue)),
326+
text(
327+
"existing1",
328+
"""
329+
existing1
330+
content
331+
""",
332+
spec -> spec.path("a/file1.txt").noTrim()
333+
),
334+
text(
335+
"existing2",
336+
"""
337+
existing2
338+
content
339+
""",
340+
spec -> spec.path("b/file2.txt").noTrim()
341+
)
342+
);
343+
}
344+
345+
@Test
346+
void globDoesNotCreateNewFile() {
347+
rewriteRun(
348+
spec -> spec.recipe(new AppendToTextFile("**/*.txt", "content", "preamble", true, AppendToTextFile.Strategy.Leave))
349+
);
350+
}
351+
352+
@Test
353+
void globSkipsNonMatchingFiles() {
354+
rewriteRun(
355+
spec -> spec.recipe(new AppendToTextFile("**/*.txt", "content", null, true, AppendToTextFile.Strategy.Continue)),
356+
text(
357+
"keep me",
358+
spec -> spec.path("a/file.md")
359+
),
360+
text(
361+
"existing",
362+
"""
363+
existing
364+
content
365+
""",
366+
spec -> spec.path("a/file.txt").noTrim()
367+
)
368+
);
369+
}
370+
}
371+
319372
@Nested
320373
class Merge {
321374
/**

0 commit comments

Comments
 (0)