Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---
## [0.0.5] - Unreleased

### Added
- New path predicates: `hasParentMatching`, `hasAncestorMatching`, `hasDirectChildMatching`, `hasDescendantMatching`, `hasSiblingMatching`, `hasFullPathMatchingGlob`, `hasFullPathMatching`, `hasNameMatchingGlob`

### Changed
- `PathUtils` removed, `PathPredicates`rework

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

Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,14 @@ child_limit_static/
Or you can also set a limitation function, to dynamically choose the number of children displayed in each directory.
It avoids cluttering the whole console with known large folders (e.g. `node_modules`) but continue to pretty print normally other folders.

Use the `ChildLimitBuilder` and `PathPredicates` classes to help you build the limit function that fits your needs..
Use the `ChildLimitBuilder` and `PathPredicates` classes to help you build the limit function that fits your needs.

```java
// Example: ChildLimitDynamic.java
var isNodeModulePredicate = PathPredicates.builder().hasName("node_modules").build();
var childLimit = ChildLimitBuilder.builder()
.defaultLimit(ChildLimitBuilder.UNLIMITED)
.limit(PathPredicates.hasName("node_modules"), 0)
.limit(isNodeModulePredicate, 0)
.build();
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.withChildLimit(childLimit))
Expand Down Expand Up @@ -278,12 +279,13 @@ Files and directories can be selectively included or excluded using a custom `Pr
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.

