From 5073402b979d8312906562ce9dc41338661f9a98 Mon Sep 17 00:00:00 2001 From: Samuel SCHNEGG Date: Wed, 1 Oct 2025 16:43:40 +0200 Subject: [PATCH 1/2] Reworked filtering --- CHANGELOG.md | 1 + README.md | 16 ++-- .../example/CompleteExample.java | 70 +++++++++++++++ .../example/Filtering.java | 10 ++- .../PrettyPrintOptions.java | 34 ++++--- .../scanner/DefaultPathToTreeScanner.java | 28 +++--- .../scanner/ScanningOptions.java | 2 - .../FileTreePrettyPrinterTest.java | 11 --- .../jfiletreeprettyprinter/FilteringTest.java | 89 ++++++++++++------- 9 files changed, 182 insertions(+), 79 deletions(-) create mode 100644 src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/CompleteExample.java diff --git a/CHANGELOG.md b/CHANGELOG.md index f1cce56..dbf3fc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - `PathUtils` removed, `PathPredicates` rework - Line extension: empty string is permitted +- Filtering: split into distinct directories and files filters --- ## [0.0.4] - 2025-09-27 diff --git a/README.md b/README.md index fe75cdf..394f149 100644 --- a/README.md +++ b/README.md @@ -276,17 +276,23 @@ sorting/ ## Filtering Files and directories can be selectively included or excluded using a custom `Predicate`. -Filtering is **recursive by default**: directory's contents will always be traversed. -However, if a directory does not match and none of its children match, the directory itself will not be displayed. +Filtering is independant for files & directories. Files are filtered only if their parent directory pass the directory filter. +If none of some directory's children match, the directory is still displayed. The `PathPredicates` class provides several ready-to-use methods for creating common predicates, as well as a builder for creating more advanced predicates. ```java // Example: Filtering.java -var hasJavaExtensionPredicate = PathPredicates.builder().hasExtension("java").build(); +Predicate excludeDirWithNoJavaFiles = dir -> !PathPredicates.hasNameEndingWith(dir, "no_java_file"); +var isJavaFilePredicate = PathPredicates.builder().hasExtension("java").build(); + var prettyPrinter = FileTreePrettyPrinter.builder() - .customizeOptions(options -> options.filter(hasJavaExtensionPredicate)) - .build(); + .customizeOptions( + options -> options + .filterDirectories(excludeDirWithNoJavaFiles) + .filterFiles(hasJavaExtensionPredicate) + ) + .build(); ``` ``` filtering/ diff --git a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/CompleteExample.java b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/CompleteExample.java new file mode 100644 index 0000000..5f2e074 --- /dev/null +++ b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/CompleteExample.java @@ -0,0 +1,70 @@ +package io.github.computerdaddyguy.jfiletreeprettyprinter.example; + +import io.github.computerdaddyguy.jfiletreeprettyprinter.ChildLimitBuilder; +import io.github.computerdaddyguy.jfiletreeprettyprinter.FileTreePrettyPrinter; +import io.github.computerdaddyguy.jfiletreeprettyprinter.PathPredicates; +import io.github.computerdaddyguy.jfiletreeprettyprinter.PrettyPrintOptions.Sorts; +import java.nio.file.Path; +import java.util.function.Function; + +public class CompleteExample { + + public static void main(String[] args) { + + var rootFolder = "JFileTreePrettyPrinter"; + + var filterDir = PathPredicates.builder() + .pathTest(path -> !PathPredicates.hasName(path, ".git")) + .pathTest(path -> !PathPredicates.hasFullPathMatchingGlob(path, "**/.git")) + .pathTest(path -> !PathPredicates.hasFullPathMatchingGlob(path, "**/.github")) + .pathTest(path -> !PathPredicates.hasFullPathMatchingGlob(path, "**/.settings")) + .pathTest(path -> !PathPredicates.hasFullPathMatchingGlob(path, "**/src/example")) + .pathTest(path -> !PathPredicates.hasFullPathMatchingGlob(path, "**/src/test")) + .pathTest(path -> !PathPredicates.hasFullPathMatchingGlob(path, "**/target")) + .build(); + + var filterFiles = PathPredicates.builder() + .pathTest(path -> !PathPredicates.hasNameStartingWith(path, ".")) + .pathTest(path -> { + if (PathPredicates.hasParentMatching(path, parent -> PathPredicates.hasName(parent, "jfiletreeprettyprinter"))) { + return PathPredicates.hasName(path, "FileTreePrettyPrinter.java"); + } + return true; + }) + .build(); + + var childLimitFunction = ChildLimitBuilder.builder() + .limit(path -> PathPredicates.hasFullPathMatchingGlob(path, "**/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer"), 0) + .limit(path -> PathPredicates.hasFullPathMatchingGlob(path, "**/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner"), 0) + .build(); + + Function lineExtension = path -> { + if (PathPredicates.hasName(path, "JfileTreePrettyPrinter-structure.png")) { + return "\t// This image"; + } else if (PathPredicates.hasName(path, "FileTreePrettyPrinter.java")) { + return "\t// Main entry point"; + } else if (PathPredicates.hasName(path, "README.md")) { + return "\t\t// You're reading at this!"; + } else if (PathPredicates.hasName(path, "java")) { + return ""; + } + return null; + }; + + var prettyPrinter = FileTreePrettyPrinter.builder() + .customizeOptions( + options -> options + .withEmojis(true) + .withCompactDirectories(true) + .filterDirectories(filterDir) + .filterFiles(filterFiles) + .withChildLimit(childLimitFunction) + .withLineExtension(lineExtension) + .sort(Sorts.DIRECTORY_FIRST) + ) + .build(); + var tree = prettyPrinter.prettyPrint("."); + System.out.println(tree); + } + +} diff --git a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/Filtering.java b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/Filtering.java index 81d9699..ce92d81 100644 --- a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/Filtering.java +++ b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/Filtering.java @@ -2,13 +2,21 @@ import io.github.computerdaddyguy.jfiletreeprettyprinter.FileTreePrettyPrinter; import io.github.computerdaddyguy.jfiletreeprettyprinter.PathPredicates; +import java.nio.file.Path; +import java.util.function.Predicate; public class Filtering { public static void main(String[] args) { + Predicate excludeDirWithNoJavaFiles = dir -> !PathPredicates.hasNameEndingWith(dir, "no_java_file"); var hasJavaExtensionPredicate = PathPredicates.builder().hasExtension("java").build(); + var prettyPrinter = FileTreePrettyPrinter.builder() - .customizeOptions(options -> options.filter(hasJavaExtensionPredicate)) + .customizeOptions( + options -> options + .filterDirectories(excludeDirWithNoJavaFiles) + .filterFiles(hasJavaExtensionPredicate) + ) .build(); var tree = prettyPrinter.prettyPrint("src/example/resources/filtering"); diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java index 1850ca5..2bada28 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java @@ -297,25 +297,37 @@ public PrettyPrintOptions sort(Comparator pathComparator) { // ---------- Filtering ---------- - @Nullable - private Predicate pathFilter = null; + private Predicate dirFilter = dir -> true; + private Predicate fileFilter = dir -> true; @Override - @Nullable public Predicate pathFilter() { - return pathFilter; + return path -> PathPredicates.isDirectory(path) + ? dirFilter.test(path) + : fileFilter.test(path); + } + + /** + * Use a custom filter for retain only some directories. + * + * Directories that do not pass this filter will not be displayed. + * + * @param filter The filter to apply on directories, cannot be null + */ + public PrettyPrintOptions filterDirectories(@Nullable Predicate filter) { + this.dirFilter = Objects.requireNonNull(filter, "filter is null"); + return this; } /** - * Use a custom filter for retain only some files and/or directories. + * Use a custom filter for retain only some files. + * + * Files that do not pass this filter will not be displayed. * - * Filtering is recursive by default: directory's contents will always be traversed. - * However, if a directory does not match and none of its children match, the directory itself will not be displayed. - - * @param filter The filter, null to disable filtering + * @param filter The filter to apply on files, cannot be null */ - public PrettyPrintOptions filter(@Nullable Predicate filter) { - this.pathFilter = filter; + public PrettyPrintOptions filterFiles(@Nullable Predicate filter) { + this.fileFilter = Objects.requireNonNull(filter, "filter is null"); return this; } diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/DefaultPathToTreeScanner.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/DefaultPathToTreeScanner.java index f903a6c..1524170 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/DefaultPathToTreeScanner.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/DefaultPathToTreeScanner.java @@ -1,6 +1,5 @@ package io.github.computerdaddyguy.jfiletreeprettyprinter.scanner; -import io.github.computerdaddyguy.jfiletreeprettyprinter.PathPredicates; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.DirectoryEntry; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.FileEntry; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.MaxDepthReachEntry; @@ -35,14 +34,14 @@ public TreeEntry scan(Path fileOrDir) { } @Nullable - private TreeEntry handle(int depth, Path fileOrDir, @Nullable Predicate filter) { + private TreeEntry handle(int depth, Path fileOrDir, Predicate filter) { return fileOrDir.toFile().isDirectory() ? handleDirectory(depth, fileOrDir, filter) : handleFile(fileOrDir); } @Nullable - private TreeEntry handleDirectory(int depth, Path dir, @Nullable Predicate filter) { + private TreeEntry handleDirectory(int depth, Path dir, Predicate filter) { if (depth >= options.getMaxDepth()) { var maxDepthEntry = new MaxDepthReachEntry(depth); @@ -58,15 +57,15 @@ private TreeEntry handleDirectory(int depth, Path dir, @Nullable Predicate throw new UncheckedIOException("Unable to list files for directory: " + dir, e); } - // Filter is active and no children match - if (depth > 0 && filter != null && childEntries.isEmpty() && !filter.test(dir)) { - return null; // Do no show this directory at all - } +// // Filter is active and no children match +// if (depth > 0 && filter != null && childEntries.isEmpty() && !filter.test(dir)) { +// return null; // Do no show this directory at all +// } return new DirectoryEntry(dir, childEntries); } - private List handleDirectoryChildren(int depth, Path dir, Iterator pathIterator, @Nullable Predicate filter) { + private List handleDirectoryChildren(int depth, Path dir, Iterator pathIterator, Predicate filter) { var childEntries = new ArrayList(); int maxChildEntries = options.getChildLimit().applyAsInt(dir); @@ -95,7 +94,7 @@ private List handleDirectoryChildren(int depth, Path dir, Iterator handleLeftOverChildren(int depth, Iterator pathIterator, @Nullable Predicate filter) { + private List handleLeftOverChildren(int depth, Iterator pathIterator, Predicate filter) { var childEntries = new ArrayList(); if (filter == null) { @@ -121,13 +120,10 @@ private List handleLeftOverChildren(int depth, Iterator pathIte return childEntries; } - private Iterator directoryStreamToIterator(DirectoryStream childrenStream, @Nullable Predicate filter) { - var stream = StreamSupport.stream(childrenStream.spliterator(), false); - if (filter != null) { - var recursiveFilter = PathPredicates.builder().isDirectory().build().or(filter); - stream = stream.filter(recursiveFilter); - } - return stream + private Iterator directoryStreamToIterator(DirectoryStream childrenStream, Predicate filter) { + return StreamSupport + .stream(childrenStream.spliterator(), false) + .filter(filter) .sorted(options.pathComparator()) .iterator(); } diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/ScanningOptions.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/ScanningOptions.java index f4f5eab..ffdf9b2 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/ScanningOptions.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/ScanningOptions.java @@ -5,7 +5,6 @@ import java.util.function.Predicate; import java.util.function.ToIntFunction; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; @NullMarked public interface ScanningOptions { @@ -16,7 +15,6 @@ public interface ScanningOptions { Comparator pathComparator(); - @Nullable Predicate pathFilter(); } diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FileTreePrettyPrinterTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FileTreePrettyPrinterTest.java index f257345..a11e879 100644 --- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FileTreePrettyPrinterTest.java +++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FileTreePrettyPrinterTest.java @@ -21,15 +21,4 @@ void prettyPrint_by_path_and_string_are_same() { assertThat(printer.prettyPrint(path)).isEqualTo(printer.prettyPrint(path.toString())); } - @Test - void prettyPrintWithFilter_by_path_and_string_are_same() { - var path = FileStructures.simpleDirectoryWithFilesAndFolders(root, 3, 3); - - FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() - .customizeOptions(options -> options.filter(PathPredicates::isFile)) - .build(); - - assertThat(printer.prettyPrint(path)).isEqualTo(printer.prettyPrint(path.toString())); - } - } diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FilteringTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FilteringTest.java index 1a46e3b..575ed8c 100644 --- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FilteringTest.java +++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FilteringTest.java @@ -3,16 +3,22 @@ import static org.assertj.core.api.Assertions.assertThat; import io.github.computerdaddyguy.jfiletreeprettyprinter.PrettyPrintOptions.Sorts; +import io.github.computerdaddyguy.jfiletreeprettyprinter.util.FileStructureCreator; +import java.nio.file.Path; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; class FilteringTest { + @TempDir + private Path root; + @Test - void example() { + void example_file() { var filter = PathPredicates.builder().hasExtension("java").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() - .customizeOptions(options -> options.filter(filter)) + .customizeOptions(options -> options.filterFiles(filter)) .build(); var result = printer.prettyPrint("src/example/resources/filtering"); @@ -22,27 +28,30 @@ void example() { │ ├─ file_B.java │ └─ file_E.java ├─ dir_with_nested_java_files/ - │ └─ nested_dir_with_java_files/ - │ ├─ file_G.java - │ └─ file_J.java + │ ├─ nested_dir_with_java_files/ + │ │ ├─ file_G.java + │ │ └─ file_J.java + │ └─ nested_dir_with_no_java_file/ + ├─ dir_with_no_java_file/ └─ file_A.java"""; assertThat(result).isEqualTo(expected); } @Test - void example_dir_match() { + void example_dir() { var filter = PathPredicates.builder().hasNameEndingWith("no_java_file").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() - .customizeOptions(options -> options.filter(filter)) + .customizeOptions(options -> options.filterDirectories(filter)) .build(); var result = printer.prettyPrint("src/example/resources/filtering"); var expected = """ filtering/ - ├─ dir_with_nested_java_files/ - │ └─ nested_dir_with_no_java_file/ - └─ dir_with_no_java_file/"""; + ├─ dir_with_no_java_file/ + │ ├─ file_M.cpp + │ └─ file_N.ts + └─ file_A.java"""; assertThat(result).isEqualTo(expected); } @@ -52,14 +61,16 @@ void example_and_sorting() { var filter = PathPredicates.builder().hasExtension("java").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.sort(Sorts.BY_NAME.reversed())) - .customizeOptions(options -> options.filter(filter)) + .customizeOptions(options -> options.filterFiles(filter)) .build(); var result = printer.prettyPrint("src/example/resources/filtering"); var expected = """ filtering/ ├─ file_A.java + ├─ dir_with_no_java_file/ ├─ dir_with_nested_java_files/ + │ ├─ nested_dir_with_no_java_file/ │ └─ nested_dir_with_java_files/ │ ├─ file_J.java │ └─ file_G.java @@ -75,7 +86,7 @@ void example_childLimit_1() { var filter = PathPredicates.builder().hasExtension("java").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.withChildLimit(1)) - .customizeOptions(options -> options.filter(filter)) + .customizeOptions(options -> options.filterFiles(filter)) .build(); var result = printer.prettyPrint("src/example/resources/filtering"); @@ -84,7 +95,7 @@ void example_childLimit_1() { ├─ dir_with_java_files/ │ ├─ file_B.java │ └─ ... (1 file skipped) - └─ ... (1 file and 1 directory skipped)"""; + └─ ... (1 file and 2 directories skipped)"""; assertThat(result).isEqualTo(expected); } @@ -94,7 +105,7 @@ void example_childLimit_2() { var filter = PathPredicates.builder().hasExtension("java").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.withChildLimit(2)) - .customizeOptions(options -> options.filter(filter)) + .customizeOptions(options -> options.filterFiles(filter)) .build(); var result = printer.prettyPrint("src/example/resources/filtering"); @@ -104,10 +115,11 @@ void example_childLimit_2() { │ ├─ file_B.java │ └─ file_E.java ├─ dir_with_nested_java_files/ - │ └─ nested_dir_with_java_files/ - │ ├─ file_G.java - │ └─ file_J.java - └─ ... (1 file skipped)"""; + │ ├─ nested_dir_with_java_files/ + │ │ ├─ file_G.java + │ │ └─ file_J.java + │ └─ nested_dir_with_no_java_file/ + └─ ... (1 file and 1 directory skipped)"""; assertThat(result).isEqualTo(expected); } @@ -117,7 +129,7 @@ void example_childLimit_3() { var filter = PathPredicates.builder().hasExtension("java").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.withChildLimit(3)) - .customizeOptions(options -> options.filter(filter)) + .customizeOptions(options -> options.filterFiles(filter)) .build(); var result = printer.prettyPrint("src/example/resources/filtering"); @@ -127,32 +139,43 @@ void example_childLimit_3() { │ ├─ file_B.java │ └─ file_E.java ├─ dir_with_nested_java_files/ - │ └─ nested_dir_with_java_files/ - │ ├─ file_G.java - │ └─ file_J.java - └─ file_A.java"""; + │ ├─ nested_dir_with_java_files/ + │ │ ├─ file_G.java + │ │ └─ file_J.java + │ └─ nested_dir_with_no_java_file/ + ├─ dir_with_no_java_file/ + └─ ... (1 file skipped)"""; assertThat(result).isEqualTo(expected); } @Test void example_compact_dir() { + // @formatter:off + var path = FileStructureCreator + .forTargetPath(root) + .createAndEnterDirectory("level1") + .createAndEnterDirectory("level2") + .createAndEnterDirectory("level3") + .createAndEnterDirectory("level4") + .createFiles("file4#", 3) + .up() // level4 + .up() // level3 + .up() // level2 + .up() // level1 + .getPath(); + // @formatter:on + var filter = PathPredicates.builder().hasExtension("java").build(); FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() .customizeOptions(options -> options.withCompactDirectories(true)) - .customizeOptions(options -> options.filter(filter)) + .customizeOptions(options -> options.filterFiles(filter)) .build(); - var result = printer.prettyPrint("src/example/resources/filtering"); + var result = printer.prettyPrint(path); var expected = """ - filtering/ - ├─ dir_with_java_files/ - │ ├─ file_B.java - │ └─ file_E.java - ├─ dir_with_nested_java_files/nested_dir_with_java_files/ - │ ├─ file_G.java - │ └─ file_J.java - └─ file_A.java"""; + targetPath/ + └─ level1/level2/level3/level4/"""; assertThat(result).isEqualTo(expected); } From 7b49ceea19e99bba4804bf6ecfea194902b3710c Mon Sep 17 00:00:00 2001 From: Samuel SCHNEGG Date: Wed, 1 Oct 2025 16:52:49 +0200 Subject: [PATCH 2/2] Fix sonar --- .../example/CompleteExample.java | 2 -- .../renderer/DefaultTreeEntryRenderer.java | 21 ++++++++----- .../scanner/DefaultPathToTreeScanner.java | 30 ++++++------------- .../LineExtensionTest.java | 22 ++++++++++++++ 4 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/CompleteExample.java b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/CompleteExample.java index 5f2e074..a231d26 100644 --- a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/CompleteExample.java +++ b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/CompleteExample.java @@ -11,8 +11,6 @@ public class CompleteExample { public static void main(String[] args) { - var rootFolder = "JFileTreePrettyPrinter"; - var filterDir = PathPredicates.builder() .pathTest(path -> !PathPredicates.hasName(path, ".git")) .pathTest(path -> !PathPredicates.hasFullPathMatchingGlob(path, "**/.git")) diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/DefaultTreeEntryRenderer.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/DefaultTreeEntryRenderer.java index 709c8e3..7e5694f 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/DefaultTreeEntryRenderer.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/DefaultTreeEntryRenderer.java @@ -13,6 +13,7 @@ import java.util.Objects; import java.util.Optional; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; @NullMarked class DefaultTreeEntryRenderer implements TreeEntryRenderer { @@ -41,14 +42,17 @@ private String renderTree(TreeEntry entry, Depth depth) { private String renderDirectory(Depth depth, DirectoryEntry dirEntry, List compactPaths) { - Optional extension = null; + boolean extensionEvaluated = false; + String extension = null; + if (options.areCompactDirectoriesUsed() && !depth.isRoot() && dirEntry.getEntries().size() == 1 && dirEntry.getEntries().get(0) instanceof DirectoryEntry childDirEntry) { extension = computeLineExtension(dirEntry.getDir()); - if (extension.isEmpty()) { + extensionEvaluated = true; + if (extension == null) { var newCompactPaths = new ArrayList<>(compactPaths); newCompactPaths.add(childDirEntry.getDir()); return renderDirectory(depth, childDirEntry, newCompactPaths); @@ -56,10 +60,10 @@ private String renderDirectory(Depth depth, DirectoryEntry dirEntry, List } var line = lineRenderer.renderDirectoryBegin(depth, dirEntry, compactPaths); - if (extension == null) { + if (!extensionEvaluated) { extension = computeLineExtension(dirEntry.getDir()); } - line += extension.orElse(""); + line += Optional.ofNullable(extension).orElse(""); var childIt = dirEntry.getEntries().iterator(); @@ -81,16 +85,17 @@ private String renderDirectory(Depth depth, DirectoryEntry dirEntry, List return line + childLines.toString(); } - private Optional computeLineExtension(Path path) { + @Nullable + private String computeLineExtension(Path path) { if (options.getLineExtension() == null) { - return Optional.empty(); + return null; } - return Optional.ofNullable(options.getLineExtension().apply(path)); + return options.getLineExtension().apply(path); } private String renderFile(Depth depth, FileEntry fileEntry) { var line = lineRenderer.renderFile(depth, fileEntry); - line += computeLineExtension(fileEntry.getFile()).orElse(""); + line += Optional.ofNullable(computeLineExtension(fileEntry.getFile())).orElse(""); return line; } diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/DefaultPathToTreeScanner.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/DefaultPathToTreeScanner.java index 1524170..ee1666e 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/DefaultPathToTreeScanner.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/scanner/DefaultPathToTreeScanner.java @@ -57,11 +57,6 @@ private TreeEntry handleDirectory(int depth, Path dir, Predicate filter) { throw new UncheckedIOException("Unable to list files for directory: " + dir, e); } -// // Filter is active and no children match -// if (depth > 0 && filter != null && childEntries.isEmpty() && !filter.test(dir)) { -// return null; // Do no show this directory at all -// } - return new DirectoryEntry(dir, childEntries); } @@ -97,24 +92,17 @@ private List handleDirectoryChildren(int depth, Path dir, Iterator handleLeftOverChildren(int depth, Iterator pathIterator, Predicate filter) { var childEntries = new ArrayList(); - if (filter == null) { - var skippedChildren = new ArrayList(); - pathIterator.forEachRemaining(skippedChildren::add); + var skippedChildren = new ArrayList(); + while (pathIterator.hasNext()) { + var child = pathIterator.next(); + var childEntry = handle(depth + 1, child, filter); + if (childEntry != null) { // Is null if no children file is retained by filter + skippedChildren.add(child); + } + } + if (!skippedChildren.isEmpty()) { var childrenSkippedEntry = new SkippedChildrenEntry(skippedChildren); childEntries.add(childrenSkippedEntry); - } else { - var skippedChildren = new ArrayList(); - while (pathIterator.hasNext()) { - var child = pathIterator.next(); - var childEntry = handle(depth + 1, child, filter); - if (childEntry != null) { // Is null if no children file is retained by filter - skippedChildren.add(child); - } - } - if (!skippedChildren.isEmpty()) { - var childrenSkippedEntry = new SkippedChildrenEntry(skippedChildren); - childEntries.add(childrenSkippedEntry); - } } return childEntries; diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java index 7010040..f60d2b8 100644 --- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java +++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java @@ -88,6 +88,28 @@ void compact_dir_first_dir() { compact_dir(lineExtension, expected); } + @Test + void compact_dir_empty_string_workds() { + + Function lineExtension = p -> { + if (PathPredicates.hasName(p, "dirA")) { + return ""; + } + return null; + }; + + var expected = """ + targetPath/ + ├─ dirA/ + │ └─ dirB/dirC/ + │ ├─ file1 + │ ├─ file2 + │ └─ file3 + └─ dirX/"""; + + compact_dir(lineExtension, expected); + } + @Test void compact_dir_middle_dir() {