All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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