Skip to content

Commit 2315251

Browse files
first commit
0 parents  commit 2315251

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+849
-0
lines changed

.gitignore

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Compiled class files
2+
*.class
3+
*.jar
4+
*.war
5+
*.ear
6+
7+
# Log files
8+
*.log
9+
hs_err_pid*
10+
11+
# Maven specific
12+
target/
13+
pom.xml.tag
14+
pom.xml.releaseBackup
15+
pom.xml.versionsBackup
16+
release.properties
17+
dependency-reduced-pom.xml
18+
buildNumber.properties
19+
.mvn/timing.properties
20+
.mvn/wrapper/maven-wrapper.jar
21+
22+
# GPG keys (security risk!)
23+
private.gpg
24+
public.gpg
25+
*.asc
26+
27+
# IntelliJ IDEA
28+
.idea/
29+
*.iml
30+
*.iws
31+
out/
32+
33+
# Eclipse
34+
.project
35+
.classpath
36+
.settings/
37+
38+
# NetBeans
39+
nbproject/private/
40+
build/
41+
nbbuild/
42+
dist/
43+
nbdist/
44+
.netbeans/
45+
46+
# VS Code
47+
.vscode/
48+
49+
# OS-specific files
50+
.DS_Store
51+
Thumbs.db
52+
53+
# SonarQube
54+
.sonar/
55+
target/site/
56+
target/surefire-reports/
57+
target/failsafe-reports/
58+
59+
# GitHub Actions cache
60+
.github/workflows/*.log
61+
.github/workflows/cache/

ROADMAP.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Roadmap
2+
3+
## Next version
4+
[x] Use JSpecify annotations
5+
[ ] Builder with options (depth symbols, emoji, colors)
6+
[ ] Max depth option
7+
[ ] Unify dir-in-a-row into a single entry option
8+
[ } Regroup all formatting work under single "LineFormatter"
9+
[ ] Better "isLast" detection algorithm
10+
[ ] Unit tests, using @TempDir
11+
12+
## Other ideas
13+
[ ] Filtering
14+
[ ] Alternative implementation (using DirectoryStream instead of Files.walkFileTree)

pom.xml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>io.github.computerdaddyguy</groupId>
7+
<artifactId>jfiletreeprinter</artifactId>
8+
<version>0.0.1-SNAPSHOT</version>
9+
10+
<properties>
11+
<java.version>21</java.version>
12+
<maven.compiler.source>21</maven.compiler.source>
13+
<maven.compiler.target>21</maven.compiler.target>
14+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15+
16+
<jspecify.version>1.0.0</jspecify.version>
17+
</properties>
18+
19+
<dependencies>
20+
<dependency>
21+
<groupId>org.jspecify</groupId>
22+
<artifactId>jspecify</artifactId>
23+
<version>${jspecify.version}</version>
24+
</dependency>
25+
</dependencies>
26+
27+
</project>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.github.computerdaddyguy.jfiletreeprinter;
2+
3+
import java.io.IOException;
4+
5+
import io.github.computerdaddyguy.jfiletreeprinter.core.FileTreePrettyPrinter;
6+
7+
public class FileWalkTest {
8+
9+
public static void main(String[] args) throws IOException {
10+
test("C:\\Users\\samue\\eclipse-workspace\\jfiletreeprinter\\src\\test\\resources");
11+
// test("C:\\Users\\samue\\eclipse-workspace\\jfiletreeprinter\\src\\test\\resources\\empty_folder");
12+
// test("C:\\Users\\samue\\eclipse-workspace\\jfiletreeprinter\\src\\test\\resources\\folder_with_1_file");
13+
// test("C:\\Users\\samue\\eclipse-workspace\\jfiletreeprinter\\src\\test\\resources\\folder_with_3_files");
14+
// test("C:\\Users\\samue\\eclipse-workspace\\jfiletreeprinter\\src\\test\\resources\\folder_with_3_files_and_subfolders");
15+
// test("C:\\Users\\samue\\eclipse-workspace\\jfiletreeprinter\\src\\test\\resources\\skipped");
16+
// test("C:\\Users\\samue\\eclipse-workspace\\jfiletreeprinter");
17+
}
18+
19+
private static void test(String path) throws IOException {
20+
System.out.println("-------------------------------------------------------");
21+
System.out.println(">>> " + path);
22+
var tree = FileTreePrettyPrinter.createDefault().prettyPrint(path);
23+
System.out.println(tree);
24+
System.out.println("-------------------------------------------------------");
25+
}
26+
27+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.github.computerdaddyguy.jfiletreeprinter.core;
2+
3+
import io.github.computerdaddyguy.jfiletreeprinter.core.visitor.FileTreePrettyPrintVisitor;
4+
import java.io.IOException;
5+
import java.io.UncheckedIOException;
6+
import java.nio.file.Files;
7+
import java.nio.file.Path;
8+
import java.util.Objects;
9+
import org.jspecify.annotations.NullMarked;
10+
11+
@NullMarked
12+
class DefaultFileTreePrettyPrinter implements FileTreePrettyPrinter {
13+
14+
private final FileTreePrettyPrintVisitor visitor;
15+
16+
public DefaultFileTreePrettyPrinter(FileTreePrettyPrintVisitor visitor) {
17+
this.visitor = Objects.requireNonNull(visitor, "visitor cannot be null");
18+
}
19+
20+
@Override
21+
public String prettyPrint(Path path) {
22+
try {
23+
visitor.reset();
24+
Files.walkFileTree(path, visitor);
25+
return visitor.getResult();
26+
} catch (IOException e) {
27+
throw new UncheckedIOException("Exception in pretty-print process of path: " + path.toAbsolutePath(), e);
28+
}
29+
}
30+
31+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.github.computerdaddyguy.jfiletreeprinter.core;
2+
3+
import io.github.computerdaddyguy.jfiletreeprinter.core.visitor.FileTreePrettyPrintVisitor;
4+
import java.nio.file.Path;
5+
import org.jspecify.annotations.NullMarked;
6+
7+
/**
8+
* @implNote Instances of this interface are not thread safe.
9+
*/
10+
@NullMarked
11+
public interface FileTreePrettyPrinter {
12+
13+
String prettyPrint(Path path);
14+
15+
default String prettyPrint(String path) {
16+
return prettyPrint(Path.of(path));
17+
}
18+
19+
static FileTreePrettyPrinter createDefault() {
20+
var visitor = FileTreePrettyPrintVisitor.builder().build();
21+
return new DefaultFileTreePrettyPrinter(visitor);
22+
}
23+
24+
static FileTreePrettyPrinter createWithVisitor(FileTreePrettyPrintVisitor visitor) {
25+
return new DefaultFileTreePrettyPrinter(visitor);
26+
}
27+
28+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.github.computerdaddyguy.jfiletreeprinter.core.predicates;
2+
3+
import java.nio.file.Path;
4+
import java.util.Collection;
5+
import java.util.List;
6+
import java.util.function.Predicate;
7+
import org.jspecify.annotations.NullMarked;
8+
9+
@NullMarked
10+
public final class FileVisitorPredicates {
11+
12+
private FileVisitorPredicates() {
13+
// Helper class
14+
}
15+
16+
public static Predicate<Path> hasName(String name) {
17+
return hasNameIn(List.of(name));
18+
}
19+
20+
public static Predicate<Path> hasNameIn(Collection<String> names) {
21+
return (path) -> names.contains(path.getFileName().toString());
22+
}
23+
24+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package io.github.computerdaddyguy.jfiletreeprinter.core.visitor;
2+
3+
import io.github.computerdaddyguy.jfiletreeprinter.core.visitor.depth.Depth;
4+
import io.github.computerdaddyguy.jfiletreeprinter.core.visitor.depth.DepthFormatter;
5+
import io.github.computerdaddyguy.jfiletreeprinter.core.visitor.depth.DepthSymbol;
6+
import io.github.computerdaddyguy.jfiletreeprinter.core.visitor.filename.FileNameFormatter;
7+
import io.github.computerdaddyguy.jfiletreeprinter.core.visitor.limit.ChildVisitCounter;
8+
import io.github.computerdaddyguy.jfiletreeprinter.core.visitor.limit.ChildrenLimitConfig;
9+
import java.io.File;
10+
import java.io.IOException;
11+
import java.nio.file.FileVisitResult;
12+
import java.nio.file.Path;
13+
import java.nio.file.attribute.BasicFileAttributes;
14+
import org.jspecify.annotations.NullMarked;
15+
import org.jspecify.annotations.Nullable;
16+
17+
@NullMarked
18+
class DefaultFileTreePrettyPrintVisitor implements FileTreePrettyPrintVisitor {
19+
20+
private final DepthFormatter depthFormatter;
21+
private final ChildrenLimitConfig childrenLimitConfig;
22+
private final FileNameFormatter fileNameFormatter;
23+
24+
private StringBuilder buff;
25+
private Depth depth;
26+
private ChildVisitCounter counter;
27+
28+
public DefaultFileTreePrettyPrintVisitor(
29+
DepthFormatter depthFormatter,
30+
ChildrenLimitConfig childrenLimitConfig, FileNameFormatter fileNameFormatter
31+
) {
32+
super();
33+
this.depthFormatter = depthFormatter;
34+
this.childrenLimitConfig = childrenLimitConfig;
35+
this.fileNameFormatter = fileNameFormatter;
36+
37+
reset();
38+
}
39+
40+
@Override
41+
public String getResult() {
42+
return buff.toString();
43+
}
44+
45+
@Override
46+
public void reset() {
47+
this.buff = new StringBuilder();
48+
this.depth = new Depth();
49+
this.counter = new ChildVisitCounter(childrenLimitConfig.getChildrenLimitFunction());
50+
}
51+
52+
@Override
53+
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
54+
// DEPTH
55+
updateDepth(dir);
56+
_appendBuff_depth();
57+
depth = depth.append(DepthSymbol.NON_LAST_FILE); // assume not last until proven otherwise
58+
59+
// FILE
60+
_appendBuff_path(fileNameFormatter.formatDirectoryBegin(dir, attrs));
61+
62+
// COUNTER
63+
counter.registerChildVisitInCurrentDir(dir);
64+
counter.enterNewDirectory(dir);
65+
66+
return FileVisitResult.CONTINUE;
67+
}
68+
69+
@Override
70+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
71+
// COUNTER
72+
if (counter.exceedsCurrentLimit()) {
73+
return FileVisitResult.SKIP_SIBLINGS;
74+
}
75+
counter.registerChildVisitInCurrentDir(file);
76+
77+
// DEPTH
78+
updateDepth(file);
79+
_appendBuff_depth();
80+
81+
// FILE
82+
_appendBuff_path(fileNameFormatter.formatFile(file, attrs));
83+
84+
return FileVisitResult.CONTINUE;
85+
}
86+
87+
@Override
88+
public FileVisitResult visitFileFailed(Path file, @Nullable IOException exc) throws IOException {
89+
// DEPTH
90+
updateDepth(file);
91+
_appendBuff_depth();
92+
93+
// FILE
94+
if (exc != null) {
95+
_appendBuff_path(fileNameFormatter.formatFileException(file, exc));
96+
}
97+
98+
return FileVisitResult.CONTINUE;
99+
}
100+
101+
@Override
102+
public FileVisitResult postVisitDirectory(Path dir, @Nullable IOException exc) throws IOException {
103+
104+
if (exc != null) {
105+
_appendBuff_path(fileNameFormatter.formatDirectoryException(dir, exc));
106+
}
107+
108+
// DEPTH
109+
depth = depth.pop();
110+
111+
// COUNTER
112+
var limitReached = counter.exceedsCurrentLimit();
113+
if (limitReached) {
114+
depth = depth.append(DepthSymbol.LAST_FILE);
115+
_appendBuff_depth();
116+
_appendBuff_path(
117+
childrenLimitConfig.getFormatter().formatLimitReached(counter.notVisitedInCurrentDirCount())
118+
);
119+
depth = depth.pop();
120+
}
121+
counter.exitCurrentDirectory();
122+
123+
if (limitReached) {
124+
return FileVisitResult.SKIP_SIBLINGS;
125+
}
126+
return FileVisitResult.CONTINUE;
127+
}
128+
129+
private void updateDepth(Path path) {
130+
if (depth.isEmpty()) {
131+
return;
132+
}
133+
134+
var isLast = isLastChild(path);
135+
depth = depth.pop();
136+
depth = depth.append(isLast ? DepthSymbol.LAST_FILE : DepthSymbol.NON_LAST_FILE);
137+
}
138+
139+
private boolean isLastChild(Path path) {
140+
Path parent = path.getParent();
141+
File[] siblings = parent.toFile().listFiles();
142+
return siblings != null && siblings[siblings.length - 1].toPath().equals(path);
143+
}
144+
145+
private void _appendBuff_depth() {
146+
buff.append(depthFormatter.format(depth));
147+
}
148+
149+
private void _appendBuff_path(@Nullable String str) {
150+
if (str != null) {
151+
buff.append(str).append('\n');
152+
}
153+
}
154+
155+
}

0 commit comments

Comments
 (0)