Skip to content

Commit 5073402

Browse files
Reworked filtering
1 parent 784fcf7 commit 5073402

File tree

9 files changed

+182
-79
lines changed

9 files changed

+182
-79
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
### Changed
1515
- `PathUtils` removed, `PathPredicates` rework
1616
- Line extension: empty string is permitted
17+
- Filtering: split into distinct directories and files filters
1718

1819
---
1920
## [0.0.4] - 2025-09-27

README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -276,17 +276,23 @@ sorting/
276276
## Filtering
277277
Files and directories can be selectively included or excluded using a custom `Predicate<Path>`.
278278

279-
Filtering is **recursive by default**: directory's contents will always be traversed.
280-
However, if a directory does not match and none of its children match, the directory itself will not be displayed.
279+
Filtering is independant for files & directories. Files are filtered only if their parent directory pass the directory filter.
280+
If none of some directory's children match, the directory is still displayed.
281281

282282
The `PathPredicates` class provides several ready-to-use methods for creating common predicates, as well as a builder for creating more advanced predicates.
283283

284284
```java
285285
// Example: Filtering.java
286-
var hasJavaExtensionPredicate = PathPredicates.builder().hasExtension("java").build();
286+
Predicate<Path> excludeDirWithNoJavaFiles = dir -> !PathPredicates.hasNameEndingWith(dir, "no_java_file");
287+
var isJavaFilePredicate = PathPredicates.builder().hasExtension("java").build();
288+
287289
var prettyPrinter = FileTreePrettyPrinter.builder()
288-
.customizeOptions(options -> options.filter(hasJavaExtensionPredicate))
289-
.build();
290+
.customizeOptions(
291+
options -> options
292+
.filterDirectories(excludeDirWithNoJavaFiles)
293+
.filterFiles(hasJavaExtensionPredicate)
294+
)
295+
.build();
290296
```
291297
```
292298
filtering/
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package io.github.computerdaddyguy.jfiletreeprettyprinter.example;
2+
3+
import io.github.computerdaddyguy.jfiletreeprettyprinter.ChildLimitBuilder;
4+
import io.github.computerdaddyguy.jfiletreeprettyprinter.FileTreePrettyPrinter;
5+
import io.github.computerdaddyguy.jfiletreeprettyprinter.PathPredicates;
6+
import io.github.computerdaddyguy.jfiletreeprettyprinter.PrettyPrintOptions.Sorts;
7+
import java.nio.file.Path;
8+
import java.util.function.Function;
9+
10+
public class CompleteExample {
11+
12+
public static void main(String[] args) {
13+
14+
var rootFolder = "JFileTreePrettyPrinter";
15+
16+
var filterDir = PathPredicates.builder()
17+
.pathTest(path -> !PathPredicates.hasName(path, ".git"))
18+
.pathTest(path -> !PathPredicates.hasFullPathMatchingGlob(path, "**/.git"))
19+
.pathTest(path -> !PathPredicates.hasFullPathMatchingGlob(path, "**/.github"))
20+
.pathTest(path -> !PathPredicates.hasFullPathMatchingGlob(path, "**/.settings"))
21+
.pathTest(path -> !PathPredicates.hasFullPathMatchingGlob(path, "**/src/example"))
22+
.pathTest(path -> !PathPredicates.hasFullPathMatchingGlob(path, "**/src/test"))
23+
.pathTest(path -> !PathPredicates.hasFullPathMatchingGlob(path, "**/target"))
24+
.build();
25+
26+
var filterFiles = PathPredicates.builder()
27+
.pathTest(path -> !PathPredicates.hasNameStartingWith(path, "."))
28+
.pathTest(path -> {
29+
if (PathPredicates.hasParentMatching(path, parent -> PathPredicates.hasName(parent, "jfiletreeprettyprinter"))) {
30+
return PathPredicates.hasName(path, "FileTreePrettyPrinter.java");
31+
}
32+
return true;
33+
})
34+
.build();
35+
36+
var childLimitFunction = ChildLimitBuilder.builder()
37+
.limit(path -> PathPredicates.hasFullPathMatchingGlob(path, "**/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer"), 0)
38+
.limit(path -> PathPredicates.hasFullPathMatchingGlob(path, "**/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner"), 0)
39+
.build();
40+
41+
Function<Path, String> lineExtension = path -> {
42+
if (PathPredicates.hasName(path, "JfileTreePrettyPrinter-structure.png")) {
43+
return "\t// This image";
44+
} else if (PathPredicates.hasName(path, "FileTreePrettyPrinter.java")) {
45+
return "\t// Main entry point";
46+
} else if (PathPredicates.hasName(path, "README.md")) {
47+
return "\t\t// You're reading at this!";
48+
} else if (PathPredicates.hasName(path, "java")) {
49+
return "";
50+
}
51+
return null;
52+
};
53+
54+
var prettyPrinter = FileTreePrettyPrinter.builder()
55+
.customizeOptions(
56+
options -> options
57+
.withEmojis(true)
58+
.withCompactDirectories(true)
59+
.filterDirectories(filterDir)
60+
.filterFiles(filterFiles)
61+
.withChildLimit(childLimitFunction)
62+
.withLineExtension(lineExtension)
63+
.sort(Sorts.DIRECTORY_FIRST)
64+
)
65+
.build();
66+
var tree = prettyPrinter.prettyPrint(".");
67+
System.out.println(tree);
68+
}
69+
70+
}

src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/Filtering.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22

33
import io.github.computerdaddyguy.jfiletreeprettyprinter.FileTreePrettyPrinter;
44
import io.github.computerdaddyguy.jfiletreeprettyprinter.PathPredicates;
5+
import java.nio.file.Path;
6+
import java.util.function.Predicate;
57

68
public class Filtering {
79

810
public static void main(String[] args) {
11+
Predicate<Path> excludeDirWithNoJavaFiles = dir -> !PathPredicates.hasNameEndingWith(dir, "no_java_file");
912
var hasJavaExtensionPredicate = PathPredicates.builder().hasExtension("java").build();
13+
1014
var prettyPrinter = FileTreePrettyPrinter.builder()
11-
.customizeOptions(options -> options.filter(hasJavaExtensionPredicate))
15+
.customizeOptions(
16+
options -> options
17+
.filterDirectories(excludeDirWithNoJavaFiles)
18+
.filterFiles(hasJavaExtensionPredicate)
19+
)
1220
.build();
1321

1422
var tree = prettyPrinter.prettyPrint("src/example/resources/filtering");

src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -297,25 +297,37 @@ public PrettyPrintOptions sort(Comparator<Path> pathComparator) {
297297

298298
// ---------- Filtering ----------
299299

300-
@Nullable
301-
private Predicate<Path> pathFilter = null;
300+
private Predicate<Path> dirFilter = dir -> true;
301+
private Predicate<Path> fileFilter = dir -> true;
302302

303303
@Override
304-
@Nullable
305304
public Predicate<Path> pathFilter() {
306-
return pathFilter;
305+
return path -> PathPredicates.isDirectory(path)
306+
? dirFilter.test(path)
307+
: fileFilter.test(path);
308+
}
309+
310+
/**
311+
* Use a custom filter for retain only some directories.
312+
*
313+
* Directories that do not pass this filter will not be displayed.
314+
*
315+
* @param filter The filter to apply on directories, cannot be <code>null</code>
316+
*/
317+
public PrettyPrintOptions filterDirectories(@Nullable Predicate<Path> filter) {
318+
this.dirFilter = Objects.requireNonNull(filter, "filter is null");
319+
return this;
307320
}
308321

309322
/**
310-
* Use a custom filter for retain only some files and/or directories.
323+
* Use a custom filter for retain only some files.
324+
*
325+
* Files that do not pass this filter will not be displayed.
311326
*
312-
* Filtering is recursive by default: directory's contents will always be traversed.
313-
* However, if a directory does not match and none of its children match, the directory itself will not be displayed.
314-
315-
* @param filter The filter, <code>null</code> to disable filtering
327+
* @param filter The filter to apply on files, cannot be <code>null</code>
316328
*/
317-
public PrettyPrintOptions filter(@Nullable Predicate<Path> filter) {
318-
this.pathFilter = filter;
329+
public PrettyPrintOptions filterFiles(@Nullable Predicate<Path> filter) {
330+
this.fileFilter = Objects.requireNonNull(filter, "filter is null");
319331
return this;
320332
}
321333

src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/DefaultPathToTreeScanner.java

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.computerdaddyguy.jfiletreeprettyprinter.scanner;
22

3-
import io.github.computerdaddyguy.jfiletreeprettyprinter.PathPredicates;
43
import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.DirectoryEntry;
54
import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.FileEntry;
65
import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.MaxDepthReachEntry;
@@ -35,14 +34,14 @@ public TreeEntry scan(Path fileOrDir) {
3534
}
3635

3736
@Nullable
38-
private TreeEntry handle(int depth, Path fileOrDir, @Nullable Predicate<Path> filter) {
37+
private TreeEntry handle(int depth, Path fileOrDir, Predicate<Path> filter) {
3938
return fileOrDir.toFile().isDirectory()
4039
? handleDirectory(depth, fileOrDir, filter)
4140
: handleFile(fileOrDir);
4241
}
4342

4443
@Nullable
45-
private TreeEntry handleDirectory(int depth, Path dir, @Nullable Predicate<Path> filter) {
44+
private TreeEntry handleDirectory(int depth, Path dir, Predicate<Path> filter) {
4645

4746
if (depth >= options.getMaxDepth()) {
4847
var maxDepthEntry = new MaxDepthReachEntry(depth);
@@ -58,15 +57,15 @@ private TreeEntry handleDirectory(int depth, Path dir, @Nullable Predicate<Path>
5857
throw new UncheckedIOException("Unable to list files for directory: " + dir, e);
5958
}
6059

61-
// Filter is active and no children match
62-
if (depth > 0 && filter != null && childEntries.isEmpty() && !filter.test(dir)) {
63-
return null; // Do no show this directory at all
64-
}
60+
// // Filter is active and no children match
61+
// if (depth > 0 && filter != null && childEntries.isEmpty() && !filter.test(dir)) {
62+
// return null; // Do no show this directory at all
63+
// }
6564

6665
return new DirectoryEntry(dir, childEntries);
6766
}
6867

69-
private List<TreeEntry> handleDirectoryChildren(int depth, Path dir, Iterator<Path> pathIterator, @Nullable Predicate<Path> filter) {
68+
private List<TreeEntry> handleDirectoryChildren(int depth, Path dir, Iterator<Path> pathIterator, Predicate<Path> filter) {
7069

7170
var childEntries = new ArrayList<TreeEntry>();
7271
int maxChildEntries = options.getChildLimit().applyAsInt(dir);
@@ -95,7 +94,7 @@ private List<TreeEntry> handleDirectoryChildren(int depth, Path dir, Iterator<Pa
9594
return childEntries;
9695
}
9796

98-
private List<TreeEntry> handleLeftOverChildren(int depth, Iterator<Path> pathIterator, @Nullable Predicate<Path> filter) {
97+
private List<TreeEntry> handleLeftOverChildren(int depth, Iterator<Path> pathIterator, Predicate<Path> filter) {
9998
var childEntries = new ArrayList<TreeEntry>();
10099

101100
if (filter == null) {
@@ -121,13 +120,10 @@ private List<TreeEntry> handleLeftOverChildren(int depth, Iterator<Path> pathIte
121120
return childEntries;
122121
}
123122

124-
private Iterator<Path> directoryStreamToIterator(DirectoryStream<Path> childrenStream, @Nullable Predicate<Path> filter) {
125-
var stream = StreamSupport.stream(childrenStream.spliterator(), false);
126-
if (filter != null) {
127-
var recursiveFilter = PathPredicates.builder().isDirectory().build().or(filter);
128-
stream = stream.filter(recursiveFilter);
129-
}
130-
return stream
123+
private Iterator<Path> directoryStreamToIterator(DirectoryStream<Path> childrenStream, Predicate<Path> filter) {
124+
return StreamSupport
125+
.stream(childrenStream.spliterator(), false)
126+
.filter(filter)
131127
.sorted(options.pathComparator())
132128
.iterator();
133129
}

src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/ScanningOptions.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import java.util.function.Predicate;
66
import java.util.function.ToIntFunction;
77
import org.jspecify.annotations.NullMarked;
8-
import org.jspecify.annotations.Nullable;
98

109
@NullMarked
1110
public interface ScanningOptions {
@@ -16,7 +15,6 @@ public interface ScanningOptions {
1615

1716
Comparator<Path> pathComparator();
1817

19-
@Nullable
2018
Predicate<Path> pathFilter();
2119

2220
}

src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FileTreePrettyPrinterTest.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,4 @@ void prettyPrint_by_path_and_string_are_same() {
2121
assertThat(printer.prettyPrint(path)).isEqualTo(printer.prettyPrint(path.toString()));
2222
}
2323

24-
@Test
25-
void prettyPrintWithFilter_by_path_and_string_are_same() {
26-
var path = FileStructures.simpleDirectoryWithFilesAndFolders(root, 3, 3);
27-
28-
FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder()
29-
.customizeOptions(options -> options.filter(PathPredicates::isFile))
30-
.build();
31-
32-
assertThat(printer.prettyPrint(path)).isEqualTo(printer.prettyPrint(path.toString()));
33-
}
34-
3524
}

0 commit comments

Comments
 (0)