> extensions;
+
+ /* package */ LineExtensionBuilder() {
+ this.extensions = new ArrayList<>();
+ }
+
+ /**
+ * Builds the final function mapping a {@link Path} to an extension string.
+ *
+ * The function applies the registered rules in insertion order.
+ * The first rule returning a non-{@code null} value determines the extension.
+ * If none match, the function returns {@code null}.
+ *
+ * @return a function mapping paths to extensions
+ */
+ public Function build() {
+ var immutExtensions = List.copyOf(extensions);
+ return path -> {
+ String result = LineExtensions.NO_EXTENSION;
+ for (var rule : immutExtensions) {
+ result = rule.apply(path);
+ if (!Objects.equals(result, LineExtensions.NO_EXTENSION)) {
+ break;
+ }
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Adds a custom line extension rule.
+ *
+ * The function should return either:
+ *
+ * - {@code null} to indicate "no extension".
+ * - an empty string to force a line break.
+ * - a non-empty string to append after the path.
+ *
+ *
+ * @param lineExtension a function mapping paths to extensions (non-null)
+ * @return this builder (for chaining)
+ * @throws NullPointerException if {@code lineExtension} is null
+ */
+ public LineExtensionBuilder add(Function lineExtension) {
+ Objects.requireNonNull(lineExtension, "lineExtension is null");
+ this.extensions.add(lineExtension);
+ return this;
+ }
+
+ /**
+ * Adds a rule that appends the given extension when the matcher matches.
+ *
+ * If the matcher does not match, the rule returns {@code null}.
+ *
+ * @param pathMatcher the matcher to test paths against (non-null)
+ * @param extension the extension string to return when matched
+ * @return this builder (for chaining)
+ * @throws NullPointerException if {@code pathMatcher} is null
+ */
+ public LineExtensionBuilder add(PathMatcher pathMatcher, String extension) {
+ Objects.requireNonNull(pathMatcher, "pathMatcher is null");
+ return add(path -> pathMatcher.matches(path) ? extension : LineExtensions.NO_EXTENSION);
+ }
+
+ /**
+ * Adds a rule that forces a line break (instead of appending text)
+ * whenever the given matcher matches.
+ *
+ * @param pathMatcher the matcher to test paths against (non-null)
+ * @return this builder (for chaining)
+ */
+ public LineExtensionBuilder addLineBreak(PathMatcher pathMatcher) {
+ return add(pathMatcher, LineExtensions.LINE_BREAK_EXTENSION);
+ }
+
+}
diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensions.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensions.java
new file mode 100644
index 0000000..efce6d4
--- /dev/null
+++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensions.java
@@ -0,0 +1,53 @@
+package io.github.computerdaddyguy.jfiletreeprettyprinter;
+
+/**
+ * Utility class providing constants and factory methods for creating
+ * {@link LineExtensionBuilder} instances used to define per-path line extensions
+ * in pretty-printed file trees.
+ *
+ * Line extensions are optional text fragments appended to specific lines
+ * in the printed tree. They can be used to add comments, annotations, or
+ * formatting cues.
+ *
+ *
+ *
+ * Two special constants are provided:
+ *
+ *
+ * - {@link #NO_EXTENSION} β indicates no extension for a given path.
+ * - {@link #LINE_BREAK_EXTENSION} β indicates a forced line break,
+ * useful for splitting compact directory chains.
+ *
+ *
+ * Example usage:
+ * {@code
+ * var lineExt = LineExtensions.builder()
+ * .add(PathMatchers.hasName("README.md"), "\t// Project documentation")
+ * .addLineBreak(PathMatchers.hasRelativePathMatchingGlob(".", "src/main/java"))
+ * .build();
+ * }
+ *
+ * @see LineExtensionBuilder
+ */
+public final class LineExtensions {
+
+ /** Indicates that no extension should be applied to the line ({@code null}). */
+ public static final String NO_EXTENSION = null;
+
+ /** Indicates a forced line break (empty string). */
+ public static final String LINE_BREAK_EXTENSION = "";
+
+ private LineExtensions() {
+ // Helper class
+ }
+
+ /**
+ * Returns a new {@link LineExtensionBuilder}.
+ *
+ * @return a fresh builder instance
+ */
+ public static LineExtensionBuilder builder() {
+ return new LineExtensionBuilder();
+ }
+
+}
diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathSortBuilder.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathSortBuilder.java
new file mode 100644
index 0000000..0745e40
--- /dev/null
+++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathSortBuilder.java
@@ -0,0 +1,138 @@
+package io.github.computerdaddyguy.jfiletreeprettyprinter;
+
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.ToIntFunction;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * A builder for creating a {@link Comparator Comparator<Path>} that defines
+ * a custom sorting order for file system paths based on rule precedence.
+ *
+ * Each rule assigns an integer "precedence" value to paths. The first rule
+ * that matches a path determines its precedence. Paths are then sorted by
+ * ascending precedence value (lower values come first), followed by an
+ * alphabetical fallback comparison.
+ *
+ *
+ *
+ * Predefined precedence constants:
+ *
+ *
+ * - {@link #HIGHEST_PRECEDENCE} ({@code Integer.MIN_VALUE}) β top priority
+ * - {@link #DEFAULT_PRECEDENCE} ({@code 0}) β default order
+ * - {@link #LOWEST_PRECEDENCE} ({@code Integer.MAX_VALUE}) β last priority
+ *
+ *
+ * Example usage:
+ * {@code
+ * var customSort = PathSortBuilder.newInstance()
+ * .addFirst(PathMatchers.hasName("README.md")) // always first
+ * .addLast(PathMatchers.hasName("target")) // always last
+ * .add(path -> path.toString().contains("core") ? -10 : 0) // custom priority rule
+ * .build();
+ *
+ * var printer = FileTreePrettyPrinter.builder()
+ * .customizeOptions(options -> options.sort(customSort))
+ * .build();
+ * }
+ */
+@NullMarked
+public class PathSortBuilder {
+
+ /** Highest possible precedence β items appear first. */
+ public static final int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
+
+ /** Default precedence (neutral value). */
+ public static final int DEFAULT_PRECEDENCE = 0;
+
+ /** Lowest possible precedence β items appear last. */
+ public static final int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
+
+ private List> orders;
+
+ /* package */ PathSortBuilder() {
+ this.orders = new ArrayList<>();
+ }
+
+ /**
+ * Builds the final {@link Comparator Comparator<Path>} based on the configured rules.
+ *
+ * Rules are tested in the order they were added. The first matching rule
+ * having a result different than {@link DEFAULT_PRECEDENCE} (meaning, {@code 0})
+ * determines the precedence value for a given path. Paths are sorted by
+ * this precedence, and then alphabetically as a tiebreaker.
+ *
+ *
+ * @return a comparator defining the final path order
+ */
+ public Comparator build() {
+ var immutOrders = List.copyOf(orders);
+ Function finalFunction = path -> {
+ int result = DEFAULT_PRECEDENCE;
+ for (var rule : immutOrders) {
+ result = rule.applyAsInt(path);
+ if (result != DEFAULT_PRECEDENCE) {
+ break;
+ }
+ }
+ return result;
+ };
+ return Comparator.comparing(finalFunction).thenComparing(PathSorts.ALPHABETICAL);
+ }
+
+ /**
+ * Adds a custom rule function defining a precedence for a path.
+ *
+ * @param order a function returning a precedence value
+ * @return this builder for chaining
+ *
+ * @throws NullPointerException if {@code order} is null
+ */
+ public PathSortBuilder add(ToIntFunction order) {
+ Objects.requireNonNull(order, "order is null");
+ this.orders.add(order);
+ return this;
+ }
+
+ /**
+ * Adds a rule that assigns a precedence value to all paths matching
+ * the specified {@link PathMatcher}.
+ *
+ * @param pathMatcher the matcher to test paths
+ * @param order the precedence value to assign
+ * @return this builder for chaining
+ *
+ * @throws NullPointerException if {@code pathMatcher} is null
+ */
+ public PathSortBuilder add(PathMatcher pathMatcher, int order) {
+ Objects.requireNonNull(pathMatcher, "pathMatcher is null");
+ return add(path -> pathMatcher.matches(path) ? order : DEFAULT_PRECEDENCE);
+ }
+
+ /**
+ * Adds a rule that forces matching paths to appear first in the sort order.
+ *
+ * @param pathMatcher the matcher identifying high-priority paths
+ * @return this builder for chaining
+ */
+ public PathSortBuilder addFirst(PathMatcher pathMatcher) {
+ return add(pathMatcher, HIGHEST_PRECEDENCE);
+ }
+
+ /**
+ * Adds a rule that forces matching paths to appear last in the sort order.
+ *
+ * @param pathMatcher the matcher identifying low-priority paths
+ * @return this builder for chaining
+ */
+ public PathSortBuilder addLast(PathMatcher pathMatcher) {
+ return add(pathMatcher, LOWEST_PRECEDENCE);
+ }
+
+}
diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathSorts.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathSorts.java
new file mode 100644
index 0000000..4d8fd6e
--- /dev/null
+++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathSorts.java
@@ -0,0 +1,139 @@
+package io.github.computerdaddyguy.jfiletreeprettyprinter;
+
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.Optional;
+
+public final class PathSorts {
+
+ private PathSorts() {
+ // Helper class
+ }
+
+ /**
+ * Default alphabetical comparator based on the file name.
+ */
+ public static final Comparator ALPHABETICAL = Comparator.comparing(
+ (Path path) -> Optional.ofNullable(path.getFileName())
+ .map(Path::toString)
+ .orElse("")
+ );
+
+ /**
+ * Comparator that sorts paths by file size in ascending order.
+ * Directories are treated as having a size of {@code 0}, so they appear before regular files.
+ */
+ public static final Comparator BY_FILE_SIZE = Comparator.comparing(
+ (Path path) -> PathMatchers.isDirectory().matches(path) ? 0 : path.toFile().length()
+ );
+
+ /**
+ * Comparator that places directories before files.
+ * Paths of the same type (both directories or both files) are considered equal.
+ */
+ public static final Comparator DIRECTORY_FIRST = (Path path1, Path path2) -> {
+ var isP1Dir = PathMatchers.isDirectory().matches(path1);
+ var isP2Dir = PathMatchers.isDirectory().matches(path2);
+ if (isP1Dir == isP2Dir) {
+ return 0;
+ }
+ return isP1Dir ? -1 : 1;
+ };
+
+ /**
+ * Comparator that places files before directories.
+ * Paths of the same type (both directories or both files) are considered equal.
+ */
+ public static final Comparator DIRECTORY_LAST = DIRECTORY_FIRST.reversed();
+
+ /**
+ * Comparator that sort files by their extension (= the part after the last '.' character in the name).
+ * In case of several extensions (e.g. ".tar.gz"), files are firstly ordered by the "gz", then by "tar.gz":
+ * - ccc.gz
+ * - ddd.gz
+ * - .tar.gz
+ * - aaa.tar.gz
+ * - bbb.tar.gz
+ */
+
+ /**
+ * Comparator that sorts files by their extension(s), defined as the substring(s) after each '.' in the name.
+ *
+ * Examples of ordering:
+ *
+ * ccc.gz
+ * ddd.gz
+ * aaa.tar.gz
+ * bbb.tar.gz
+ * zzz.txt
+ *
+ *
+ * Rules:
+ *
+ * - If a file has no extension, it is sorted before files with extensions (e.g.,
README comes before aaa.txt).
+ * - For multi-part extensions (like
.tar.gz), comparison is hierarchical:
+ * first by the last extension (gz), then by the preceding extension (tar.gz), etc.
+ * - If extensions are equal, names are compared alphabetically as a tie-breaker.
+ *
+ */
+ public static final Comparator BY_EXTENSION = (p1, p2) -> {
+ var isDir1 = PathMatchers.isDirectory().matches(p1);
+ var isDir2 = PathMatchers.isDirectory().matches(p2);
+
+ // Directories come first
+ if (isDir1) {
+ return isDir2 ? 0 : -1;
+ }
+ if (isDir2) {
+ return 1;
+ }
+
+ String name1 = p1.getFileName().toString();
+ String name2 = p2.getFileName().toString();
+
+ String[] parts1 = name1.split("\\.");
+ String[] parts2 = name2.split("\\.");
+
+ // Handle files without extensions
+ if (parts1.length == 1 && parts2.length == 1) {
+ return name1.compareToIgnoreCase(name2);
+ }
+ if (parts1.length == 1) {
+ return -1; // p1 has no extension --> comes first
+ }
+ if (parts2.length == 1) {
+ return 1; // p2 has no extension --> comes first
+ }
+
+ // Compare extensions starting from the last part
+ int i1 = parts1.length - 1;
+ int i2 = parts2.length - 1;
+ while (i1 >= 1 && i2 >= 1) {
+ int cmp = parts1[i1].compareToIgnoreCase(parts2[i2]);
+ if (cmp != 0) {
+ return cmp;
+ }
+ i1--;
+ i2--;
+ }
+
+ // If one has more extension parts, it comes after
+ if (i1 > i2)
+ return 1;
+ if (i2 > i1)
+ return -1;
+
+ // Files have same extension (will be sorted by the next comparator - the alphabetical tie-breaker comparator by default)
+ return 0;
+ };
+
+ /**
+ * Returns a new {@link PathSortBuilder}.
+ *
+ * @return a fresh builder instance
+ */
+ public static PathSortBuilder builder() {
+ return new PathSortBuilder();
+ }
+
+}
diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java
index 2f52829..58d500c 100644
--- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java
+++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java
@@ -27,7 +27,7 @@ public static PrettyPrintOptions createDefault() {
// ---------- Child limit function ----------
- private ToIntFunction childLimit = p -> -1;
+ private ToIntFunction childLimit = p -> ChildLimits.UNLIMITED;
@Override
public ToIntFunction getChildLimit() {
@@ -50,6 +50,9 @@ public PrettyPrintOptions withChildLimit(int childLimit) {
* Default is no limit.
*
* @param childLimitFunction The dynamic limitation function, cannot be null. A negative computed value means no limit.
+ *
+ * @see ChildLimits
+ * @see ChildLimitBuilder
*/
public PrettyPrintOptions withChildLimit(ToIntFunction childLimitFunction) {
this.childLimit = Objects.requireNonNull(childLimitFunction, "childLimitFunction is null");
@@ -152,129 +155,7 @@ public PrettyPrintOptions withMaxDepth(int maxDepth) {
// ---------- File sort ----------
- public static final class Sorts {
-
- private Sorts() {
- // Helper class
- }
-
- /**
- * Comparator that sort paths alphabetically by name.
- */
- public static final Comparator BY_NAME = Comparator.comparing((Path path) -> path.getFileName().toString());
-
- /**
- * Comparator that sorts paths by file size in ascending order.
- * Directories are treated as having a size of {@code 0}, so they appear before regular files.
- */
- public static final Comparator BY_FILE_SIZE = Comparator.comparing((Path path) -> {
- var file = path.toFile();
- return file.isDirectory() ? 0 : file.length();
- });
-
- /**
- * Comparator that places directories before files.
- * Paths of the same type (both directories or both files) are considered equal.
- */
- public static final Comparator DIRECTORY_FIRST = (Path path1, Path path2) -> {
- var isP1Dir = path1.toFile().isDirectory();
- var isP2Dir = path2.toFile().isDirectory();
- if (isP1Dir == isP2Dir) {
- return 0;
- }
- return isP1Dir ? -1 : 1;
- };
-
- /**
- * Comparator that places files before directories.
- * Paths of the same type (both directories or both files) are considered equal.
- */
- public static final Comparator DIRECTORY_LAST = DIRECTORY_FIRST.reversed();
-
- /**
- * Comparator that sort files by their extension (= the part after the last '.' character in the name).
- * In case of several extensions (e.g. ".tar.gz"), files are firstly ordered by the "gz", then by "tar.gz":
- * - ccc.gz
- * - ddd.gz
- * - .tar.gz
- * - aaa.tar.gz
- * - bbb.tar.gz
- */
-
- /**
- * Comparator that sorts files by their extension(s), defined as the substring(s) after each '.' in the name.
- *
- * Examples of ordering:
- *
- * ccc.gz
- * ddd.gz
- * aaa.tar.gz
- * bbb.tar.gz
- * zzz.txt
- *
- *
- * Rules:
- *
- * - If a file has no extension, it is sorted before files with extensions (e.g.,
README comes before aaa.txt).
- * - For multi-part extensions (like
.tar.gz), comparison is hierarchical:
- * first by the last extension (gz), then by the preceding extension (tar.gz), etc.
- * - If extensions are equal, names are compared alphabetically as a tie-breaker.
- *
- */
- public static final Comparator BY_EXTENSION = (p1, p2) -> {
- boolean isDir1 = p1.toFile().isDirectory();
- boolean isDir2 = p2.toFile().isDirectory();
-
- // Directories come first
- if (isDir1) {
- return isDir2 ? 0 : -1;
- }
- if (isDir2) {
- return 1;
- }
-
- String name1 = p1.getFileName().toString();
- String name2 = p2.getFileName().toString();
-
- String[] parts1 = name1.split("\\.");
- String[] parts2 = name2.split("\\.");
-
- // Handle files without extensions
- if (parts1.length == 1 && parts2.length == 1) {
- return name1.compareToIgnoreCase(name2);
- }
- if (parts1.length == 1) {
- return -1; // p1 has no extension --> comes first
- }
- if (parts2.length == 1) {
- return 1; // p2 has no extension --> comes first
- }
-
- // Compare extensions starting from the last part
- int i1 = parts1.length - 1;
- int i2 = parts2.length - 1;
- while (i1 >= 1 && i2 >= 1) {
- int cmp = parts1[i1].compareToIgnoreCase(parts2[i2]);
- if (cmp != 0) {
- return cmp;
- }
- i1--;
- i2--;
- }
-
- // If one has more extension parts, it comes after
- if (i1 > i2)
- return 1;
- if (i2 > i1)
- return -1;
-
- // Files have same extension (will be sorted by the next comparator - the alphabetical tie-breaker comparator by default)
- return 0;
- };
-
- }
-
- private Comparator pathComparator = Sorts.BY_NAME;
+ private Comparator pathComparator = PathSorts.ALPHABETICAL;
@Override
public Comparator pathComparator() {
@@ -289,9 +170,12 @@ public Comparator pathComparator() {
* to ensure consistent ordering across all systems.
*
* @param pathComparator The custom comparator
+ *
+ * @see PathSorts
+ * @see PathSortBuilder
*/
public PrettyPrintOptions sort(Comparator pathComparator) {
- this.pathComparator = Objects.requireNonNull(pathComparator, "pathComparator is null").thenComparing(Sorts.BY_NAME);
+ this.pathComparator = Objects.requireNonNull(pathComparator, "pathComparator is null").thenComparing(PathSorts.ALPHABETICAL);
return this;
}
@@ -311,6 +195,8 @@ public PathMatcher pathFilter() {
* Directories that do not pass this filter will not be displayed.
*
* @param matcher The filter to apply on directories, cannot be null
+ *
+ * @see PathMatchers
*/
public PrettyPrintOptions filterDirectories(@Nullable PathMatcher matcher) {
this.dirMatcher = Objects.requireNonNull(matcher, "matcher is null");
@@ -323,6 +209,8 @@ public PrettyPrintOptions filterDirectories(@Nullable PathMatcher matcher) {
* Files that do not pass this filter will not be displayed.
*
* @param matcher The filter to apply on files, cannot be null
+ *
+ * @see PathMatchers
*/
public PrettyPrintOptions filterFiles(@Nullable PathMatcher matcher) {
this.fileMatcher = Objects.requireNonNull(matcher, "matcher is null");
@@ -351,6 +239,10 @@ public Function getLineExtension() {
* If the function returns {@code null}, nothing is added.
*
* @param lineExtension the custom line extension function, or {@code null} to disable
+ *
+ *
+ * @see LineExtensions
+ * @see LineExtensionBuilder
*/
public PrettyPrintOptions withLineExtension(@Nullable Function lineExtension) {
this.lineExtension = lineExtension;
diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/ChildLimitDynamicTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/ChildLimitDynamicTest.java
index 41ba666..30e1928 100644
--- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/ChildLimitDynamicTest.java
+++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/ChildLimitDynamicTest.java
@@ -18,10 +18,10 @@ class ChildLimitDynamicTest {
// @formatter:off
options -> options.withChildLimit(
- ChildLimitBuilder.builder()
- .limit(PathMatchers.hasName("limit_1"), 1)
- .limit(PathMatchers.hasName("limit_3"), 3)
- .defaultLimit(ChildLimitBuilder.UNLIMITED)
+ ChildLimits.builder()
+ .add(PathMatchers.hasName("limit_1"), 1)
+ .add(PathMatchers.hasName("limit_3"), 3)
+ .setDefault(ChildLimits.UNLIMITED)
.build()
)
// @formatter:on
diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FilteringTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FilteringTest.java
index f31dc1f..da30094 100644
--- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FilteringTest.java
+++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FilteringTest.java
@@ -2,7 +2,6 @@
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;
@@ -60,7 +59,7 @@ void example_and_sorting() {
var filter = PathMatchers.hasExtension("java");
FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder()
- .customizeOptions(options -> options.sort(Sorts.BY_NAME.reversed()))
+ .customizeOptions(options -> options.sort(PathSorts.ALPHABETICAL.reversed()))
.customizeOptions(options -> options.filterFiles(filter))
.build();
diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java
index 75c1566..d145a3a 100644
--- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java
+++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/LineExtensionTest.java
@@ -31,21 +31,12 @@ void example_dir_match() {
var printedPath = Path.of("src/example/resources/line_extension");
- Function lineExtension = path -> {
- if (PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/api").matches(path)) {
- return "\t\t\t// All API code: controllers, etc.";
- }
- if (PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/domain").matches(path)) {
- return "\t\t\t// All domain code: value objects, etc.";
- }
- if (PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/infra").matches(path)) {
- return "\t\t\t// All infra code: database, email service, etc.";
- }
- if (PathMatchers.hasNameMatchingGlob("*.properties").matches(path)) {
- return "\t// Config file";
- }
- return null;
- };
+ Function lineExtension = LineExtensions.builder()
+ .add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/api"), "\t\t\t// All API code: controllers, etc.")
+ .add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/domain"), "\t\t\t// All domain code: value objects, etc.")
+ .add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/infra"), "\t\t\t// All infra code: database, email service, etc.")
+ .add(PathMatchers.hasNameMatchingGlob("*.properties"), "\t// Config file")
+ .build();
var printer = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.withLineExtension(lineExtension))
@@ -71,12 +62,9 @@ void example_dir_match() {
@Test
void compact_dir_first_dir() {
- Function lineExtension = p -> {
- if (PathMatchers.hasName("dirA").matches(p)) {
- return " // 1";
- }
- return null;
- };
+ Function lineExtension = LineExtensions.builder()
+ .add(PathMatchers.hasName("dirA"), " // 1")
+ .build();
var expected = """
targetPath/
@@ -91,14 +79,11 @@ void compact_dir_first_dir() {
}
@Test
- void compact_dir_empty_string_workds() {
+ void compact_dir_empty_string_creates_line_break() {
- Function lineExtension = p -> {
- if (PathMatchers.hasName("dirA").matches(p)) {
- return "";
- }
- return null;
- };
+ Function lineExtension = LineExtensions.builder()
+ .addLineBreak(PathMatchers.hasName("dirA"))
+ .build();
var expected = """
targetPath/
@@ -115,12 +100,9 @@ void compact_dir_empty_string_workds() {
@Test
void compact_dir_middle_dir() {
- Function lineExtension = p -> {
- if (PathMatchers.hasName("dirB").matches(p)) {
- return " // 2";
- }
- return null;
- };
+ Function lineExtension = LineExtensions.builder()
+ .add(PathMatchers.hasName("dirB"), " // 2")
+ .build();
var expected = """
targetPath/
@@ -137,12 +119,9 @@ void compact_dir_middle_dir() {
@Test
void compact_dir_last_dir() {
- Function lineExtension = p -> {
- if (PathMatchers.hasName("dirC").matches(p)) {
- return " // 3";
- }
- return null;
- };
+ Function lineExtension = LineExtensions.builder()
+ .add(PathMatchers.hasName("dirC"), " // 3")
+ .build();
var expected = """
targetPath/
diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathSortBuilderTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathSortBuilderTest.java
new file mode 100644
index 0000000..1d91e63
--- /dev/null
+++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PathSortBuilderTest.java
@@ -0,0 +1,114 @@
+package io.github.computerdaddyguy.jfiletreeprettyprinter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.nio.file.Path;
+import java.util.Comparator;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class PathSortBuilderTest {
+
+ private static void assertCompare(Comparator comparator, Path pathFirst, Path pathSecond) {
+ assertThat(comparator.compare(pathFirst, pathSecond)).isNegative();
+ assertThat(comparator.compare(pathSecond, pathFirst)).isPositive();
+ assertThat(comparator.compare(pathFirst, pathFirst)).isZero();
+ assertThat(comparator.compare(pathSecond, pathSecond)).isZero();
+ }
+
+ @Nested
+ class AddFunction {
+
+ @Test
+ void when_compare_equal_then_alphabetical() {
+ var comparator = PathSorts.builder()
+ .add(p -> 10)
+ .build();
+
+ var pathA = Path.of("A");
+ var pathB = Path.of("B");
+
+ assertCompare(comparator, pathA, pathB);
+ }
+
+ @Test
+ void nominal() {
+ var comparator = PathSorts.builder()
+ .add(p -> p.getFileName().toString().equals("A") ? 2 : 1)
+ .build();
+
+ var pathA = Path.of("A");
+ var pathB = Path.of("B");
+
+ assertCompare(comparator, pathB, pathA);
+ }
+
+ }
+
+ @Nested
+ class AddMatcher {
+
+ @Test
+ void when_compare_equal_then_alphabetical() {
+ var comparator = PathSorts.builder()
+ .add(p -> true, 10)
+ .build();
+
+ var pathA = Path.of("A");
+ var pathB = Path.of("B");
+
+ assertCompare(comparator, pathA, pathB);
+ }
+
+ @Test
+ void nominal() {
+ var comparator = PathSorts.builder()
+ .add(p -> p.getFileName().toString().equals("A"), 2)
+ .add(p -> p.getFileName().toString().equals("B"), 1)
+ .build();
+
+ var pathA = Path.of("A");
+ var pathB = Path.of("B");
+
+ assertCompare(comparator, pathB, pathA);
+ }
+
+ }
+
+ @Nested
+ class AddFirst {
+
+ @Test
+ void when_compare_equal_then_alphabetical() {
+ var comparator = PathSorts.builder()
+ .addFirst(p -> p.getFileName().toString().equals("A"))
+ .addFirst(p -> p.getFileName().toString().equals("B"))
+ .build();
+
+ var pathA = Path.of("A");
+ var pathB = Path.of("B");
+
+ assertCompare(comparator, pathA, pathB);
+ }
+
+ }
+
+ @Nested
+ class AddLast {
+
+ @Test
+ void when_compare_equal_then_alphabetical() {
+ var comparator = PathSorts.builder()
+ .addFirst(p -> p.getFileName().toString().equals("A"))
+ .addFirst(p -> p.getFileName().toString().equals("B"))
+ .build();
+
+ var pathA = Path.of("A");
+ var pathB = Path.of("B");
+
+ assertCompare(comparator, pathA, pathB);
+ }
+
+ }
+
+}
diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/SortingTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/SortingTest.java
index c996f85..90f8c31 100644
--- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/SortingTest.java
+++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/SortingTest.java
@@ -16,7 +16,7 @@ class SortingTest {
void example() {
FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder()
- .customizeOptions(options -> options.sort(PrettyPrintOptions.Sorts.DIRECTORY_FIRST))
+ .customizeOptions(options -> options.sort(PathSorts.DIRECTORY_FIRST))
.build();
var result = printer.prettyPrint("src/example/resources/sorting");
@@ -80,7 +80,7 @@ void defaultOrderIsAlphabetical() {
void alphabetical_reversed() {
FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder()
- .customizeOptions(options -> options.sort(PrettyPrintOptions.Sorts.BY_NAME.reversed()))
+ .customizeOptions(options -> options.sort(PathSorts.ALPHABETICAL.reversed()))
.build();
var result = printer.prettyPrint(buildDefaultPath());
@@ -107,7 +107,7 @@ void alphabetical_reversed() {
void fileSize() {
FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder()
- .customizeOptions(options -> options.sort(PrettyPrintOptions.Sorts.BY_FILE_SIZE))
+ .customizeOptions(options -> options.sort(PathSorts.BY_FILE_SIZE))
.build();
var result = printer.prettyPrint("src/example/resources/sorting");
@@ -151,7 +151,7 @@ private Path build_directoryFirstOrLast_paths() {
void directoriesFirst() {
FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder()
- .customizeOptions(options -> options.sort(PrettyPrintOptions.Sorts.DIRECTORY_FIRST))
+ .customizeOptions(options -> options.sort(PathSorts.DIRECTORY_FIRST))
.build();
var result = printer.prettyPrint(build_directoryFirstOrLast_paths());
@@ -174,7 +174,7 @@ void directoriesFirst() {
void directoriesLast() {
FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder()
- .customizeOptions(options -> options.sort(PrettyPrintOptions.Sorts.DIRECTORY_LAST))
+ .customizeOptions(options -> options.sort(PathSorts.DIRECTORY_LAST))
.build();
var result = printer.prettyPrint(build_directoryFirstOrLast_paths());
@@ -217,7 +217,7 @@ private Path build_extension_paths() {
void byExtension() {
FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder()
- .customizeOptions(options -> options.sort(PrettyPrintOptions.Sorts.BY_EXTENSION))
+ .customizeOptions(options -> options.sort(PathSorts.BY_EXTENSION))
.build();
var result = printer.prettyPrint(build_extension_paths());