Skip to content

Commit ddc26bb

Browse files
New LineExtensionBuilder
1 parent 9188107 commit ddc26bb

File tree

6 files changed

+156
-44
lines changed

6 files changed

+156
-44
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
### Added
1212
- New various path matchers
13+
- New `LineExtensionBuilder` helper class
1314

1415
### Changed
1516
- Filtering: now using `PathMatcher` interface instead of `Predicate<Path>`

README.md

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -249,28 +249,23 @@ child_limit_dynamic/
249249
You can extend each displayed path with additional information by providing a custom `Function<Path, String>`.
250250
This is useful to annotate your tree with comments, display file sizes, or add domain-specific notes.
251251

252-
The function receives the current path and returns an optional string to append.
252+
The function receives the current path and returns an optional string to append (empty string is authorized).
253253
If the function returns `null`, nothing is added.
254254

255+
Use the `LineExtensionBuilder` class to help you build line extension functions.
256+
255257
```java
256258
// Example: LineExtension.java
257259
var printedPath = Path.of("src/example/resources/line_extension");
258260

259-
Function<Path, String> lineExtension = path -> {
260-
if (PathMatchers.hasRelativePathMatchingGlob("src/main/java/api", printedPath).matches(path)) {
261-
return "\t\t\t// All API code: controllers, etc.";
262-
}
263-
if (PathMatchers.hasRelativePathMatchingGlob("src/main/java/domain", printedPath).matches(path)) {
264-
return "\t\t\t// All domain code: value objects, etc.";
265-
}
266-
if (PathMatchers.hasRelativePathMatchingGlob("src/main/java/infra", printedPath).matches(path)) {
267-
return "\t\t\t// All infra code: database, email service, etc.";
268-
}
269-
if (PathMatchers.hasNameMatchingGlob("*.properties").matches(path)) {
270-
return "\t// Config file";
271-
}
272-
return null;
273-
};
261+
Function<Path, String> lineExtension = LineExtensionBuilder.newInstance()
262+
.add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/api"), "\t\t\t// All API code: controllers, etc.")
263+
.add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/domain"), "\t\t\t// All domain code: value objects, etc.")
264+
.add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/infra"), "\t\t\t// All infra code: database, email service, etc.")
265+
.add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/api"), "\t\t\t// All API code: controllers, etc.")
266+
.add(PathMatchers.hasNameMatchingGlob("*.properties"), "\t// Config file")
267+
.build();
268+
274269
var prettyPrinter = FileTreePrettyPrinter.builder()
275270
.customizeOptions(options -> options.withLineExtension(lineExtension))
276271
.build();

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
## To do
2929
- [x] More `PathMatchers` functions!
30-
- [ ] Helper class for line extension
30+
- [x] Helper class for line extension
3131
- [ ] Helper class for sorting
3232
- [ ] Option: custom emojis
3333
- [ ] Option: hide number of skipped files and folders for child limit

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

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.computerdaddyguy.jfiletreeprettyprinter.example;
22

33
import io.github.computerdaddyguy.jfiletreeprettyprinter.FileTreePrettyPrinter;
4+
import io.github.computerdaddyguy.jfiletreeprettyprinter.LineExtensionBuilder;
45
import io.github.computerdaddyguy.jfiletreeprettyprinter.PathMatchers;
56
import java.nio.file.Path;
67
import java.util.function.Function;
@@ -10,21 +11,14 @@ public class LineExtension {
1011
public static void main(String[] args) {
1112
var printedPath = Path.of("src/example/resources/line_extension");
1213

13-
Function<Path, String> lineExtension = path -> {
14-
if (PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/api").matches(path)) {
15-
return "\t\t\t// All API code: controllers, etc.";
16-
}
17-
if (PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/domain").matches(path)) {
18-
return "\t\t\t// All domain code: value objects, etc.";
19-
}
20-
if (PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/infra").matches(path)) {
21-
return "\t\t\t// All infra code: database, email service, etc.";
22-
}
23-
if (PathMatchers.hasNameMatchingGlob("*.properties").matches(path)) {
24-
return "\t// Config file";
25-
}
26-
return null;
27-
};
14+
Function<Path, String> lineExtension = LineExtensionBuilder.newInstance()
15+
.add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/api"), "\t\t\t// All API code: controllers, etc.")
16+
.add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/domain"), "\t\t\t// All domain code: value objects, etc.")
17+
.add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/infra"), "\t\t\t// All infra code: database, email service, etc.")
18+
.add(PathMatchers.hasRelativePathMatchingGlob(printedPath, "src/main/java/api"), "\t\t\t// All API code: controllers, etc.")
19+
.add(PathMatchers.hasNameMatchingGlob("*.properties"), "\t// Config file")
20+
.build();
21+
2822
var prettyPrinter = FileTreePrettyPrinter.builder()
2923
.customizeOptions(options -> options.withLineExtension(lineExtension))
3024
.build();

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

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,12 @@ public static void main(String[] args) {
7070
/*
7171
* Add some comments on a few files and directories
7272
*/
73-
Function<Path, String> lineExtension = path -> {
74-
if (PathMatchers.hasName("project-structure.png").matches(path)) {
75-
return "\t// This image";
76-
} else if (PathMatchers.hasName("FileTreePrettyPrinter.java").matches(path)) {
77-
return "\t// Main entry point";
78-
} else if (PathMatchers.hasName("README.md").matches(path)) {
79-
return "\t\t// You're reading at this!";
80-
} else if (PathMatchers.hasRelativePathMatchingGlob(projectFolder, "src/main/java").matches(path)) {
81-
return ""; // Empty string: force line break in compact directory chain
82-
}
83-
return null;
84-
};
73+
Function<Path, String> lineExtension = LineExtensionBuilder.newInstance()
74+
.add(PathMatchers.hasName("project-structure.png"), "\t// This image")
75+
.add(PathMatchers.hasName("FileTreePrettyPrinter.java"), "\t// Main entry point")
76+
.add(PathMatchers.hasName("README.md"), "\t\t// You're reading at this!")
77+
.addLineBreak(PathMatchers.hasRelativePathMatchingGlob(projectFolder, "src/main/java"))
78+
.build();
8579

8680
/*
8781
* Sort all paths by directory first (then alphabetically by default)
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package io.github.computerdaddyguy.jfiletreeprettyprinter;
2+
3+
import java.nio.file.Path;
4+
import java.nio.file.PathMatcher;
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
import java.util.Objects;
8+
import java.util.function.Function;
9+
import org.jspecify.annotations.NullMarked;
10+
11+
/**
12+
* A builder for constructing functions that provide optional line extensions
13+
* (such as comments or formatting markers) when pretty-printing file trees.
14+
* <p>
15+
* A {@code LineExtensionBuilder} allows you to add rules in the form of
16+
* {@link Function} objects that map a {@link Path} to an extension string.
17+
* When the resulting function is applied to a path, rules are evaluated
18+
* in insertion order, and the first non-{@code null} result is used.
19+
* <ul>
20+
* <li>{@code null} means no extension (line is printed normally).</li>
21+
* <li>An empty string means "force line break" in compact directory chains.</li>
22+
* <li>Any non-empty string is appended after the path (e.g. a comment).</li>
23+
* </ul>
24+
*
25+
* <p>Example usage:
26+
* <pre>{@code
27+
* var lineExtension = LineExtensionBuilder.newInstance()
28+
* .add(PathMatchers.hasName("README.md"), " // Project documentation")
29+
* .addLineBreak(PathMatchers.hasRelativePathMatchingGlob(root, "src/main/java"))
30+
* .build();
31+
* }</pre>
32+
*
33+
* The returned {@code Function<Path, String>} can then be passed to
34+
* {@link PrettyPrintOptions#withLineExtension(Function)}.
35+
*
36+
* @see PrettyPrintOptions
37+
*/
38+
@NullMarked
39+
public class LineExtensionBuilder {
40+
41+
private static final String NO_EXTENSION = null;
42+
private static final String LINE_BREAK_EXTENSION = "";
43+
44+
private List<Function<Path, String>> extensions;
45+
46+
private LineExtensionBuilder() {
47+
this.extensions = new ArrayList<>();
48+
}
49+
50+
/**
51+
* Returns a new {@link LineExtensionBuilder}.
52+
*
53+
* @return a fresh builder instance
54+
*/
55+
public static LineExtensionBuilder newInstance() {
56+
return new LineExtensionBuilder();
57+
}
58+
59+
/**
60+
* Builds the final function mapping a {@link Path} to an extension string.
61+
* <p>
62+
* The function applies the registered rules in insertion order.
63+
* The first rule returning a non-{@code null} value determines the extension.
64+
* If none match, the function returns {@code null}.
65+
*
66+
* @return a function mapping paths to extensions
67+
*/
68+
public Function<Path, String> build() {
69+
var immutExtensions = List.copyOf(extensions);
70+
return path -> {
71+
String result = NO_EXTENSION;
72+
for (var rule : immutExtensions) {
73+
result = rule.apply(path);
74+
if (result != NO_EXTENSION) {
75+
break;
76+
}
77+
}
78+
return result;
79+
};
80+
}
81+
82+
/**
83+
* Adds a custom line extension rule.
84+
* <p>
85+
* The function should return either:
86+
* <ul>
87+
* <li>{@code null} to indicate "no extension".</li>
88+
* <li>an empty string to force a line break.</li>
89+
* <li>a non-empty string to append after the path.</li>
90+
* </ul>
91+
*
92+
* @param lineExtension a function mapping paths to extensions (non-null)
93+
* @return this builder (for chaining)
94+
* @throws NullPointerException if {@code lineExtension} is null
95+
*/
96+
public LineExtensionBuilder add(Function<Path, String> lineExtension) {
97+
Objects.requireNonNull(lineExtension, "lineExtension is null");
98+
this.extensions.add(lineExtension);
99+
return this;
100+
}
101+
102+
/**
103+
* Adds a rule that appends the given extension when the matcher matches.
104+
* <p>
105+
* If the matcher does not match, the rule returns {@code null}.
106+
*
107+
* @param pathMatcher the matcher to test paths against (non-null)
108+
* @param extension the extension string to return when matched
109+
* @return this builder (for chaining)
110+
* @throws NullPointerException if {@code pathMatcher} is null
111+
*/
112+
public LineExtensionBuilder add(PathMatcher pathMatcher, String extension) {
113+
Objects.requireNonNull(pathMatcher, "pathMatcher is null");
114+
return add(path -> pathMatcher.matches(path) ? extension : NO_EXTENSION);
115+
}
116+
117+
/**
118+
* Adds a rule that forces a line break (instead of appending text)
119+
* whenever the given matcher matches.
120+
*
121+
* @param pathMatcher the matcher to test paths against (non-null)
122+
* @return this builder (for chaining)
123+
*/
124+
public LineExtensionBuilder addLineBreak(PathMatcher pathMatcher) {
125+
return add(pathMatcher, LINE_BREAK_EXTENSION);
126+
}
127+
128+
}

0 commit comments

Comments
 (0)