diff --git a/CHANGELOG.md b/CHANGELOG.md index 553305e..1f95050 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- ## [0.0.6] - Unreleased +### Added +- Emojis: option to use custom mapping + ### Changed - Child limit: do not print skipped children count - Max depth: do not print "max depth reached" diff --git a/README.md b/README.md index 5d31ac7..097399f 100644 --- a/README.md +++ b/README.md @@ -167,13 +167,13 @@ sorting/ ``` ## Emojis ❀️ -If your terminal supports them, you can choose to use emojis. +You can choose to use default built-in emojis, or define your own emoji mapping. Folders use the πŸ“‚ emoji, and files will have an emoji depending on their extension (when applicable). ```java // Example: Emojis.java var prettyPrinter = FileTreePrettyPrinter.builder() - .customizeOptions(options -> options.withEmojis(true)) + .customizeOptions(options -> options.withDefaultEmojis()) // or withEmojis(EmojiMapping) for custom mapping .build(); ``` @@ -194,9 +194,6 @@ var prettyPrinter = FileTreePrettyPrinter.builder() └─ 🎬 file.avi ``` -> [!TIP] -> *Idea for a future version: option to allow custom emoji mapping* - ## Child limit You can set a fixed limit to the number of children displayed for each directory. Each directory and file that pass the filter (if set) counts for one. diff --git a/ROADMAP.md b/ROADMAP.md index 569ecb9..89834c4 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -5,6 +5,7 @@ - [x] Option: filtering - [x] Option: ordering - [x] Option: emojis + - [x] Option: custom emojis mapping - [x] Option: compact directories display - [x] Option: line extension (=additional text after the file name) - [x] Option: children limit (static & dynamic) @@ -31,8 +32,6 @@ - [x] Publish on Maven Central! ## To do -- [ ] Option: hide number of skipped files and folders for child limit -- [ ] Option: custom emojis - [ ] Option: custom tree format ## Backlog / To analyze / To implement if requested diff --git a/assets/project-structure.png b/assets/project-structure.png index 6c88b78..19d0c8d 100644 Binary files a/assets/project-structure.png and b/assets/project-structure.png differ diff --git a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/Emojis.java b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/Emojis.java index c3a4d65..925ea0a 100644 --- a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/Emojis.java +++ b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/Emojis.java @@ -6,7 +6,7 @@ public class Emojis { public static void main(String[] args) { var prettyPrinter = FileTreePrettyPrinter.builder() - .customizeOptions(options -> options.withEmojis(true)) + .customizeOptions(options -> options.withDefaultEmojis()) // or withEmojis(EmojiMapping) for custom mapping .build(); var tree = prettyPrinter.prettyPrint("src/example/resources/emojis"); System.out.println(tree); diff --git a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/ProjectStructure.java b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/ProjectStructure.java index 10b3978..66f0c0a 100644 --- a/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/ProjectStructure.java +++ b/src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/ProjectStructure.java @@ -87,7 +87,7 @@ public static void main(String[] args) { var prettyPrinter = FileTreePrettyPrinter.builder() .customizeOptions( options -> options - .withEmojis(true) // Use emojis! + .withDefaultEmojis() // Use emojis! .withCompactDirectories(true) // Inline directory chains: "src/main/java/..." .filterDirectories(dirFilter) .filterFiles(fileFilter) diff --git a/src/example/resources/emojis/code/github/changelog b/src/example/resources/emojis/applications/executables/file.bin similarity index 100% rename from src/example/resources/emojis/code/github/changelog rename to src/example/resources/emojis/applications/executables/file.bin diff --git a/src/example/resources/emojis/code/github/changelog.md b/src/example/resources/emojis/code/build_tools/makefile similarity index 100% rename from src/example/resources/emojis/code/github/changelog.md rename to src/example/resources/emojis/code/build_tools/makefile diff --git a/src/example/resources/emojis/code/github/licence b/src/example/resources/emojis/code/docker-compose.yml similarity index 100% rename from src/example/resources/emojis/code/github/licence rename to src/example/resources/emojis/code/docker-compose.yml diff --git a/src/example/resources/emojis/code/github/licence.md b/src/example/resources/emojis/internet/github/changelog similarity index 100% rename from src/example/resources/emojis/code/github/licence.md rename to src/example/resources/emojis/internet/github/changelog diff --git a/src/example/resources/emojis/code/github/readme b/src/example/resources/emojis/internet/github/changelog.md similarity index 100% rename from src/example/resources/emojis/code/github/readme rename to src/example/resources/emojis/internet/github/changelog.md diff --git a/src/example/resources/emojis/code/github/readme.md b/src/example/resources/emojis/internet/github/contributing similarity index 100% rename from src/example/resources/emojis/code/github/readme.md rename to src/example/resources/emojis/internet/github/contributing diff --git a/src/example/resources/emojis/code/github/roadmap b/src/example/resources/emojis/internet/github/contributing.md similarity index 100% rename from src/example/resources/emojis/code/github/roadmap rename to src/example/resources/emojis/internet/github/contributing.md diff --git a/src/example/resources/emojis/code/github/roadmap.md b/src/example/resources/emojis/internet/github/license similarity index 100% rename from src/example/resources/emojis/code/github/roadmap.md rename to src/example/resources/emojis/internet/github/license diff --git a/src/example/resources/emojis/code/github/security b/src/example/resources/emojis/internet/github/license.md similarity index 100% rename from src/example/resources/emojis/code/github/security rename to src/example/resources/emojis/internet/github/license.md diff --git a/src/example/resources/emojis/code/github/security.md b/src/example/resources/emojis/internet/github/readme similarity index 100% rename from src/example/resources/emojis/code/github/security.md rename to src/example/resources/emojis/internet/github/readme diff --git a/src/example/resources/emojis/internet/github/readme.md b/src/example/resources/emojis/internet/github/readme.md new file mode 100644 index 0000000..e69de29 diff --git a/src/example/resources/emojis/internet/github/roadmap b/src/example/resources/emojis/internet/github/roadmap new file mode 100644 index 0000000..e69de29 diff --git a/src/example/resources/emojis/internet/github/roadmap.md b/src/example/resources/emojis/internet/github/roadmap.md new file mode 100644 index 0000000..e69de29 diff --git a/src/example/resources/emojis/internet/github/security b/src/example/resources/emojis/internet/github/security new file mode 100644 index 0000000..e69de29 diff --git a/src/example/resources/emojis/internet/github/security.md b/src/example/resources/emojis/internet/github/security.md new file mode 100644 index 0000000..e69de29 diff --git a/src/example/resources/emojis/internet/robots.txt b/src/example/resources/emojis/internet/robots.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java index 58d500c..a48608c 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java @@ -1,6 +1,7 @@ package io.github.computerdaddyguy.jfiletreeprettyprinter; import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.RenderingOptions; +import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji.EmojiMapping; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.ScanningOptions; import java.nio.file.Path; import java.nio.file.PathMatcher; @@ -95,21 +96,28 @@ public PrettyPrintOptions withTreeFormat(TreeFormat treeFormat) { // ---------- Emojis ---------- - private boolean emojis = false; + private EmojiMapping emojiMapping = EmojiMapping.none(); @Override - public boolean areEmojisUsed() { - return emojis; + public EmojiMapping getEmojiMapping() { + return emojiMapping; } /** - * Whether or not use emojis in directory/filename rendering. Not all terminals supports emojis. - * Default is {@code false}. + * Use default emojis for directory/filename rendering. + */ + public PrettyPrintOptions withDefaultEmojis() { + this.emojiMapping = EmojiMapping.createDefault(); + return this; + } + + /** + * Use the given emojis mapping for directory/filename rendering. * - * @param useEmojis {@code true} to use emojis, {@code false} otherwise. + * @see EmojiMapping */ - public PrettyPrintOptions withEmojis(boolean useEmojis) { - this.emojis = useEmojis; + public PrettyPrintOptions withEmojis(EmojiMapping mapping) { + this.emojiMapping = Objects.requireNonNull(mapping, "mapping is null"); return this; } diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/DefaultFileFormatter.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/DefaultFileFormatter.java similarity index 99% rename from src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/DefaultFileFormatter.java rename to src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/DefaultFileFormatter.java index b4b5d2f..14cfa2a 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/DefaultFileFormatter.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/DefaultFileFormatter.java @@ -1,4 +1,4 @@ -package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.file; +package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.DirectoryEntry; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.FileEntry; diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/DefaultLineRenderer.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/DefaultLineRenderer.java index 9a09b47..276b20f 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/DefaultLineRenderer.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/DefaultLineRenderer.java @@ -2,7 +2,6 @@ import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.depth.Depth; import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.depth.DepthFormatter; -import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.file.FileFormatter; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.DirectoryEntry; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.FileEntry; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.MaxDepthReachEntry; diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/EmojiFileFormatter.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/EmojiFileFormatter.java similarity index 93% rename from src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/EmojiFileFormatter.java rename to src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/EmojiFileFormatter.java index 9ce524d..58774f9 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/EmojiFileFormatter.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/EmojiFileFormatter.java @@ -1,5 +1,6 @@ -package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.file; +package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer; +import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji.EmojiMapping; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.DirectoryEntry; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.FileEntry; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.MaxDepthReachEntry; @@ -22,7 +23,7 @@ public EmojiFileFormatter(FileFormatter decorated, EmojiMapping emojiMapping) { } private String getFileEmojiPrefix(Path p) { - var emoji = emojiMapping.getFileEmoji(p); + var emoji = emojiMapping.getPathEmoji(p); return getEmojiPrefix(emoji); } diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/FileFormatter.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/FileFormatter.java similarity index 85% rename from src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/FileFormatter.java rename to src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/FileFormatter.java index 4df01f5..19c349d 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/FileFormatter.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/FileFormatter.java @@ -1,5 +1,6 @@ -package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.file; +package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer; +import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji.EmojiMapping; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.DirectoryEntry; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.FileEntry; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.MaxDepthReachEntry; @@ -9,7 +10,7 @@ import org.jspecify.annotations.NullMarked; @NullMarked -public interface FileFormatter { +interface FileFormatter { String formatDirectoryBegin(DirectoryEntry dirEntry, List dirs); @@ -23,10 +24,6 @@ static FileFormatter createDefault() { return new DefaultFileFormatter(); } - static FileFormatter wrapWithEmojis(FileFormatter decorated) { - return wrapWithEmojis(decorated, EmojiMapping.createDefault()); - } - static FileFormatter wrapWithEmojis(FileFormatter decorated, EmojiMapping emojiMapping) { return new EmojiFileFormatter(decorated, emojiMapping); } diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/LineRenderer.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/LineRenderer.java index f59258f..6f3b96e 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/LineRenderer.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/LineRenderer.java @@ -2,7 +2,6 @@ import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.depth.Depth; import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.depth.DepthFormatter; -import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.file.FileFormatter; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.DirectoryEntry; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.FileEntry; import io.github.computerdaddyguy.jfiletreeprettyprinter.scanner.TreeEntry.MaxDepthReachEntry; @@ -31,9 +30,7 @@ static LineRenderer create(RenderingOptions options) { var treeFormatter = DepthFormatter.getInstance(options.getTreeFormat()); var fileFormatter = FileFormatter.createDefault(); - if (options.areEmojisUsed()) { - fileFormatter = FileFormatter.wrapWithEmojis(fileFormatter); - } + fileFormatter = FileFormatter.wrapWithEmojis(fileFormatter, options.getEmojiMapping()); return new DefaultLineRenderer(treeFormatter, fileFormatter); } diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/RenderingOptions.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/RenderingOptions.java index 9fc63a4..b7fccfb 100644 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/RenderingOptions.java +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/RenderingOptions.java @@ -1,6 +1,7 @@ package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer; import io.github.computerdaddyguy.jfiletreeprettyprinter.PrettyPrintOptions.TreeFormat; +import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji.EmojiMapping; import java.nio.file.Path; import java.util.function.Function; import org.jspecify.annotations.NullMarked; @@ -10,10 +11,10 @@ public interface RenderingOptions { /** - * Are emojis used (filename, etc.)? + * The emoji mapping to use * @return */ - boolean areEmojisUsed(); + EmojiMapping getEmojiMapping(); /** * Are directories compacted into one entry? diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/DefaultEmojiMapping.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/DefaultEmojiMapping.java new file mode 100644 index 0000000..b233fcc --- /dev/null +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/DefaultEmojiMapping.java @@ -0,0 +1,35 @@ +package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji; + +import io.github.computerdaddyguy.jfiletreeprettyprinter.PathMatchers; +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +class DefaultEmojiMapping implements EmojiMapping { + + private final Function dirEmojis; + private final String defaultDirEmoji; + + private final Function fileEmojis; + private final String defaultFileEmoji; + + public DefaultEmojiMapping(String defaultDirEmoji, Function dirEmojis, String defaultFileEmoji, Function fileEmojis) { + super(); + this.dirEmojis = Objects.requireNonNull(dirEmojis, "dirEmojis is null"); + this.defaultDirEmoji = Objects.requireNonNull(defaultDirEmoji, "defaultDirEmoji is null"); + this.fileEmojis = Objects.requireNonNull(fileEmojis, "fileEmojis is null"); + this.defaultFileEmoji = Objects.requireNonNull(defaultFileEmoji, "defaultFileEmoji is null"); + } + + @Override + public @Nullable String getPathEmoji(Path path) { + return PathMatchers.isDirectory().matches(path) + ? Optional.ofNullable(dirEmojis.apply(path)).orElse(defaultDirEmoji) + : Optional.ofNullable(fileEmojis.apply(path)).orElse(defaultFileEmoji); + } + +} diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/DefaultEmojiMappingBuilder.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/DefaultEmojiMappingBuilder.java new file mode 100644 index 0000000..8b515dd --- /dev/null +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/DefaultEmojiMappingBuilder.java @@ -0,0 +1,288 @@ +package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji; + +import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji.PathMatcherEmojiFunction.EmojiMatch; +import java.nio.file.PathMatcher; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.jspecify.annotations.NullMarked; + +@NullMarked +class DefaultEmojiMappingBuilder implements EmojiMappingBuilder { + + private static final Map DEFAULT_FILENAME_EMOJIS = buildDefaultFileNamesEmojis(); + private static final Map DEFAULT_FILEEXTENSIONS_EMOJIS = buildDefaultFileExtensionsEmojis(); + + private String defaultDirEmoji = "πŸ“‚"; + private List dirMatchersEmojis = new ArrayList<>(); + private Map dirNamesEmojis = new HashMap<>(); + + private String defaultFileEmoji = "πŸ“„"; + private List fileMatchersEmojis = new ArrayList<>(); + private Map fileNamesEmojis = new HashMap<>(); + private Map fileExtensionsEmojis = new HashMap<>(); + + public static DefaultEmojiMappingBuilder newBlankInstance() { + return new DefaultEmojiMappingBuilder(); + } + + public static DefaultEmojiMappingBuilder newDefaultInstance() { + var instance = newBlankInstance(); + instance.fileNamesEmojis = new HashMap<>(DEFAULT_FILENAME_EMOJIS); + instance.fileExtensionsEmojis = new HashMap<>(DEFAULT_FILEEXTENSIONS_EMOJIS); + return instance; + } + + @Override + public EmojiMapping build() { + return new DefaultEmojiMapping( + defaultDirEmoji, + new SequentialEmojiFunction( + List.of( + new PathMatcherEmojiFunction(dirMatchersEmojis), + new PathNameEmojiFunction(dirNamesEmojis) + ) + ), + defaultFileEmoji, + new SequentialEmojiFunction( + List.of( + new PathMatcherEmojiFunction(fileMatchersEmojis), + new PathNameEmojiFunction(fileNamesEmojis), + new PathExtensionEmojiFunction(fileExtensionsEmojis) + ) + ) + ); + } + + // ======================================================================================== + + // ---------- Directories ----------- + + @Override + public EmojiMappingBuilder setDefaultDirectoryEmoji(String emoji) { + this.defaultDirEmoji = Objects.requireNonNull(emoji, "emoji is null"); + return this; + } + + @Override + public EmojiMappingBuilder setDirectoryNameEmoji(String dirName, String emoji) { + Objects.requireNonNull(dirName, "dirName is null"); + Objects.requireNonNull(emoji, "emoji is null"); + this.dirNamesEmojis.put(dirName, emoji); + return this; + } + + @Override + public EmojiMappingBuilder addDirectoryEmoji(PathMatcher matcher, String emoji) { + Objects.requireNonNull(matcher, "matcher is null"); + Objects.requireNonNull(emoji, "emoji is null"); + this.dirMatchersEmojis.add(new EmojiMatch(matcher, emoji)); + return this; + } + + // ---------- Files ----------- + + @Override + public EmojiMappingBuilder setDefaultFileEmoji(String emoji) { + this.defaultFileEmoji = Objects.requireNonNull(emoji, "emoji is null"); + return this; + } + + @Override + public EmojiMappingBuilder setFileNameEmoji(String fileName, String emoji) { + Objects.requireNonNull(fileName, "fileName is null"); + Objects.requireNonNull(emoji, "emoji is null"); + this.fileNamesEmojis.put(fileName, emoji); + return this; + } + + @Override + public EmojiMappingBuilder setFileExtensionEmoji(String fileExtension, String emoji) { + Objects.requireNonNull(fileExtension, "fileExtension is null"); + Objects.requireNonNull(emoji, "emoji is null"); + this.fileExtensionsEmojis.put(fileExtension, emoji); + return this; + } + + @Override + public EmojiMappingBuilder addFileEmoji(PathMatcher matcher, String emoji) { + Objects.requireNonNull(matcher, "matcher is null"); + Objects.requireNonNull(emoji, "emoji is null"); + this.fileMatchersEmojis.add(new EmojiMatch(matcher, emoji)); + return this; + } + + // ======================================================================================== + + private static Map buildDefaultFileNamesEmojis() { + Map map = new HashMap<>(); + + // ---------- Applications ---------- + + // ---------- Archives ---------- + + // ---------- Code ---------- + map.put(".gitignore", "🚫"); + map.put("dockerfile", "🐳"); + map.put("docker-compose.yaml", "βš™οΈ"); + map.put("docker-compose.yml", "βš™οΈ"); + map.put("jenkinsfile", "🀡"); + + // Code - build tools + map.put("makefile", "πŸ› οΈ"); + map.put("pom.xml", "πŸ› οΈ"); + map.put("build.gradle", "πŸ› οΈ"); + map.put("package.json", "πŸ› οΈ"); + + // ---------- Data ---------- + + // ---------- Doc ---------- + + // ---------- Internet ---------- + map.put("robots.txt", "πŸ€–"); + + // Internet - github + map.put("readme", "πŸ“˜"); + map.put("readme.md", "πŸ“˜"); + map.put("roadmap", "πŸ—ΊοΈ"); + map.put("roadmap.md", "πŸ—ΊοΈ"); + map.put("license", "βš–οΈ"); + map.put("license.md", "βš–οΈ"); + map.put("changelog", "πŸ†•"); + map.put("changelog.md", "πŸ†•"); + map.put("security", "πŸ›‘οΈ"); + map.put("security.md", "πŸ›‘οΈ"); + map.put("todo", "βœ…"); + map.put("todo.md", "βœ…"); + map.put("contributing", "🀝"); + map.put("contributing.md", "🀝"); + + // ---------- Media ---------- + + return map; + } + + private static Map buildDefaultFileExtensionsEmojis() { + Map map = new HashMap<>(); + + // ---------- Applications ---------- + + // Applications - executables + map.put("exe", "βš™οΈ"); + map.put("bin", "βš™οΈ"); + map.put("msi", "πŸ“¦"); + map.put("apk", "πŸ“±"); + map.put("ipa", "πŸ“±"); + map.put("app", "πŸ–₯️"); + + // Applications - libs + map.put("dll", "🧩"); + map.put("lib", "🧩"); + map.put("so", "🧩"); + + // ---------- Archives ---------- + map.put("7z", "πŸ“¦"); + map.put("gz", "πŸ“¦"); + map.put("img", "πŸ’Ώ"); + map.put("iso", "πŸ’Ώ"); + map.put("tar", "πŸ“¦"); + map.put("rar", "πŸ“¦"); + map.put("zip", "πŸ“¦"); + + // ---------- Code ---------- + + // Code - build tools + + // Code - lang + map.put("java", "β˜•"); + map.put("class", "β˜•"); + map.put("jar", "πŸ“¦"); + map.put("py", "🐍"); + map.put("js", "⚑"); + map.put("ts", "πŸ”·"); + map.put("c", "πŸ’ "); + map.put("cpp", "πŸ’ "); + map.put("cs", "πŸ’ "); + map.put("css", "🎨"); + map.put("scss", "🎨"); + map.put("less", "🎨"); + map.put("html", "🌐"); + map.put("htm", "🌐"); + map.put("htmx", "🌐"); + map.put("php", "🐘"); + map.put("sql", "πŸ—„οΈ"); + map.put("vue", "🟩"); + + // Code - scripting + map.put("sh", "πŸ“œ"); + map.put("bash", "πŸ“œ"); + map.put("bat", "πŸ“œ"); + + // ---------- Data ---------- + map.put("cfg", "βš™οΈ"); + map.put("conf", "βš™οΈ"); + map.put("csv", "πŸ“Š"); + map.put("ini", "βš™οΈ"); + map.put("properties", "βš™οΈ"); + map.put("json", "πŸ“"); + map.put("ods", "πŸ“Š"); + map.put("xls", "πŸ“Š"); + map.put("xlsx", "πŸ“Š"); + map.put("xml", "πŸ“"); + map.put("yaml", "πŸ“"); + map.put("yml", "πŸ“"); + + // ---------- Doc ---------- + map.put("doc", "πŸ“"); + map.put("docx", "πŸ“"); + map.put("epub", "πŸ“š"); + map.put("md", "πŸ“"); + map.put("odt", "πŸ“"); + map.put("pdf", "πŸ“•"); + map.put("rtf", "πŸ“"); + map.put("txt", "πŸ“"); + + // ---------- Internet ---------- + + // ---------- Media ---------- + // Media - Audio + map.put("aac", "🎡"); + map.put("flac", "🎡"); + map.put("midi", "🎹"); + map.put("mp3", "🎡"); + map.put("ogg", "🎡"); + map.put("wav", "🎡"); + + // Media - Images + map.put("bmp", "πŸ–ΌοΈ"); + map.put("gif", "🎞️"); + map.put("jpeg", "πŸ–ΌοΈ"); + map.put("jpg", "πŸ–ΌοΈ"); + map.put("png", "πŸ–ΌοΈ"); + map.put("svg", "βœ’οΈ"); + map.put("ico", "πŸ–ΌοΈ"); + + // Media - Video + map.put("avi", "🎬"); + map.put("mkv", "🎬"); + map.put("mov", "🎬"); + map.put("mp4", "🎬"); + map.put("webm", "🎬"); + map.put("wmv", "🎬"); + + // ---------- System ---------- + map.put("bak", "πŸ’Ύ"); + map.put("log", "πŸ“œ"); + map.put("tmp", "πŸ—‘οΈ"); + map.put("key", "πŸ”‘"); + map.put("pem", "πŸ”"); + map.put("crt", "πŸ”"); + map.put("pub", "πŸ”“"); + + return map; + + } + +} \ No newline at end of file diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/EmojiMapping.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/EmojiMapping.java new file mode 100644 index 0000000..3d6e982 --- /dev/null +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/EmojiMapping.java @@ -0,0 +1,34 @@ +package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji; + +import java.nio.file.Path; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public interface EmojiMapping { + + /** + * Get the emoji to display for the given file (i.e. the file type icon). + * + * @return the emoji to use, or null if not emoji to use + */ + @Nullable + String getPathEmoji(Path path); + + static EmojiMapping none() { + return path -> null; + } + + static EmojiMapping createDefault() { + return DefaultEmojiMappingBuilder.newDefaultInstance().build(); + } + + static EmojiMappingBuilder builderFromDefault() { + return DefaultEmojiMappingBuilder.newDefaultInstance(); + } + + static EmojiMappingBuilder builderFromBlank() { + return DefaultEmojiMappingBuilder.newBlankInstance(); + } + +} diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/EmojiMappingBuilder.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/EmojiMappingBuilder.java new file mode 100644 index 0000000..57a79d2 --- /dev/null +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/EmojiMappingBuilder.java @@ -0,0 +1,131 @@ +package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji; + +import io.github.computerdaddyguy.jfiletreeprettyprinter.PathMatchers; +import java.nio.file.PathMatcher; +import org.jspecify.annotations.NullMarked; + +/** + * A builder interface for creating an {@link EmojiMapping}, which determines + * which emoji (if any) should be displayed for each file or directory + * in the pretty-printed tree. + * + *

