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
11 changes: 8 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ 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.4] - Unreleased

### Added
- Line extension: append any String after a path (comment, file details, etc.)

### Changed
- Filtering: moved to options
- Compact dirs: root dir is now never compacted

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


---
## [0.0.3] - 2025-09-21

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


---
## [0.0.2] - 2025-09-16

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


---
## [0.0.1] - 2025-09-14

### Added
Expand Down
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Limit displayed children (fixed value or dynamically)
- Compact directory chains
- Maximum depth
- Custom line extension (comment, file details, etc.)
- Various styles for tree rendering

> [!CAUTION]
Expand Down Expand Up @@ -90,6 +91,7 @@ base/
* [Max depth](#max-depth)
* [Sorting](#sorting)
* [Filtering](#filtering)
* [Line extension](#line-extension)

## Tree format
Choose between different tree formats.
Expand Down Expand Up @@ -296,6 +298,49 @@ filtering/
└─ file_A.java
```

## Line extension
You can extend each displayed path with additional information by providing a custom `Function<Path, String>`.
This is useful to annotate your tree with comments, display file sizes, or add domain-specific notes.

The function receives the current path and returns an optional string to append.
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")) {
return "\t\t\t// All API code: controllers, etc.";
}
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "domain")) {
return "\t\t\t// All domain code: value objects, etc.";
}
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "infra")) {
return "\t\t\t// All infra code: database, email service, etc.";
}
if (PathUtils.isFile(path) && PathUtils.hasName(path, "application.properties")) {
return "\t// Config file";
}
return null;
};
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.withLineExtension(lineExtension))
.build();
```
```
line_extension/
└─ src/
└─ main/
├─ java/
│ ├─ api/ // All API code: controllers, etc.
│ │ └─ Controller.java
│ ├─ domain/ // All domain code: value objects, etc.
│ │ └─ ValueObject.java
│ └─ infra/ // All infra code: database, email service, etc.
│ └─ Repository.java
└─ resources/
└─ application.properties // Config file
```

# Changelog
See [CHANGELOG.md](CHANGELOG.md) for released versions.

Expand Down
18 changes: 7 additions & 11 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,14 @@
## To do
- [x] Use Github wiki to document options instead of readme
- [x] Jacoco coverage report
- [ ] Option: Line extension (=additional text after the file name)
- [ ] Option: Filename decorator
- [x] Option: Line extension (=additional text after the file name)

## Backlog
## Backlog / To analyze / To implement if requested
- [ ] More `PathPredicates` functions!
- [ ] Option: custom tree format
- [ ] Option: custom emojis
- [ ] Option: color
- [ ] Refactor unit tests (custom assert?)
- [ ] More `PathPredicates` functions!

## Abandoned
These ideas will likely not been implemented because they do not align with JFileTreePrettyPrint vision:
- File attributes LineRenderer (size, author, createAt, etc.)
- Print optional legend for symlink/other file types symbols (at the end of the tree)
- Follow symlink option
- [ ] Option: color
- [ ] Option: Filename decorator
- [ ] Option: Follow symlink
- [ ] Advanced line extension function (file size, author, timestamps, etc. )
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.github.computerdaddyguy.jfiletreeprettyprinter.example;

import io.github.computerdaddyguy.jfiletreeprettyprinter.FileTreePrettyPrinter;
import io.github.computerdaddyguy.jfiletreeprettyprinter.PathUtils;
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")) {
return "\t\t\t// All API code: controllers, etc.";
}
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "domain")) {
return "\t\t\t// All domain code: value objects, etc.";
}
if (PathUtils.isDirectory(path) && PathUtils.hasName(path, "infra")) {
return "\t\t\t// All infra code: database, email service, etc.";
}
if (PathUtils.isFile(path) && PathUtils.hasName(path, "application.properties")) {
return "\t// Config file";
}
return null;
};
var prettyPrinter = FileTreePrettyPrinter.builder()
.customizeOptions(options -> options.withLineExtension(lineExtension))
.build();
var tree = prettyPrinter.prettyPrint("src/example/resources/line_extension");
System.out.println(tree);
}

}
Empty file.
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.nio.file.Path;
import java.util.Comparator;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
import org.jspecify.annotations.NullMarked;
Expand Down Expand Up @@ -313,9 +314,37 @@ public Predicate<Path> pathFilter() {

* @param filter The filter, <code>null</code> to disable filtering
*/
public PrettyPrintOptions filter(Predicate<Path> filter) {
public PrettyPrintOptions filter(@Nullable Predicate<Path> filter) {
this.pathFilter = filter;
return this;
}

// ---------- Line extension ----------

@Nullable
private Function<Path, String> lineExtension;

@Override
@Nullable
public Function<Path, String> getLineExtension() {
return lineExtension;
}

/**
* Sets a custom line extension function that appends additional text to each
* printed line, allowing you to customize the display of files or directories.
* <p>
* Typical use cases include adding comments, showing file sizes, or displaying metadata.
* <p>
* The function receives the current {@link Path} displayed on the line
* and returns an optional string to be appended.
* If the function returns {@code null}, nothing is added.
*
* @param lineExtension the custom line extension function, or {@code null} to disable
*/
public PrettyPrintOptions withLineExtension(@Nullable Function<Path, String> lineExtension) {
this.lineExtension = lineExtension;
return this;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,60 +26,76 @@ public DefaultTreeEntryRenderer(RenderingOptions options) {

@Override
public String renderTree(TreeEntry entry) {
var depth = Depth.createNewEmpty();
var buff = new StringBuilder();
renderTree(entry, depth, buff);
return buff.toString();
return renderTree(entry, Depth.createNewEmpty());
}

public void renderTree(TreeEntry entry, Depth depth, StringBuilder buff) {
switch (entry) {
case TreeEntry.DirectoryEntry dirEntry -> renderDirectory(buff, depth, dirEntry, List.of(dirEntry.getDir()));
case TreeEntry.FileEntry fileEntry -> renderFile(buff, depth, fileEntry);
case TreeEntry.SkippedChildrenEntry skippedChildrenEntry -> renderSkippedChildrenEntry(buff, depth, skippedChildrenEntry);
case TreeEntry.MaxDepthReachEntry maxDepthReachEntry -> renderMaxDepthReachEntry(buff, depth, maxDepthReachEntry);
}
private String renderTree(TreeEntry entry, Depth depth) {
return switch (entry) {
case TreeEntry.DirectoryEntry dirEntry -> renderDirectory(depth, dirEntry, List.of(dirEntry.getDir()));
case TreeEntry.FileEntry fileEntry -> renderFile(depth, fileEntry);
case TreeEntry.SkippedChildrenEntry skippedChildrenEntry -> renderSkippedChildrenEntry(depth, skippedChildrenEntry);
case TreeEntry.MaxDepthReachEntry maxDepthReachEntry -> renderMaxDepthReachEntry(depth, maxDepthReachEntry);
};
}

private void renderDirectory(StringBuilder buff, Depth depth, DirectoryEntry dirEntry, List<Path> compactPaths) {
private String renderDirectory(Depth depth, DirectoryEntry dirEntry, List<Path> compactPaths) {

if (options.areCompactDirectoriesUsed()
&& !depth.isRoot()
&& dirEntry.getEntries().size() == 1
&& dirEntry.getEntries().get(0) instanceof DirectoryEntry childDirEntry) {
var newCompactPaths = new ArrayList<>(compactPaths);
newCompactPaths.add(childDirEntry.getDir());
renderDirectory(buff, depth, childDirEntry, newCompactPaths);
return;

var extension = computeLineExtension(dirEntry.getDir());
if (extension.isEmpty()) {
var newCompactPaths = new ArrayList<>(compactPaths);
newCompactPaths.add(childDirEntry.getDir());
return renderDirectory(depth, childDirEntry, newCompactPaths);
}
}

buff.append(lineRenderer.renderDirectoryBegin(depth, dirEntry, compactPaths));
var line = lineRenderer.renderDirectoryBegin(depth, dirEntry, compactPaths);
line += computeLineExtension(dirEntry.getDir());

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

if (childIt.hasNext()) {
buff.append('\n');
line += "\n";
}

var childLines = new StringBuilder();

while (childIt.hasNext()) {
var childEntry = childIt.next();
var childDepth = depth.append(childIt.hasNext() ? DepthSymbol.NON_LAST_FILE : DepthSymbol.LAST_FILE);
renderTree(childEntry, childDepth, buff);
childLines.append(renderTree(childEntry, childDepth));
if (childIt.hasNext()) {
buff.append('\n');
childLines.append('\n');
}
}

return line + childLines.toString();
}

private String computeLineExtension(Path path) {
if (options.getLineExtension() == null) {
return "";
}
var extension = options.getLineExtension().apply(path);
return extension == null ? "" : extension;
}

private void renderFile(StringBuilder buff, Depth depth, FileEntry fileEntry) {
buff.append(lineRenderer.renderFile(depth, fileEntry));
private String renderFile(Depth depth, FileEntry fileEntry) {
var line = lineRenderer.renderFile(depth, fileEntry);
line += computeLineExtension(fileEntry.getFile());
return line;
}

private void renderSkippedChildrenEntry(StringBuilder buff, Depth depth, SkippedChildrenEntry skippedChildrenEntry) {
buff.append(lineRenderer.renderChildLimitReached(depth, skippedChildrenEntry));
private String renderSkippedChildrenEntry(Depth depth, SkippedChildrenEntry skippedChildrenEntry) {
return lineRenderer.renderChildLimitReached(depth, skippedChildrenEntry);
}

private void renderMaxDepthReachEntry(StringBuilder buff, Depth depth, MaxDepthReachEntry maxDepthReachEntry) {
buff.append(lineRenderer.renderMaxDepthReached(depth, maxDepthReachEntry));
private String renderMaxDepthReachEntry(Depth depth, MaxDepthReachEntry maxDepthReachEntry) {
return lineRenderer.renderMaxDepthReached(depth, maxDepthReachEntry);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,16 @@
import java.nio.file.Path;
import java.util.List;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

@NullMarked
interface LineRenderer {

@Nullable
String renderDirectoryBegin(Depth depth, DirectoryEntry dirEntry, List<Path> dirs);

@Nullable
String renderFile(Depth depth, FileEntry fileEntry);

@Nullable
String renderChildLimitReached(Depth depth, SkippedChildrenEntry skippedChildrenEntry);

@Nullable
String renderMaxDepthReached(Depth depth, MaxDepthReachEntry maxDepthReachEntry);

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer;

import io.github.computerdaddyguy.jfiletreeprettyprinter.PrettyPrintOptions.TreeFormat;
import java.nio.file.Path;
import java.util.function.Function;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

@NullMarked
public interface RenderingOptions {
Expand All @@ -24,4 +27,11 @@ public interface RenderingOptions {
*/
TreeFormat getTreeFormat();

/**
* The line extension function.
* @return
*/
@Nullable
Function<Path, String> getLineExtension();

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public int getSize() {
return symbols.size();
}

public boolean isRoot() {
return getSize() == 0;
}

public Depth append(DepthSymbol symbol) {
var newList = new ArrayList<DepthSymbol>(symbols);
newList.add(symbol);
Expand Down
Loading
Loading