The `PathPredicates` class provides several ready-to-use `Predicate<Path>` implementations for common cases, as well as a builder for creating more advanced predicates.
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();
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.filter(PathPredicates.hasExtension("java")))
.customizeOptions(options -> options.filter(hasJavaExtensionPredicate))
.build();
```
```
Expand All @@ -308,16 +310,16 @@ If the function returns `null`, nothing is added.
```java
// Example: LineExtension.java
Function<Path, String> lineExtension = path -> {
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "api")) {
if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/api")) {
return "\t\t\t// All API code: controllers, etc.";
}
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "domain")) {
if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/domain")) {
return "\t\t\t// All domain code: value objects, etc.";
}
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "infra")) {
if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/infra")) {
return "\t\t\t// All infra code: database, email service, etc.";
}
if (PathUtils.isFile(path) && PathUtils.hasName(path, "application.properties")) {
if (PathPredicates.hasNameMatchingGlob(path, "*.properties")) {
return "\t// Config file";
}
return null;
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
- [x] Option: Line extension (=additional text after the file name)

## To do
- [ ] More `PathPredicates` functions!
- [x] More `PathPredicates` functions!
- [ ] Option: custom emojis

## Backlog / To analyze / To implement if requested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
public class ChildLimitDynamic {

public static void main(String[] args) {
var isNodeModulePredicate = PathPredicates.builder().hasName("node_modules").build();
var childLimit = ChildLimitBuilder.builder()
.defaultLimit(ChildLimitBuilder.UNLIMITED)
.limit(PathPredicates.hasName("node_modules"), 0)
.limit(isNodeModulePredicate, 0)
.build();
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.withChildLimit(childLimit))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
public class Filtering {

public static void main(String[] args) {
var hasJavaExtensionPredicate = PathPredicates.builder().hasExtension("java").build();
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.filter(PathPredicates.hasExtension("java")))
.customizeOptions(options -> options.filter(hasJavaExtensionPredicate))
.build();

var tree = prettyPrinter.prettyPrint("src/example/resources/filtering");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
package io.github.computerdaddyguy.jfiletreeprettyprinter.example;

import io.github.computerdaddyguy.jfiletreeprettyprinter.FileTreePrettyPrinter;
import io.github.computerdaddyguy.jfiletreeprettyprinter.PathUtils;
import io.github.computerdaddyguy.jfiletreeprettyprinter.PathPredicates;
import java.nio.file.Path;
import java.util.function.Function;

public class LineExtension {

public static void main(String[] args) {
Function<Path, String> lineExtension = path -> {
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "api")) {
if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/api")) {
return "\t\t\t// All API code: controllers, etc.";
}
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "domain")) {
if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/domain")) {
return "\t\t\t// All domain code: value objects, etc.";
}
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "infra")) {
if (PathPredicates.hasFullPathMatchingGlob(path, "**/src/main/java/infra")) {
return "\t\t\t// All infra code: database, email service, etc.";
}
if (PathUtils.isFile(path) && PathUtils.hasName(path, "application.properties")) {
if (PathPredicates.hasNameMatchingGlob(path, "*.properties")) {
return "\t// Config file";
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
* <pre>{@code
* var childLimit = ChildLimitBuilder.builder()
* .defaultLimit(ChildLimit.UNLIMITED) // unlimited unless specified
* .limit(path -> PathUtils.hasName("bigDir"), 10) // max 10 children in "bigDir"
* .limit(path -> PathUtils.hasName("emptyDir"), 0) // disallow children in "emptyDir"
* .limit(path -> PathPredicates.hasName(path, "bigDir"), 10) // max 10 children in "bigDir"
* .limit(path -> PathPredicates.hasName(path, "emptyDir"), 0) // disallow children in "emptyDir"
* .build();
*
* }</pre>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.io.File;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -75,6 +76,32 @@ public PathPredicateBuilder fileTest(Predicate<File> predicate) {
return pathTest(path -> predicate.test(path.toFile()));
}

// ---------- PathMatcher ----------

/**
* Adds a condition that tests whether the path matches the specified glob pattern.
*
* @param glob the glob pattern to match; must not be {@code null}
*
* @return this builder for chaining
*/
public PathPredicateBuilder hasFullPathMatchingGlob(String glob) {
Objects.requireNonNull(glob, "glob is null");
return pathTest(path -> PathPredicates.hasFullPathMatchingGlob(path, glob));
}

/**
* Adds a condition that tests whether the path matches the provided {@link PathMatcher}.
*
* @param matcher the {@code PathMatcher} to use; must not be {@code null}
*
* @return this builder for chaining
*/
public PathPredicateBuilder hasFullPathMatching(PathMatcher matcher) {
Objects.requireNonNull(matcher, "matcher is null");
return pathTest(path -> PathPredicates.hasFullPathMatching(path, matcher));
}

// ---------- Name ----------

/**
Expand All @@ -86,7 +113,8 @@ public PathPredicateBuilder fileTest(Predicate<File> predicate) {
* @return this builder for chaining
*/
public PathPredicateBuilder hasName(String name) {
return pathTest(PathPredicates.hasName(name));
Objects.requireNonNull(name, "name is null");
return pathTest(path -> PathPredicates.hasName(path, name));
}

/**
Expand All @@ -98,7 +126,8 @@ public PathPredicateBuilder hasName(String name) {
* @return this builder for chaining
*/
public PathPredicateBuilder hasNameIgnoreCase(String name) {
return pathTest(PathPredicates.hasNameIgnoreCase(name));
Objects.requireNonNull(name, "name is null");
return pathTest(path -> PathPredicates.hasNameIgnoreCase(path, name));
}

/**
Expand All @@ -109,7 +138,28 @@ public PathPredicateBuilder hasNameIgnoreCase(String name) {
* @return this builder for chaining
*/
public PathPredicateBuilder hasNameMatching(Pattern pattern) {
return pathTest(PathPredicates.hasNameMatching(pattern));
Objects.requireNonNull(pattern, "pattern is null");
return pathTest(path -> PathPredicates.hasNameMatching(path, pattern));
}

/**
* Adds a condition that tests whether the file name of the given path
* matches the specified glob pattern.
*
* <p><b>Note:</b> Only the file name (the last element of the path) is tested,
* not the entire path. For example, {@code "*.txt"} will match {@code "file.txt"}.
*
* <p>The glob syntax follows {@link java.nio.file.FileSystem#getPathMatcher(String)} conventions.
*
* @param glob the glob pattern to match against the file name; must not be {@code null}
*
* @return this builder for chaining
*
* @see #hasFullPathMatchingGlob(String)
*/
public PathPredicateBuilder hasNameMatchingGlob(String glob) {
Objects.requireNonNull(glob, "glob is null");
return pathTest(path -> PathPredicates.hasNameMatchingGlob(path, glob));
}

/**
Expand All @@ -120,7 +170,8 @@ public PathPredicateBuilder hasNameMatching(Pattern pattern) {
* @return this builder for chaining
*/
public PathPredicateBuilder hasNameEndingWith(String suffix) {
return pathTest(PathPredicates.hasNameEndingWith(suffix));
Objects.requireNonNull(suffix, "suffix is null");
return pathTest(path -> PathPredicates.hasNameEndingWith(path, suffix));
}

/**
Expand All @@ -135,7 +186,8 @@ public PathPredicateBuilder hasNameEndingWith(String suffix) {
* @return this builder for chaining
*/
public PathPredicateBuilder hasExtension(String extension) {
return pathTest(PathPredicates.hasExtension(extension));
Objects.requireNonNull(extension, "extension is null");
return pathTest(path -> PathPredicates.hasExtension(path, extension));
}

// ---------- Type ----------
Expand All @@ -146,7 +198,7 @@ public PathPredicateBuilder hasExtension(String extension) {
* @return this builder for chaining
*/
public PathPredicateBuilder isDirectory() {
return pathTest(PathPredicates.isDirectory());
return pathTest(PathPredicates::isDirectory);
}

/**
Expand All @@ -155,7 +207,91 @@ public PathPredicateBuilder isDirectory() {
* @return this builder for chaining
*/
public PathPredicateBuilder isFile() {
return pathTest(PathPredicates.isFile());
return pathTest(PathPredicates::isFile);
}

// ---------- Hierarchy ----------

/**
* Adds a condition that tests the direct parent of the path.
*
* @param parentPredicate the predicate to apply on the direct parent
*
* @return this builder for chaining
*
* @see PathPredicates#hasParentMatching(Predicate)
*/
public PathPredicateBuilder hasParentMatching(Predicate<Path> parentPredicate) {
Objects.requireNonNull(parentPredicate, "parentPredicate is null");
return pathTest(path -> PathPredicates.hasParentMatching(path, parentPredicate));
}

/**
* Adds a condition that tests all ancestors of the path (stopping at the first match).
*
* The condition is satisfied if the given {@code ancestorPredicate} evaluates
* to {@code true} for any ancestor in the {@link Path#getParent()} chain.
*
* @param ancestorPredicate the predicate to apply on each ancestor
*
* @return this builder for chaining
*
* @see PathPredicates#hasAncestorMatching(Predicate)
*/
public PathPredicateBuilder hasAncestorMatching(Predicate<Path> ancestorPredicate) {
Objects.requireNonNull(ancestorPredicate, "ancestorPredicate is null");
return pathTest(path -> PathPredicates.hasAncestorMatching(path, ancestorPredicate));
}

/**
* Adds a condition that tests the direct children of the path.
*
* The condition is satisfied if the given {@code childPredicate} evaluates
* to {@code true} for at least one direct child of the tested path.
*
* @param childPredicate the predicate to apply on each direct child
*
* @return this builder for chaining
*
* @see PathPredicates#hasDirectChildMatching(Predicate)
*/
public PathPredicateBuilder hasDirectChildMatching(Predicate<Path> childPredicate) {
Objects.requireNonNull(childPredicate, "childPredicate is null");
return pathTest(path -> PathPredicates.hasDirectChildMatching(path, childPredicate));
}

/**
* Adds a condition that tests all descendants of the path (children at any depth).
*
* The condition is satisfied if the given {@code descendantPredicate} evaluates
* to {@code true} for at least one descendant in the directory tree.
*
* @param descendantPredicate the predicate to apply on each descendant
*
* @return this builder for chaining
*
* @see PathPredicates#hasDescendantMatching(Predicate)
*/
public PathPredicateBuilder hasDescendantMatching(Predicate<Path> descendantPredicate) {
Objects.requireNonNull(descendantPredicate, "descendantPredicate is null");
return pathTest(path -> PathPredicates.hasDescendantMatching(path, descendantPredicate));
}

/**
* Adds a condition that tests the siblings of the path.
*
* The condition is satisfied if the given {@code siblingPredicate} evaluates
* to {@code true} for at least one sibling of the tested path.
*
* @param siblingPredicate the predicate to apply on each sibling
*
* @return this builder for chaining
*
* @see PathPredicates#hasSiblingMatching(Predicate)
*/
public PathPredicateBuilder hasSiblingMatching(Predicate<Path> siblingPredicate) {
Objects.requireNonNull(siblingPredicate, "siblingPredicate is null");
return pathTest(path -> PathPredicates.hasSiblingMatching(path, siblingPredicate));
}

}
Loading
Loading