Emoji mappings can be defined based on directory names, file names, + * file extensions, or arbitrary path matchers. + * If multiple rules apply to a given path, the following precedence order is used: + *

+ * + *
    + *
  1. {@code addXXXEmoji(...)} β€” highest precedence (custom matchers)
  2. + *
  3. {@code setXXXNameEmoji(...)} β€” name-based mapping
  4. + *
  5. {@code setXXXExtensionEmoji(...)} β€” extension-based mapping (for files only)
  6. + *
  7. {@code setDefaultXXXEmoji(...)} β€” lowest precedence (fallback)
  8. + *
+ * + *

All emoji strings must be non-{@code null}. To override or remove a mapping, + * create a new builder instance instead of passing {@code null} values.

+ * + *

Example usage:

+ *
{@code
+ * var emojiMapping = EmojiMapping.builderFromBlank()
+ *     .setDefaultDirectoryEmoji("πŸ“‚")
+ *     .setDefaultFileEmoji("πŸ“„")
+ *     .setFileExtensionEmoji("java", "β˜•")
+ *     .setFileNameEmoji("Dockerfile", "🐳")
+ *     .addFileEmoji(PathMatchers.hasExtension("zip"), "πŸ“¦")
+ *     .build();
+ * }
+ * + * @see EmojiMapping + * @see EmojiMapping#builderFromBlank() + * @see EmojiMapping#builderFromDefault() + * @see PathMatchers + */ +@NullMarked +public interface EmojiMappingBuilder { + + /** + * Builds an immutable {@link EmojiMapping} based on the configured rules. + * + * @return a finalized {@link EmojiMapping} instance + */ + EmojiMapping build(); + + // ---------- Directories ----------- + + /** + * Sets the default emoji used for directories when no other directory rule matches. + * + * @param emoji the emoji string to display for unmatched directories + * @return this builder for chaining + * @throws NullPointerException if {@code emoji} is {@code null} + */ + EmojiMappingBuilder setDefaultDirectoryEmoji(String emoji); + + /** + * Associates a specific emoji with a directory of the given name. + * The match is case-insensitive and applies only to directories. + * + * @param dirName the directory name to match (case insensitive) + * @param emoji the emoji to display for that directory + * @return this builder for chaining + * @throws NullPointerException if {@code dirName} or {@code emoji} is {@code null} + */ + EmojiMappingBuilder setDirectoryNameEmoji(String dirName, String emoji); + + /** + * Adds a custom directory emoji rule using a {@link PathMatcher}. + * This provides the highest precedence among directory mappings. + * + * @param matcher the matcher defining which directories the emoji applies to + * @param emoji the emoji to display for matching directories + * @return this builder for chaining + * @throws NullPointerException if {@code matcher} or {@code emoji} is {@code null} + */ + EmojiMappingBuilder addDirectoryEmoji(PathMatcher matcher, String emoji); + + // ---------- Files ----------- + + /** + * Sets the default emoji used for files when no other file rule matches. + * + * @param emoji the emoji string to display for unmatched files + * @return this builder for chaining + * @throws NullPointerException if {@code emoji} is {@code null} + */ + EmojiMappingBuilder setDefaultFileEmoji(String emoji); + + /** + * Associates a specific emoji with a file of the given name. + * The match is case-insensitive and applies only to files. + * + * @param fileName the exact file name to match + * @param emoji the emoji to display for that file + * @return this builder for chaining + * @throws NullPointerException if {@code fileName} or {@code emoji} is {@code null} + */ + EmojiMappingBuilder setFileNameEmoji(String fileName, String emoji); + + /** + * Associates an emoji with all files having the specified extension. + * The extension should be provided without the leading dot. + * Matching is case-insensitive. + * + * @param fileExtension the file extension (without the leading dot) + * @param emoji the emoji to display for files with that extension + * @return this builder for chaining + * @throws NullPointerException if {@code fileExtension} or {@code emoji} is {@code null} + */ + EmojiMappingBuilder setFileExtensionEmoji(String fileExtension, String emoji); + + /** + * Adds a custom file emoji rule using a {@link PathMatcher}. + * This provides the highest precedence among file mappings. + * + * @param matcher the matcher defining which files the emoji applies to + * @param emoji the emoji to display for matching files + * @return this builder for chaining + * @throws NullPointerException if {@code matcher} or {@code emoji} is {@code null} + */ + EmojiMappingBuilder addFileEmoji(PathMatcher matcher, String emoji); + +} diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/MappingUtils.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/MappingUtils.java new file mode 100644 index 0000000..0a63c2f --- /dev/null +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/MappingUtils.java @@ -0,0 +1,25 @@ +package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.stream.Collectors; + +final class MappingUtils { + + private MappingUtils() { + // Helper class + } + + public static Map toLowerCaseKeys(Map mapping) { + Objects.requireNonNull(mapping, "mapping is null"); + return mapping.entrySet().stream() + .collect( + Collectors.toMap( + entry -> entry.getKey().toLowerCase(), + Entry::getValue + ) + ); + } + +} diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/PathExtensionEmojiFunction.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/PathExtensionEmojiFunction.java new file mode 100644 index 0000000..399c731 --- /dev/null +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/PathExtensionEmojiFunction.java @@ -0,0 +1,59 @@ +package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +class PathExtensionEmojiFunction implements Function { + + private final Map mapping; + + public PathExtensionEmojiFunction(Map mapping) { + this.mapping = MappingUtils.toLowerCaseKeys(mapping); + } + + @Override + @Nullable + public String apply(Path path) { + if (path.getFileName() == null) { // if root + return null; + } + var extensions = getExtensions(path); + String emoji = null; + for (var ext : extensions) { + emoji = mapping.get(ext); + if (emoji != null) { + return emoji; + } + } + return null; + } + + /** + * Build the list of all extensions of given path, from most specific to less specific. + * For "myFile.txt", return list ["txt"] + * For "myFile.foo.bar.baz", return list ["foo.bar.baz", "bar.baz", "baz"] + * For "myFile", return empty list (= no extension) + */ + private List getExtensions(Path path) { + String[] parts = path.getFileName().toString().toLowerCase().split("\\."); + if (parts.length == 1) { + return List.of(); // No extension + } + var extensions = new ArrayList(); + for (int i = parts.length - 1; i >= 1; i--) { + if (extensions.isEmpty()) { + extensions.add(parts[i]); + } else { + extensions.addFirst(parts[i] + "." + extensions.getFirst()); + } + } + return extensions; + } + +} diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/PathMatcherEmojiFunction.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/PathMatcherEmojiFunction.java new file mode 100644 index 0000000..edb8d4e --- /dev/null +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/PathMatcherEmojiFunction.java @@ -0,0 +1,40 @@ +package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji; + +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +class PathMatcherEmojiFunction implements Function { + + private final List mapping; + + public PathMatcherEmojiFunction(List mapping) { + super(); + this.mapping = Objects.requireNonNull(mapping, "mapping is null"); + } + + @Override + @Nullable + public String apply(Path path) { + return mapping.stream() + .filter(item -> item.matches(path)) + .findFirst() + .map(EmojiMatch::emoji) + .orElse(null); + } + + public static record EmojiMatch(PathMatcher matcher, String emoji) implements PathMatcher { + + @Override + public boolean matches(Path path) { + return matcher.matches(path); + } + + } + +} diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/PathNameEmojiFunction.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/PathNameEmojiFunction.java new file mode 100644 index 0000000..b0d906c --- /dev/null +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/PathNameEmojiFunction.java @@ -0,0 +1,27 @@ +package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji; + +import java.nio.file.Path; +import java.util.Map; +import java.util.function.Function; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +class PathNameEmojiFunction implements Function { + + private final Map mapping; + + public PathNameEmojiFunction(Map mapping) { + this.mapping = MappingUtils.toLowerCaseKeys(mapping); + } + + @Override + @Nullable + public String apply(Path path) { + if (path.getFileName() == null) { // if root + return null; + } + return mapping.get(path.getFileName().toString().toLowerCase()); + } + +} diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/SequentialEmojiFunction.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/SequentialEmojiFunction.java new file mode 100644 index 0000000..f63cfdd --- /dev/null +++ b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/emoji/SequentialEmojiFunction.java @@ -0,0 +1,32 @@ +package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji; + +import java.nio.file.Path; +import java.util.Objects; +import java.util.function.Function; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +class SequentialEmojiFunction implements Function { + + private final Iterable> functions; + + public SequentialEmojiFunction(Iterable> functions) { + super(); + this.functions = Objects.requireNonNull(functions, "functions iterable is null"); + } + + @Override + @Nullable + public String apply(Path path) { + String emoji = null; + for (var fn : functions) { + emoji = fn.apply(path); + if (emoji != null) { + break; + } + } + return emoji; + } + +} diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/DefaultEmojiMapping.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/DefaultEmojiMapping.java deleted file mode 100644 index 052363b..0000000 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/DefaultEmojiMapping.java +++ /dev/null @@ -1,54 +0,0 @@ -package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.file; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -@NullMarked -class DefaultEmojiMapping implements EmojiMapping { - - private final String directoryEmoji; - private final String defaultFileEmoji; - private final Map exactFileNamesEmojis; - private final Map fileExtensionsEmojis; - - public DefaultEmojiMapping( - String directoryEmoji, String defaultFileEmoji, Map exactFileNamesEmojis, Map fileExtensionsEmojis - ) { - super(); - this.directoryEmoji = Objects.requireNonNull(directoryEmoji, "directoryEmoji is null"); - this.defaultFileEmoji = Objects.requireNonNull(defaultFileEmoji, "defaultFileEmoji is null"); - this.exactFileNamesEmojis = Objects.requireNonNull(exactFileNamesEmojis, "exactFileNamesEmojis is null"); - this.fileExtensionsEmojis = Objects.requireNonNull(fileExtensionsEmojis, "fileExtensionsEmojis is null"); - } - - @Override - public @Nullable String getFileEmoji(Path path) { - if (path.toFile().isDirectory()) { - return directoryEmoji; - } - var fileName = path.getFileName().toString().toLowerCase(); - var emoji = exactFileNamesEmojis.get(fileName); - if (emoji == null) { - String extension = extractExtension(fileName); - emoji = extension == null ? null : fileExtensionsEmojis.get(extension); - } - if (emoji == null) { - emoji = defaultFileEmoji; - } - return emoji; - } - - @Nullable - private final String extractExtension(String fileName) { - var dotIndex = fileName.lastIndexOf('.'); - return dotIndex < 0 ? null : fileName.substring(dotIndex + 1); - } - - public static DefaultEmojiMappingBuilder builder() { - return new DefaultEmojiMappingBuilder(); - } - -} diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/DefaultEmojiMappingBuilder.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/DefaultEmojiMappingBuilder.java deleted file mode 100644 index 8e74a10..0000000 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/DefaultEmojiMappingBuilder.java +++ /dev/null @@ -1,176 +0,0 @@ -package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.file; - -import java.util.HashMap; -import java.util.Map; -import org.jspecify.annotations.NullMarked; - -@NullMarked -class DefaultEmojiMappingBuilder { - - private String directoryEmoji = "πŸ“‚"; - private String defaultFileEmoji = "πŸ“„"; - private Map exactFileNamesEmojis = buildDefaultExactFileNamesEmojis(); - private Map fileExtensionsEmojis = buildDefaultFileExtensionsEmojis(); - - EmojiMapping build() { - return new DefaultEmojiMapping(directoryEmoji, defaultFileEmoji, exactFileNamesEmojis, fileExtensionsEmojis); - } - - private Map buildDefaultExactFileNamesEmojis() { - Map map = new HashMap<>(); - - // ---------- Applications ---------- - - // ---------- Archives ---------- - - // ---------- Code ---------- - - // Code - default - map.put(".gitignore", "πŸ™ˆ"); - map.put("dockerfile", "🐳"); - map.put("jenkinsfile", "🀡"); - - // Code - github - map.put("readme", "πŸ“–"); - map.put("readme.md", "πŸ“–"); - map.put("roadmap", "πŸ—ΊοΈ"); - map.put("roadmap.md", "πŸ—ΊοΈ"); - map.put("licence", "πŸ—ΊοΈ"); - map.put("licence.md", "πŸ—ΊοΈ"); - map.put("changelog", "πŸ—ΊοΈ"); - map.put("changelog.md", "πŸ—ΊοΈ"); - map.put("security", "πŸ›‘οΈ"); - map.put("security.md", "πŸ›‘οΈ"); - - // Code - build tools - map.put("pom.xml", "πŸ—οΈ"); - map.put("build.gradle", "πŸ—οΈ"); - map.put("package.json", "πŸ—οΈ"); - - // ---------- Data ---------- - - // ---------- Doc ---------- - - // ---------- Media ---------- - - return map; - } - - private Map buildDefaultFileExtensionsEmojis() { - Map map = new HashMap<>(); - - // ---------- Applications ---------- - - // Applications - executables - map.put("exe", "βš™οΈ"); - map.put("msi", "πŸ“¦"); - map.put("apk", "πŸ“±"); - map.put("ipa", "πŸ“±"); - map.put("app", "πŸ–₯️"); - - // Applications - libs - map.put("dll", "🧩"); - map.put("lib", "🧩"); - map.put("so", "🧩"); - - // ---------- Archives ---------- - map.put("7z", "πŸ“¦"); - map.put("gz", "πŸ“¦"); - map.put("img", "πŸ’Ώ"); - map.put("iso", "πŸ’Ώ"); - map.put("tar", "πŸ“¦"); - map.put("rar", "πŸ“¦"); - map.put("zip", "πŸ“¦"); - - // ---------- Code ---------- - // Code - build tools - - // Code - lang - map.put("java", "β˜•"); - map.put("class", "β˜•"); - map.put("jar", "πŸ“¦"); - map.put("py", "🐍"); - map.put("js", "🟨"); - map.put("ts", "πŸ”·"); - map.put("c", "πŸ’ "); - map.put("cpp", "πŸ’ "); - map.put("cs", "πŸ’ "); - map.put("css", "🎨"); - map.put("scss", "🎨"); - map.put("less", "🎨"); - map.put("html", "🌐"); - map.put("htm", "🌐"); - map.put("htmx", "🌐"); - map.put("php", "🐘"); - map.put("sql", "πŸ—„οΈ"); - map.put("vue", "🟩"); - - // Code - scripting - map.put("sh", "πŸ“œ"); - map.put("bash", "πŸ“œ"); - map.put("bat", "πŸ“œ"); - - // ---------- Data ---------- - map.put("cfg", "βš™οΈ"); - map.put("conf", "βš™οΈ"); - map.put("csv", "πŸ“Š"); - map.put("ini", "βš™οΈ"); - map.put("properties", "βš™οΈ"); - map.put("json", "πŸ“"); - map.put("ods", "πŸ“Š"); - map.put("xls", "πŸ“Š"); - map.put("xlsx", "πŸ“Š"); - map.put("xml", "πŸ“"); - map.put("yaml", "πŸ“"); - map.put("yml", "πŸ“"); - - // ---------- Doc ---------- - map.put("doc", "πŸ“ƒ"); - map.put("docx", "πŸ“ƒ"); - map.put("epub", "πŸ“š"); - map.put("md", "πŸ“–"); - map.put("odt", "πŸ“ƒ"); - map.put("pdf", "πŸ“•"); - map.put("rtf", "πŸ“ƒ"); - map.put("txt", "πŸ“„"); - - // ---------- Media ---------- - // Media - Audio - map.put("aac", "🎢"); - map.put("flac", "🎢"); - map.put("midi", "🎹"); - map.put("mp3", "🎡"); - map.put("ogg", "🎢"); - map.put("wav", "🎡"); - - // Media - Images - map.put("bmp", "πŸ–ΌοΈ"); - map.put("gif", "🎞️"); - map.put("jpeg", "πŸ–ΌοΈ"); - map.put("jpg", "πŸ–ΌοΈ"); - map.put("png", "πŸ–ΌοΈ"); - map.put("svg", "βœ’οΈ"); - map.put("ico", "πŸ”²"); - - // Media - Video - map.put("avi", "🎬"); - map.put("mkv", "🎬"); - map.put("mov", "🎬"); - map.put("mp4", "🎬"); - map.put("webm", "🎬"); - map.put("wmv", "🎬"); - - // ---------- System ---------- - map.put("bak", "πŸ’Ύ"); - map.put("log", "πŸ“œ"); - map.put("tmp", "πŸ—‘οΈ"); - map.put("key", "πŸ”‘"); - map.put("pem", "πŸ”"); - map.put("crt", "πŸ”"); - map.put("pub", "πŸ”“"); - - return map; - - } - -} \ No newline at end of file diff --git a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/EmojiMapping.java b/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/EmojiMapping.java deleted file mode 100644 index 6d195fe..0000000 --- a/src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/renderer/file/EmojiMapping.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.file; - -import java.nio.file.Path; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -@NullMarked -public interface EmojiMapping { - - /** - * Get the emoji to display for the given file (i.e. the file type icon). - */ - @Nullable - String getFileEmoji(Path file); - - static EmojiMapping createDefault() { - return DefaultEmojiMapping.builder().build(); - } - -} diff --git a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/EmojisTest.java b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/EmojisTest.java index f93b41d..1e54113 100644 --- a/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/EmojisTest.java +++ b/src/test/java/io/github/computerdaddyguy/jfiletreeprettyprinter/EmojisTest.java @@ -2,8 +2,11 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.emoji.EmojiMapping; +import io.github.computerdaddyguy.jfiletreeprettyprinter.util.FileStructureCreator; import io.github.computerdaddyguy.jfiletreeprettyprinter.util.FileStructures; import java.nio.file.Path; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -12,14 +15,13 @@ class EmojisTest { @TempDir private Path root; - private FileTreePrettyPrinter printer = FileTreePrettyPrinter.builder() - .customizeOptions( - options -> options.withEmojis(true) - ) - .build(); - @Test void emptyDir() { + + var printer = FileTreePrettyPrinter.builder() + .customizeOptions(PrettyPrintOptions::withDefaultEmojis) + .build(); + var path = FileStructures.emptyDirectory(root); var result = printer.prettyPrint(path); var expected = "πŸ“‚ targetPath/"; @@ -29,6 +31,10 @@ void emptyDir() { @Test void emojis() { + var printer = FileTreePrettyPrinter.builder() + .customizeOptions(PrettyPrintOptions::withDefaultEmojis) + .build(); + var result = printer.prettyPrint("src/example/resources/emojis"); var expected = """ πŸ“‚ emojis/ @@ -36,6 +42,7 @@ void emojis() { β”‚ β”œβ”€ πŸ“‚ executables/ β”‚ β”‚ β”œβ”€ πŸ“± file.apk β”‚ β”‚ β”œβ”€ πŸ–₯️ file.app + β”‚ β”‚ β”œβ”€ βš™οΈ file.bin β”‚ β”‚ β”œβ”€ βš™οΈ file.exe β”‚ β”‚ β”œβ”€ πŸ“¦ file.msi β”‚ β”‚ └─ πŸ“± file2.ipa @@ -52,24 +59,15 @@ void emojis() { β”‚ β”œβ”€ πŸ“¦ file.tar β”‚ └─ πŸ“¦ file.zip β”œβ”€ πŸ“‚ code/ - β”‚ β”œβ”€ πŸ™ˆ .gitignore + β”‚ β”œβ”€ 🚫 .gitignore β”‚ β”œβ”€ 🐳 Dockerfile β”‚ β”œβ”€ 🀡 Jenkinsfile β”‚ β”œβ”€ πŸ“‚ build_tools/ - β”‚ β”‚ β”œβ”€ πŸ—οΈ build.gradle - β”‚ β”‚ β”œβ”€ πŸ—οΈ package.json - β”‚ β”‚ └─ πŸ—οΈ pom.xml - β”‚ β”œβ”€ πŸ“‚ github/ - β”‚ β”‚ β”œβ”€ πŸ—ΊοΈ changelog - β”‚ β”‚ β”œβ”€ πŸ—ΊοΈ changelog.md - β”‚ β”‚ β”œβ”€ πŸ—ΊοΈ licence - β”‚ β”‚ β”œβ”€ πŸ—ΊοΈ licence.md - β”‚ β”‚ β”œβ”€ πŸ“– readme - β”‚ β”‚ β”œβ”€ πŸ“– readme.md - β”‚ β”‚ β”œβ”€ πŸ—ΊοΈ roadmap - β”‚ β”‚ β”œβ”€ πŸ—ΊοΈ roadmap.md - β”‚ β”‚ β”œβ”€ πŸ›‘οΈ security - β”‚ β”‚ └─ πŸ›‘οΈ security.md + β”‚ β”‚ β”œβ”€ πŸ› οΈ build.gradle + β”‚ β”‚ β”œβ”€ πŸ› οΈ makefile + β”‚ β”‚ β”œβ”€ πŸ› οΈ package.json + β”‚ β”‚ └─ πŸ› οΈ pom.xml + β”‚ β”œβ”€ βš™οΈ docker-compose.yml β”‚ β”œβ”€ πŸ“‚ lang/ β”‚ β”‚ β”œβ”€ πŸ’  file.c β”‚ β”‚ β”œβ”€ β˜• file.class @@ -105,28 +103,43 @@ void emojis() { β”‚ β”œβ”€ πŸ“ file.yaml β”‚ └─ πŸ“ file.yml β”œβ”€ πŸ“‚ doc/ - β”‚ β”œβ”€ πŸ“ƒ file.doc - β”‚ β”œβ”€ πŸ“ƒ file.docx + β”‚ β”œβ”€ πŸ“ file.doc + β”‚ β”œβ”€ πŸ“ file.docx β”‚ β”œβ”€ πŸ“š file.epub - β”‚ β”œβ”€ πŸ“– file.md - β”‚ β”œβ”€ πŸ“ƒ file.odt + β”‚ β”œβ”€ πŸ“ file.md + β”‚ β”œβ”€ πŸ“ file.odt β”‚ β”œβ”€ πŸ“• file.pdf - β”‚ β”œβ”€ πŸ“ƒ file.rtf - β”‚ └─ πŸ“„ file.txt + β”‚ β”œβ”€ πŸ“ file.rtf + β”‚ └─ πŸ“ file.txt β”œβ”€ πŸ“„ file.unknown_extension β”œβ”€ πŸ“„ file_without_extension + β”œβ”€ πŸ“‚ internet/ + β”‚ β”œβ”€ πŸ“‚ github/ + β”‚ β”‚ β”œβ”€ πŸ†• changelog + β”‚ β”‚ β”œβ”€ πŸ†• changelog.md + β”‚ β”‚ β”œβ”€ 🀝 contributing + β”‚ β”‚ β”œβ”€ 🀝 contributing.md + β”‚ β”‚ β”œβ”€ βš–οΈ license + β”‚ β”‚ β”œβ”€ βš–οΈ license.md + β”‚ β”‚ β”œβ”€ πŸ“˜ readme + β”‚ β”‚ β”œβ”€ πŸ“˜ readme.md + β”‚ β”‚ β”œβ”€ πŸ—ΊοΈ roadmap + β”‚ β”‚ β”œβ”€ πŸ—ΊοΈ roadmap.md + β”‚ β”‚ β”œβ”€ πŸ›‘οΈ security + β”‚ β”‚ └─ πŸ›‘οΈ security.md + β”‚ └─ πŸ€– robots.txt β”œβ”€ πŸ“‚ media/ β”‚ β”œβ”€ πŸ“‚ audio/ - β”‚ β”‚ β”œβ”€ 🎢 file.aac - β”‚ β”‚ β”œβ”€ 🎢 file.flac + β”‚ β”‚ β”œβ”€ 🎡 file.aac + β”‚ β”‚ β”œβ”€ 🎡 file.flac β”‚ β”‚ β”œβ”€ 🎹 file.midi β”‚ β”‚ β”œβ”€ 🎡 file.mp3 - β”‚ β”‚ β”œβ”€ 🎢 file.ogg + β”‚ β”‚ β”œβ”€ 🎡 file.ogg β”‚ β”‚ └─ 🎡 file.wav β”‚ β”œβ”€ πŸ“‚ images/ β”‚ β”‚ β”œβ”€ πŸ–ΌοΈ file.bmp β”‚ β”‚ β”œβ”€ 🎞️ file.gif - β”‚ β”‚ β”œβ”€ πŸ”² file.ico + β”‚ β”‚ β”œβ”€ πŸ–ΌοΈ file.ico β”‚ β”‚ β”œβ”€ πŸ–ΌοΈ file.jpeg β”‚ β”‚ β”œβ”€ πŸ–ΌοΈ file.jpg β”‚ β”‚ β”œβ”€ πŸ–ΌοΈ file.png @@ -150,4 +163,189 @@ void emojis() { assertThat(result).isEqualTo(expected); } + @Nested + class DirectoryEmojiMapping { + + @Test + void dir_name() { + + // @formatter:off + var path = FileStructureCreator + .forTargetPath(root) + .createDirectory("dirA") + .createDirectory("dirB") + .createDirectory("dirC") + .getPath(); + // @formatter:on + + var mapping = EmojiMapping.builderFromDefault() + .setDirectoryNameEmoji("dirA", "⭐") // add emoji + .build(); + + var printer = FileTreePrettyPrinter.builder() + .customizeOptions( + options -> options.withEmojis(mapping) + ) + .build(); + + var result = printer.prettyPrint(path); + + var expected = """ + πŸ“‚ targetPath/ + β”œβ”€ ⭐ dirA/ + β”œβ”€ πŸ“‚ dirB/ + └─ πŸ“‚ dirC/"""; + + assertThat(result).isEqualTo(expected); + } + + @Test + void dir_match() { + + // @formatter:off + var path = FileStructureCreator + .forTargetPath(root) + .createDirectory("dirA") + .createDirectory("dirB") + .createDirectory("dirC") + .getPath(); + // @formatter:on + + var mapping = EmojiMapping.builderFromDefault() + .addDirectoryEmoji(PathMatchers.hasName("dirA"), "⭐") // add emoji + .addDirectoryEmoji(PathMatchers.hasName("dirB"), "😊") // change existing emoji + .build(); + + var printer = FileTreePrettyPrinter.builder() + .customizeOptions( + options -> options.withEmojis(mapping) + ) + .build(); + + var result = printer.prettyPrint(path); + + var expected = """ + πŸ“‚ targetPath/ + β”œβ”€ ⭐ dirA/ + β”œβ”€ 😊 dirB/ + └─ πŸ“‚ dirC/"""; + + assertThat(result).isEqualTo(expected); + } + + } + + @Nested + class FileEmojiMapping { + + @Test + void file_name() { + + // @formatter:off + var path = FileStructureCreator + .forTargetPath(root) + .createFile("aaa") + .createFile("dockerfile") + .createFile("jenkinsfile") + .createFile("license") + .getPath(); + // @formatter:on + + var mapping = EmojiMapping.builderFromDefault() + .setFileNameEmoji("aaa", "⭐") // add emoji + .setFileNameEmoji("dockerfile", "😊") // change existing emoji + .build(); + + var printer = FileTreePrettyPrinter.builder() + .customizeOptions( + options -> options.withEmojis(mapping) + ) + .build(); + + var result = printer.prettyPrint(path); + + var expected = """ + πŸ“‚ targetPath/ + β”œβ”€ ⭐ aaa + β”œβ”€ 😊 dockerfile + β”œβ”€ 🀡 jenkinsfile + └─ βš–οΈ license"""; + + assertThat(result).isEqualTo(expected); + } + + @Test + void file_extension() { + + // @formatter:off + var path = FileStructureCreator + .forTargetPath(root) + .createFile("file.plop") + .createFile("file.avi") + .createFile("file.gif") + .createFile("license") + .getPath(); + // @formatter:on + + var mapping = EmojiMapping.builderFromDefault() + .setFileExtensionEmoji("plop", "⭐") // add emoji + .setFileExtensionEmoji("avi", "😊") // change existing emoji + .build(); + + var printer = FileTreePrettyPrinter.builder() + .customizeOptions( + options -> options.withEmojis(mapping) + ) + .build(); + + var result = printer.prettyPrint(path); + + var expected = """ + πŸ“‚ targetPath/ + β”œβ”€ 😊 file.avi + β”œβ”€ 🎞️ file.gif + β”œβ”€ ⭐ file.plop + └─ βš–οΈ license"""; + + assertThat(result).isEqualTo(expected); + } + + @Test + void file_match() { + + // @formatter:off + var path = FileStructureCreator + .forTargetPath(root) + .createFile("file.plop") + .createFile("file.avi") + .createFile("file.gif") + .createFile("license") + .getPath(); + // @formatter:on + + var mapping = EmojiMapping.builderFromDefault() + .addFileEmoji(PathMatchers.hasName("file.plop"), "⭐") // add emoji + .addFileEmoji(PathMatchers.hasName("file.avi"), "😊") // change existing emoji + .build(); + + var printer = FileTreePrettyPrinter.builder() + .customizeOptions( + options -> options.withEmojis(mapping) + ) + .build(); + + var result = printer.prettyPrint(path); + + var expected = """ + πŸ“‚ targetPath/ + β”œβ”€ 😊 file.avi + β”œβ”€ 🎞️ file.gif + β”œβ”€ ⭐ file.plop + └─ βš–οΈ license"""; + + assertThat(result).isEqualTo(expected); + } + + } + }