commonMain.Path.kt Maven / Gradle / Ivy
@file:JvmName("Paths")
package pt.lightweightform.lfkotlin
import kotlin.jvm.JvmName
/** Representation of a path. */
public typealias Path = String
/**
* String used as a placeholder when the actual identifier of a certain path is unknown or
* irrelevant.
*/
public const val PATH_ID_PLACEHOLDER: String = "?"
/** Maximum number of elements in JDK 6 and above. */
public const val ARRAY_MAX_SIZE: Int = Int.MAX_VALUE - 2
// Normalises [pathList] by resolving ".", "..", and ""
private fun normalizeList(pathList: List): List {
val resolved = mutableListOf()
for (part in pathList) {
if (part == "..") {
if (resolved.size > 0) {
resolved.removeLast()
}
} else if (part != "" && part != ".") {
resolved += part
}
}
return resolved
}
/** Whether [path] represents an absolute path. */
public fun isAbsolutePath(path: Path): Boolean = path.isNotEmpty() && path[0] == '/'
/** Returns a path consisting of the given [path] with [id] appended to it. */
public fun appendToPath(path: Path, id: String): Path =
if (path.endsWith("/")) "$path$id" else "$path/$id"
/** Resolves [path] against [currentPath] and returns a normalised path in list form. */
public fun resolvePathToList(currentPath: Path, path: Path): List {
if (!isAbsolutePath(currentPath)) {
throw IllegalArgumentException("First argument must be an absolute path")
}
val fullPath = if (isAbsolutePath(path)) path else "$currentPath/$path"
return normalizeList(fullPath.split('/'))
}
/** Resolves [path] against [path] and returns the result. */
public fun resolvePath(currentPath: Path, path: Path): Path =
listToPath(resolvePathToList(currentPath, path))
/** Returns an absolute path given a path in list form. */
public fun listToPath(pathList: List): Path = pathList.joinToString("/", prefix = "/")
/** Whether a given index found in a path (e.g. for accessing an array element) is valid. */
public fun isValidPathIndex(id: String, acceptPlaceholder: Boolean = false): Boolean =
(acceptPlaceholder && id == PATH_ID_PLACEHOLDER) ||
id.toIntOrNull().let { it != null && it >= 0 && it <= ARRAY_MAX_SIZE }
/**
* Whether a given identifier can be used as part of a path. No identifiers may contain a forward
* slash (since it conflicts with the path separator).
*/
public fun isValidPathId(id: String): Boolean = id != "." && id != ".." && id.indexOf('/') == -1
/** Whether [path2] is a subpath of [path1]. */
public fun isSubpath(path1: Path, path2: Path): Boolean =
path1.length < path2.length && (path1 == "/" || path2.startsWith("$path1/"))
/** Whether two paths match (if they are equal when placeholders are ignored). */
public fun pathsMatch(path1: Path, path2: Path): Boolean {
val path1List = resolvePathToList("/", path1)
val path2List = resolvePathToList("/", path2)
return path1List.size == path2List.size &&
path1List.withIndex().all { (i, part) ->
part == PATH_ID_PLACEHOLDER ||
path2List[i] == PATH_ID_PLACEHOLDER ||
part == path2List[i]
}
}
/**
* Whether [path2] is a matching subpath of [path1] (matching in the sense that placeholders are
* ignored).
*/
public fun isMatchingSubpath(path1: Path, path2: Path): Boolean {
val path1List = resolvePathToList("/", path1)
val path2List = resolvePathToList("/", path2)
return path1List.size < path2List.size &&
path1List.withIndex().all { (i, part) ->
part == PATH_ID_PLACEHOLDER ||
path2List[i] == PATH_ID_PLACEHOLDER ||
part == path2List[i]
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy