Skip to content

Commit 118c94a

Browse files
Option: Line extension (=additional text after the file name)
1 parent a3893fb commit 118c94a

File tree

16 files changed

+359
-54
lines changed

16 files changed

+359
-54
lines changed

CHANGELOG.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
---
89
## [0.0.4] - Unreleased
910

11+
### Added
12+
- Line extension: append any String after a path (comment, file details, etc.)
13+
1014
### Changed
1115
- Filtering: moved to options
16+
- Compact dirs: root dir is now never compacted
1217

1318
### Removed
1419
- Error handling: exceptions are now thrown instead of being handled by the renderer
1520

16-
21+
---
1722
## [0.0.3] - 2025-09-21
1823

1924
### Added
@@ -25,14 +30,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2530
- Sorting: option method `withFileSort` renamed in `sort`
2631
- Child limit: various renaming and refactor
2732

28-
33+
---
2934
## [0.0.2] - 2025-09-16
3035

3136
### Added
3237
- Option: sorting files and directories
3338
- 39 new default files & extension mappings for emojis
3439

35-
40+
---
3641
## [0.0.1] - 2025-09-14
3742

3843
### Added

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- Limit displayed children (fixed value or dynamically)
1616
- Compact directory chains
1717
- Maximum depth
18+
- Custom line extension (comment, file details, etc.)
1819
- Various styles for tree rendering
1920

2021
> [!CAUTION]
@@ -90,6 +91,7 @@ base/
9091
* [Max depth](#max-depth)
9192
* [Sorting](#sorting)
9293
* [Filtering](#filtering)
94+
* [Line extension](#line-extension)
9395

9496
## Tree format
9597
Choose between different tree formats.
@@ -296,6 +298,49 @@ filtering/
296298
└─ file_A.java
297299
```
298300

301+
## Line extension
302+
You can extend each displayed path with additional information by providing a custom `Function<Path, String>`.
303+
This is useful to annotate your tree with comments, display file sizes, or add domain-specific notes.
304+
305+
The function receives the current path and returns an optional string to append.
306+
If the function returns `null`, nothing is added.
307+
308+
```java
309+
// Example: LineExtension.java
310+
Function<Path, String> lineExtension = path -> {
311+
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "api")) {
312+
return "\t\t\t// All API code: controllers, etc.";
313+
}
314+
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "domain")) {
315+
return "\t\t\t// All domain code: value objects, etc.";
316+
}
317+
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "infra")) {
318+
return "\t\t\t// All infra code: database, email service, etc.";
319+
}
320+
if (PathUtils.isFile(path) && PathUtils.hasName(path, "application.properties")) {
321+
return "\t// Config file";
322+
}
323+
return null;
324+
};
325+
var prettyPrinter = FileTreePrettyPrinter.builder()
326+
.customizeOptions(options -> options.withLineExtension(lineExtension))
327+
.build();
328+
```
329+
```
330+
line_extension/
331+
└─ src/
332+
└─ main/
333+
├─ java/
334+
│ ├─ api/ // All API code: controllers, etc.
335+
│ │ └─ Controller.java
336+
│ ├─ domain/ // All domain code: value objects, etc.
337+
│ │ └─ ValueObject.java
338+
│ └─ infra/ // All infra code: database, email service, etc.
339+
│ └─ Repository.java
340+
└─ resources/
341+
└─ application.properties // Config file
342+
```
343+
299344
# Changelog
300345
See [CHANGELOG.md](CHANGELOG.md) for released versions.
301346

ROADMAP.md

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,14 @@
2020
## To do
2121
- [x] Use Github wiki to document options instead of readme
2222
- [x] Jacoco coverage report
23-
- [ ] Option: Line extension (=additional text after the file name)
24-
- [ ] Option: Filename decorator
23+
- [x] Option: Line extension (=additional text after the file name)
2524

26-
## Backlog
25+
## Backlog / To analyze / To implement if requested
26+
- [ ] More `PathPredicates` functions!
2727
- [ ] Option: custom tree format
2828
- [ ] Option: custom emojis
29-
- [ ] Option: color
3029
- [ ] Refactor unit tests (custom assert?)
31-
- [ ] More `PathPredicates` functions!
32-
33-
## Abandoned
34-
These ideas will likely not been implemented because they do not align with JFileTreePrettyPrint vision:
35-
- File attributes LineRenderer (size, author, createAt, etc.)
36-
- Print optional legend for symlink/other file types symbols (at the end of the tree)
37-
- Follow symlink option
30+
- [ ] Option: color
31+
- [ ] Option: Filename decorator
32+
- [ ] Option: Follow symlink
33+
- [ ] Advanced line extension function (file size, author, timestamps, etc. )
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.github.computerdaddyguy.jfiletreeprettyprinter.example;
2+
3+
import io.github.computerdaddyguy.jfiletreeprettyprinter.FileTreePrettyPrinter;
4+
import io.github.computerdaddyguy.jfiletreeprettyprinter.PathUtils;
5+
import java.nio.file.Path;
6+
import java.util.function.Function;
7+
8+
public class LineExtension {
9+
10+
public static void main(String[] args) {
11+
Function<Path, String> lineExtension = path -> {
12+
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "api")) {
13+
return "\t\t\t// All API code: controllers, etc.";
14+
}
15+
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "domain")) {
16+
return "\t\t\t// All domain code: value objects, etc.";
17+
}
18+
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "infra")) {
19+
return "\t\t\t// All infra code: database, email service, etc.";
20+
}
21+
if (PathUtils.isFile(path) && PathUtils.hasName(path, "application.properties")) {
22+
return "\t// Config file";
23+
}
24+
return null;
25+
};
26+
var prettyPrinter = FileTreePrettyPrinter.builder()
27+
.customizeOptions(options -> options.withLineExtension(lineExtension))
28+
.build();
29+
var tree = prettyPrinter.prettyPrint("src/example/resources/line_extension");
30+
System.out.println(tree);
31+
}
32+
33+
}

src/example/resources/line_extension/src/main/java/api/Controller.java

Whitespace-only changes.

src/example/resources/line_extension/src/main/java/domain/ValueObject.java

Whitespace-only changes.

src/example/resources/line_extension/src/main/java/infra/Repository.java

Whitespace-only changes.

src/example/resources/line_extension/src/main/resources/application.properties

Whitespace-only changes.

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.nio.file.Path;
66
import java.util.Comparator;
77
import java.util.Objects;
8+
import java.util.function.Function;
89
import java.util.function.Predicate;
910
import java.util.function.ToIntFunction;
1011
import org.jspecify.annotations.NullMarked;
@@ -313,9 +314,37 @@ public Predicate<Path> pathFilter() {
313314
314315
* @param filter The filter, <code>null</code> to disable filtering
315316
*/
316-
public PrettyPrintOptions filter(Predicate<Path> filter) {
317+
public PrettyPrintOptions filter(@Nullable Predicate<Path> filter) {
317318
this.pathFilter = filter;
318319
return this;
319320
}
320321

322+
// ---------- Line extension ----------
323+
324+
@Nullable
325+
private Function<Path, String> lineExtension;
326+
327+
@Override
328+
@Nullable
329+
public Function<Path, String> getLineExtension() {
330+
return lineExtension;
331+
}
332+
333+
/**
334+
* Sets a custom line extension function that appends additional text to each
335+
* printed line, allowing you to customize the display of files or directories.
336+
* <p>
337+
* Typical use cases include adding comments, showing file sizes, or displaying metadata.
338+
* <p>
339+
* The function receives the current {@link Path} displayed on the line
340+
* and returns an optional string to be appended.
341+
* If the function returns {@code null}, nothing is added.
342+
*
343+
* @param lineExtension the custom line extension function, or {@code null} to disable
344+
*/
345+
public PrettyPrintOptions withLineExtension(@Nullable Function<Path, String> lineExtension) {
346+
this.lineExtension = lineExtension;
347+
return this;
348+
}
349+
321350
}

src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/DefaultTreeEntryRenderer.java

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,60 +26,76 @@ public DefaultTreeEntryRenderer(RenderingOptions options) {
2626

2727
@Override
2828
public String renderTree(TreeEntry entry) {
29-
var depth = Depth.createNewEmpty();
30-
var buff = new StringBuilder();
31-
renderTree(entry, depth, buff);
32-
return buff.toString();
29+
return renderTree(entry, Depth.createNewEmpty());
3330
}
3431

35-
public void renderTree(TreeEntry entry, Depth depth, StringBuilder buff) {
36-
switch (entry) {
37-
case TreeEntry.DirectoryEntry dirEntry -> renderDirectory(buff, depth, dirEntry, List.of(dirEntry.getDir()));
38-
case TreeEntry.FileEntry fileEntry -> renderFile(buff, depth, fileEntry);
39-
case TreeEntry.SkippedChildrenEntry skippedChildrenEntry -> renderSkippedChildrenEntry(buff, depth, skippedChildrenEntry);
40-
case TreeEntry.MaxDepthReachEntry maxDepthReachEntry -> renderMaxDepthReachEntry(buff, depth, maxDepthReachEntry);
41-
}
32+
private String renderTree(TreeEntry entry, Depth depth) {
33+
return switch (entry) {
34+
case TreeEntry.DirectoryEntry dirEntry -> renderDirectory(depth, dirEntry, List.of(dirEntry.getDir()));
35+
case TreeEntry.FileEntry fileEntry -> renderFile(depth, fileEntry);
36+
case TreeEntry.SkippedChildrenEntry skippedChildrenEntry -> renderSkippedChildrenEntry(depth, skippedChildrenEntry);
37+
case TreeEntry.MaxDepthReachEntry maxDepthReachEntry -> renderMaxDepthReachEntry(depth, maxDepthReachEntry);
38+
};
4239
}
4340

44-
private void renderDirectory(StringBuilder buff, Depth depth, DirectoryEntry dirEntry, List<Path> compactPaths) {
41+
private String renderDirectory(Depth depth, DirectoryEntry dirEntry, List<Path> compactPaths) {
4542

4643
if (options.areCompactDirectoriesUsed()
44+
&& !depth.isRoot()
4745
&& dirEntry.getEntries().size() == 1
4846
&& dirEntry.getEntries().get(0) instanceof DirectoryEntry childDirEntry) {
49-
var newCompactPaths = new ArrayList<>(compactPaths);
50-
newCompactPaths.add(childDirEntry.getDir());
51-
renderDirectory(buff, depth, childDirEntry, newCompactPaths);
52-
return;
47+
48+
var extension = computeLineExtension(dirEntry.getDir());
49+
if (extension.isEmpty()) {
50+
var newCompactPaths = new ArrayList<>(compactPaths);
51+
newCompactPaths.add(childDirEntry.getDir());
52+
return renderDirectory(depth, childDirEntry, newCompactPaths);
53+
}
5354
}
5455

55-
buff.append(lineRenderer.renderDirectoryBegin(depth, dirEntry, compactPaths));
56+
var line = lineRenderer.renderDirectoryBegin(depth, dirEntry, compactPaths);
57+
line += computeLineExtension(dirEntry.getDir());
5658

5759
var childIt = dirEntry.getEntries().iterator();
5860

5961
if (childIt.hasNext()) {
60-
buff.append('\n');
62+
line += "\n";
6163
}
6264

65+
var childLines = new StringBuilder();
66+
6367
while (childIt.hasNext()) {
6468
var childEntry = childIt.next();
6569
var childDepth = depth.append(childIt.hasNext() ? DepthSymbol.NON_LAST_FILE : DepthSymbol.LAST_FILE);
66-
renderTree(childEntry, childDepth, buff);
70+
childLines.append(renderTree(childEntry, childDepth));
6771
if (childIt.hasNext()) {
68-
buff.append('\n');
72+
childLines.append('\n');
6973
}
7074
}
75+
76+
return line + childLines.toString();
77+
}
78+
79+
private String computeLineExtension(Path path) {
80+
if (options.getLineExtension() == null) {
81+
return "";
82+
}
83+
var extension = options.getLineExtension().apply(path);
84+
return extension == null ? "" : extension;
7185
}
7286

73-
private void renderFile(StringBuilder buff, Depth depth, FileEntry fileEntry) {
74-
buff.append(lineRenderer.renderFile(depth, fileEntry));
87+
private String renderFile(Depth depth, FileEntry fileEntry) {
88+
var line = lineRenderer.renderFile(depth, fileEntry);
89+
line += computeLineExtension(fileEntry.getFile());
90+
return line;
7591
}
7692

77-
private void renderSkippedChildrenEntry(StringBuilder buff, Depth depth, SkippedChildrenEntry skippedChildrenEntry) {
78-
buff.append(lineRenderer.renderChildLimitReached(depth, skippedChildrenEntry));
93+
private String renderSkippedChildrenEntry(Depth depth, SkippedChildrenEntry skippedChildrenEntry) {
94+
return lineRenderer.renderChildLimitReached(depth, skippedChildrenEntry);
7995
}
8096

81-
private void renderMaxDepthReachEntry(StringBuilder buff, Depth depth, MaxDepthReachEntry maxDepthReachEntry) {
82-
buff.append(lineRenderer.renderMaxDepthReached(depth, maxDepthReachEntry));
97+
private String renderMaxDepthReachEntry(Depth depth, MaxDepthReachEntry maxDepthReachEntry) {
98+
return lineRenderer.renderMaxDepthReached(depth, maxDepthReachEntry);
8399
}
84100

85101
}

0 commit comments

Comments
 (0)