personthecat.catlib.util.PathUtils Maven / Gradle / Ivy
Show all versions of catlib-quilt Show documentation
package personthecat.catlib.util;
import lombok.experimental.UtilityClass;
import net.minecraft.class_2960;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
import static personthecat.catlib.io.FileIO.listFiles;
import static personthecat.catlib.util.Shorthand.f;
/** A collection of tools used for interacting with file paths and {@link class_2960}s. */
@UtilityClass
@SuppressWarnings("unused")
public class PathUtils {
/**
* Converts the input path into a fully-qualified ResourceLocation.
*
* @param path The raw file URI being decoded.
* @return The corresponding {@link class_2960 ID} of this path.
*/
public static class_2960 getResourceLocation(final String path) {
final String namespace = getNamespaceFromPath(path);
final String result = path
.replaceAll("(assets|data)[/\\\\]", "")
.replaceAll(namespace + "[/\\\\]", "")
.replaceAll("^[/\\\\]", "");
return new class_2960(namespace, result);
}
/**
* Looks for the first path component after one of the root resource folders.
*
* @param path The raw file URI being decoded.
* @return The namespace of the mod this refers to, if possible, else itself.
*/
private static String getNamespaceFromPath(final String path) {
boolean rootFound = false;
for (String s : path.split("[/\\\\]")) {
if (rootFound && !s.isEmpty()) {
return s;
}
rootFound |= "assets".equals(s) || "data".equals(s);
}
return path;
}
/**
* Reuses a foreign path as a sub path. This function will specifically
* place the namespace after block/
or item/
*
* For example, the resource ID minecraft:block/id
will be
* converted into block/minecraft/id
*
*
* Note: this is unable to output backslashes instead of forward slashes.
*
*
* @param id The resource location being transformed.
* @return The transformed path.
*/
public static String namespaceToSub(final class_2960 id) {
return id.method_12832().replaceFirst("(blocks?|items?|^)[/\\\\]?", "$1/" + id.method_12836() + "/");
}
/**
* Shorthand for {@link #namespaceToSub(class_2960)} using a raw path.
*
* @param id The raw path being transformed.
* @return The transformed path.
*/
public static String namespaceToSub(final String id) {
return namespaceToSub(fromRawPath(id));
}
/**
* Removes all parent directories from a resource location as a string.
*
* For example, name:pathA/pathB
will be transformed into
* pathB
.
*
* @param path The path being parsed.
* @return The filename or key at the end of the path.
*/
public static String filename(final String path) {
final String[] split = path.split("[/\\\\]");
return split[split.length - 1];
}
/**
* Variant of {@link #filename(String)} which accepts a {@link class_2960}.
*
* @param id The path being parsed.
* @return The filename or key at the end of the path.
*/
public static String endOfPath(final class_2960 id) {
return filename(id.method_12832());
}
/**
* Determines whether the given file path has an extension.
*
* @param path The path to a file, complete or incomplete.
* @return Whether an extension was queried.
*/
public static boolean hasExtension(final String path) {
return path.endsWith(".") || !extension(new File(path)).isEmpty();
}
/**
* Determines the extension of the input file.
*
* @param file The file whose name is being formatted.
* @return The extension of the given file.
*/
public static String extension(final File file) {
return extension(file.getName());
}
/**
* Variant of {@link #extension(File)} which accepts a string.
*
* @param s The name of the file or path being operated on.
* @return The extension of the given file or path.
*/
public static String extension(final String s) {
final int index = s.lastIndexOf(".");
return index < 1 ? "" : s.substring(index + 1);
}
/**
* Gets the name of the file, minus the extension.
*
* @param file The file being operated on.
* @return The regular filename.
*/
public static String noExtension(final File file) {
return noExtension(file.getName());
}
/**
* Variant of {@link #noExtension(File)} which accepts a string.
*
* @param s The name of the file or path being operated on.
* @return The regular filename.
*/
public static String noExtension(final String s) {
final int index = s.lastIndexOf(".");
return index < 0 ? s : s.substring(0, index);
}
/**
* Determines whether the given file is in a specific folder.
*
* @param root The root file which is the expected parent directory.
* @param f The file being tested.
* @return Whether root
contains f
.
*/
public static boolean isIn(final File root, final File f) {
return f.getAbsolutePath().startsWith(root.getAbsolutePath());
}
/**
* Gets the relative path of the given file up to a root folder.
*
*
* For example, when given the following root folder:
*
* /home/user/.minecraft/config
*
* And the following file:
*
* /home/user/.minecraft/config/backups/demo.txt
*
* The following path will be output:
*
* backups/demo.txt
*
*
* @param root The root directory for the output path.
* @param f The file which the path is generated from.
* @return A relative path from root
to f
.
*/
public static String getRelativePath(final File root, final File f) {
if (root.equals(f)) {
return "/";
}
final String rootPath = root.getAbsolutePath();
final String filePath = f.getAbsolutePath();
if (rootPath.length() + 1 > filePath.length()) {
return filePath;
}
return filePath.substring(rootPath.length() + 1);
}
/**
* Appends a string of text before the extension on the given path. If this
* path contains no extension, the strings are simply concatenated.
*
* For example, appending _bar
onto foo.baz
will
* output foo_bar.baz
*
*
* @param path the fully-qualified file path.
* @param affix The string to append onto the filename.
* @return A new file path where the filename ends with the given affix.
*/
public static String appendFilename(final String path, final String affix) {
final String extension = PathUtils.extension(path);
if (extension.isEmpty()) {
return path + affix;
}
final int index = path.lastIndexOf(extension);
return path.substring(0, index - 1) + affix + "." + extension;
}
/**
* Prepends a new string of text before the last part of a file path.
*
* For example, prepending bar_
onto foo/baz
* will output foo/bar_baz
*
*
* Note: empty strings would probably produce invalid results.
*
* @param path The fully-qualified file path.
* @param prefix The string to prepend to the filename.
* @return A new file path where the filename begins with the given prefix.
*/
public static String prependFilename(final String path, final String prefix) {
return path.replaceFirst("^(.*[/\\\\])*([^/\\\\]+)$", "$1" + prefix + "$2");
}
/**
* Returns a stream of the relative file paths in the given directory.
* By default, this method excludes the extensions from the output files.
*
* @param root The root directory of the output paths.
* @param current The current file or directory being examined.
* @return A stream of the relative file paths contained at this location.
*/
public static Stream getSimpleContents(final File root, final File current) {
return getContents(root, current, true);
}
/**
* Variant of {@link #getSimpleContents(File, File)} in which the current
* directory is also the root directory.
*
* @param current The current directory being examined.
* @return The relative file paths in this directory.
*/
public static Stream getSimpleContents(final File current) {
return getContents(current, current, true);
}
/**
* Variant of {@link #getContents(File, File, boolean)} in which the current
* directory is also the root directory.
*
* @param current The current directory being examined.
* @param simple Whether to exclude extensions.
* @return The relative file paths in this directory.
*/
public static Stream getContents(final File current, boolean simple) {
return getContents(current, current, simple);
}
/**
* Returns a stream of the relative file paths in the given directory.
*
* @param root The root directory of the output paths.
* @param current The current file or directory being examined.
* @param simple Whether to exclude extensions.
* @return A stream of the relative file paths contained at this location.
*/
public static Stream getContents(final File root, final File current, boolean simple) {
final File dir = current.isDirectory() ? current : current.getParentFile();
if (simple) {
final Set contents = new HashSet<>();
for (final File file : listFiles(dir)) {
final String format = formatContents(root, file);
if (!contents.add(noExtension(format))) {
contents.add(format);
}
}
return contents.stream();
}
return Stream.of(listFiles(dir)).map(f -> formatContents(root, f));
}
/**
* Formats the given path to return only a relative path using forward slashes
* instead of backward slashes.
*
* @param root The root directory.
* @param f The file which the relative path is generated from.
* @return The formatted path.
*/
private static String formatContents(final File root, final File f) {
return f.getAbsolutePath()
.substring(root.getAbsolutePath().length())
.replace("\\", "/")
.substring(1);
}
/**
* Converts a {@link class_2960} to a raw PNG texture path.
*
* @param id The texture's identifier.
* @return The raw path pointing to the image.
*/
public static String asTexturePath(final class_2960 id) {
return asTexturePath(id.method_12836(), id.method_12832());
}
/**
* Generates a raw PNG texture path when given a mod's ID and relative
* file path.
*
* @param mod The mod's ID.
* @param path The relative path to the image.
* @return The raw path pointing to the image.
*/
public static String asTexturePath(final String mod, final String path) {
return f("/assets/{}/textures/{}.png", mod, path);
}
/**
* Converts a {@link class_2960} to a raw JSON model path.
*
* @param id The model's identifier.
* @return The raw path pointing to the model.
*/
public static String asModelPath(final class_2960 id) {
return asModelPath(id.method_12836(), id.method_12832());
}
/**
* Generates a raw JSON model path when given a mod's ID and relative
* file path.
*
* @param mod The mod's ID.
* @param path The relative path to the model.
* @return The raw path pointing to the model.
*/
public static String asModelPath(final String mod, final String path) {
return f("/assets/{}/models/{}.json", mod, path);
}
/**
* Converts a {@link class_2960} to a raw block state model path.
*
* @param id The model's identifier.
* @return The raw path pointing to the model.
*/
public static String asBlockStatePath(final class_2960 id) {
return asBlockStatePath(id.method_12836(), id.method_12832());
}
/**
* Generates a raw JSON block state path when given a mod's ID and
* relative file path.
*
* @param mod The mod's ID.
* @param path The relative path to the model.
* @return The raw path pointing to the model.
*/
public static String asBlockStatePath(final String mod, final String path) {
return f("/assets/{}/blockstates/{}.json", mod, path);
}
/**
* Converts a raw file path (starting with assets
or
* data
) into a {@link class_2960}. The type of
* file will be accounted for and elided from the return value.
*
* These examples demonstrate the expected output:
*
*
* -
* In:
assets/minecraft/textures/block/cobblestone.png
* Out: minecraft:block/cobblestone
*
* -
* In:
assets/osv/models/block/overlay.json
* Out: osv:block/overlay
*
*
*
* In the event where no content root is provided in the path,
* the input is assumed to already be an identifier and the following
* behavior can be expected:
*
*
* -
* In:
block/cobblestone
* Out: minecraft:block/cobblestone
*
*
*
* @param path The raw path pointing to a resource or {@link class_2960}
* as a string.
* @return The equivalent {@link class_2960} for this path.
*/
public static class_2960 fromRawPath(final String path) {
final int content = contentRoot(path);
if (content < 0) return new class_2960(path);
final String fromMod = path.substring(content + 1);
final int mod = fromMod.indexOf("/");
if (mod < 0) return new class_2960(path);
final String modName = fromMod.substring(0, mod);
final String fullPath = fromMod.substring(mod + 1);
final int key = fullPath.indexOf("/");
if (key < 0) return new class_2960(modName, fullPath + 1);
return new class_2960(modName, noExtension(fullPath.substring(key + 1)));
}
/**
* Determines the type of content being pointed at by the given path.
* If no content type can be determined from this path, null
* will be returned instead.
*
* These examples should demonstrate the expected behavior:
*
*
* -
* In:
assets/minecraft/textures/block/oak_door.png
* Out: textures
* -
* In:
assets/minecraft/models/block/oak_door.json
* Out: models
*
*
*
* @param path The raw file path for any resource.
* @return The type of content, e.g. textures
, or else null
.
*/
@Nullable
public static String contentType(final String path) {
final int content = contentRoot(path);
if (content < 0) {
// Assume this path starts just after the mod.
final int afterMod = path.indexOf("/");
if (afterMod < 0) return null; // Only one path element.
return path.substring(0, afterMod + 1);
}
final String fromMod = path.substring(content + 1);
final int mod = fromMod.indexOf("/");
if (mod < 0) return null; // Would be "assets/mod"
final String fullPath = fromMod.substring(mod + 1);
final int fromType = fullPath.indexOf("/");
if (fromType < 0) return null; // Would be "assets/mod/file"
return fullPath.substring(0, fromType);
}
private static int contentRoot(final String path) {
final int assets = path.indexOf("assets/");
if (assets > 0) {
return assets + "assets".length();
}
final int data = path.indexOf("data/");
if (data > 0) {
return data + "data".length();
}
return -1;
}
}