Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
@file:JvmName("Paths")
package io.kform
import kotlin.js.JsName
import kotlin.jvm.JvmField
import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic
import kotlin.math.min
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Typealias used to represent a type where either a [Path] or a [String] (representing a path) are
* accepted.
*/
public typealias PathOrString = Any
/**
* Representation of a path as a list of path [fragments].
*
* Paths represent locations of data, possibly relatively to other locations: e.g. the location of a
* value relative to the location of another value. They can also represent locations of data from
* the root when they contain a root fragment.
*/
@JsName("PathKt")
@Serializable(with = Path.Serializer::class)
public open class Path(fragments: List = emptyList()) {
/**
* Internal list of "real" fragments. For the generic [Path], this list is the same as the
* public [fragments] one, however, [AbsolutePath] hides their internal representation (it hides
* its first "root fragment") and the two lists differ.
*/
internal val realFragments: List = ArrayList(fragments)
/** List of fragments representing this path. */
public open val fragments: List
get() = realFragments
/** Creates a path from its string notation. E.g. `"./path/to/somewhere"`. */
public constructor(stringPath: String) : this(parse(stringPath))
/**
* Returns the fragment of this path with index [index].
*
* @throws IndexOutOfBoundsException When an out of bounds [index] is provided.
*/
public open fun fragment(index: Int): PathFragment = realFragments[index]
/**
* Returns the fragment of this path with index [index].
*
* @throws IndexOutOfBoundsException When an out of bounds [index] is provided.
*/
public open operator fun get(index: Int): PathFragment = fragment(index)
/**
* Returns a new path representing the parent of this path.
*
* Note that the parent of the root path is the root path itself.
*/
public open fun parent(): Path = Path(parentImpl(realFragments))
/** Returns the path resulting from appending [fragments] to this path. */
public fun append(vararg fragments: PathFragment): Path = Path(realFragments + fragments)
/**
* Returns the path resulting from appending [fragment] to this path. Equivalent to
* `append(fragment)`.
*/
public operator fun plus(fragment: PathFragment): Path = append(fragment)
/** Returns the path resulting from joining [paths] together with this path. */
public fun join(vararg paths: Path): Path =
Path(joinImpl(realFragments, *paths.map { path -> path.realFragments }.toTypedArray()))
/**
* Returns the path resulting from joining the provided paths in string notation [stringPaths]
* together with this path.
*/
public fun join(vararg stringPaths: String): Path =
join(*stringPaths.map { str -> Path(str) }.toTypedArray())
/**
* Returns the path resulting from joining [path] together with this path. Equivalent to
* `join(path)`.
*/
public open operator fun plus(path: Path): Path = join(path)
/**
* Returns the path resulting from joining the path in string notation [stringPath] together
* with this path. Equivalent to `join(stringPath)`.
*/
public open operator fun plus(stringPath: String): Path = join(stringPath)
/**
* Returns the result of resolving this path. Resolving the path removes unnecessary fragments
* such as the [current path fragment][PathFragment.CurrentPath].
*/
public fun resolve(): Path = Path(resolveImpl(realFragments))
/** Returns the path resulting from resolving a list of [paths] against this path. */
public open fun resolve(vararg paths: Path): Path =
Path(resolveImpl(realFragments, *paths.map { path -> path.realFragments }.toTypedArray()))
/**
* Returns the path resulting from resolving a list of paths in string notation [stringPaths]
* against this path.
*/
public open fun resolve(vararg stringPaths: String): Path =
resolve(*stringPaths.map { str -> Path(str) }.toTypedArray())
/**
* Returns whether this path equals [other].
*
* Two paths are equal when they both resolve to paths with the exact same fragments.
*/
override fun equals(other: Any?): Boolean =
when {
this === other -> true
other !is Path -> false
else -> resolve().realFragments == other.resolve().realFragments
}
/** Returns the hash code for this path. */
override fun hashCode(): Int = resolve().realFragments.hashCode()
/** Returns the path in string notation. */
override fun toString(): String = buildString {
for ((i, fragment) in realFragments.withIndex()) {
if (fragment is PathFragment.Root) {
clear()
append(SEPARATOR_CHARACTER)
} else {
append(fragmentToString(fragment))
// Add a trailing slash only when the last fragment is an empty id
if (
i < realFragments.size - 1 ||
(fragment is AbsolutePathFragment.Id && fragment.id == "")
) {
append(SEPARATOR_CHARACTER)
}
}
}
}
/** Returns an iterator over the path's fragments. */
public open operator fun iterator(): Iterator = realFragments.iterator()
public companion object {
// Common paths:
/** Path representing the current path (`"."`). */
@JvmField public val CURRENT: Path = Path()
/** Path representing the current path and all of its descendants (`"./∗∗"`). */
@JvmField
public val CURRENT_DEEP: Path = Path(listOf(AbsolutePathFragment.RecursiveWildcard))
/** Path representing the parent path (`".."`). */
@JvmField public val PARENT: Path = Path(listOf(PathFragment.ParentPath))
/** Path representing the children of a path (`"./∗"`). */
@JvmField public val CHILDREN: Path = Path(listOf(AbsolutePathFragment.Wildcard))
/** Path representing the descendants of a path (`"./∗/∗∗"`). */
@JvmField
public val DESCENDANTS: Path =
Path(listOf(AbsolutePathFragment.Wildcard, AbsolutePathFragment.RecursiveWildcard))
// Strings/characters to parse paths from strings:
/**
* Special fragment string representing the end of a collection. Use `"~-"` within a string
* representation of a path to represent the fragment with id `"-"` literally.
*/
public const val COLLECTION_END_STRING: String = "-"
/**
* Special fragment string representing "any" fragment. Use `"~*"` within a string
* representation of a path to represent the fragment with id `"*"` literally.
*/
public const val WILDCARD_STRING: String = "*"
/**
* Special fragment string representing zero or more fragments. Use `"~**"` within a string
* representation of a path to represent the fragment with id `"**"` literally.
*/
public const val RECURSIVE_WILDCARD_STRING: String = "**"
/**
* Special fragment string representing the "current" path. Use `"~."` within a string
* representation of a path to represent the fragment with id `"."` literally.
*/
public const val CURRENT_PATH_STRING: String = "."
/**
* Special fragment string representing the "parent" path. Use `"~.."` within a string
* representation of a path to represent the fragment with id `".."` literally.
*/
public const val PARENT_PATH_STRING: String = ".."
/**
* Character used as part of an escape sequence in the string representation of paths. It
* may be used to escape the special fragments `"."`, `".."`, `"*"`, and `"**"` with `"~."`,
* `"~.."`, `"~*"`, and `"~**"` respectively, as well as escape characters such as `"/"`,
* `"~"`, `"{"`, `"="`, `";"`, and `"}"`.
*/
public const val ESCAPE_CHARACTER: Char = '~'
/**
* Character used to represent the root path in string form, as well as separate fragments,
* e.g. the path with id fragments `x` and `y` is represented in string form as `"/x/y"`
* whilst the one with no fragments (root) is represented as `"/"`.
*/
public const val SEPARATOR_CHARACTER: Char = '/'
// Regular expressions used to escape fragments when converting a path to
// its string form
private val FRAGMENT_ESCAPE_REGEX = Regex("([~/])")
/**
* Returns [fragment] in string notation (e.g. to be used in the string representation of a
* path). Id fragments may end up escaped when they would otherwise represent a different
* fragment.
*/
@JvmStatic
public fun fragmentToString(fragment: PathFragment): String {
return when (fragment) {
is AbsolutePathFragment.Id -> {
val id = fragment.id
// Escape fragments that would have special meanings
if (
id == COLLECTION_END_STRING ||
id == WILDCARD_STRING ||
id == RECURSIVE_WILDCARD_STRING ||
id == CURRENT_PATH_STRING ||
id == PARENT_PATH_STRING
) {
return ESCAPE_CHARACTER + id
}
// Escape the escape character and the path separator
return id.replace(FRAGMENT_ESCAPE_REGEX, "$ESCAPE_CHARACTER$1")
}
is AbsolutePathFragment.CollectionEnd -> COLLECTION_END_STRING
is AbsolutePathFragment.Wildcard -> WILDCARD_STRING
is AbsolutePathFragment.RecursiveWildcard -> RECURSIVE_WILDCARD_STRING
is PathFragment.Root -> SEPARATOR_CHARACTER.toString()
is PathFragment.CurrentPath -> CURRENT_PATH_STRING
is PathFragment.ParentPath -> PARENT_PATH_STRING
}
}
/**
* Returns the list of path fragments represented by a path in string notation [stringPath].
*/
private fun parse(stringPath: String): List {
val fragmentList: MutableList = mutableListOf()
var fragmentId = ""
var i = 0
val l = stringPath.length
mainLoop@ while (i < l) {
val c = stringPath[i]
// Unescape
if (c == ESCAPE_CHARACTER) {
fragmentId += stringPath.getOrNull(++i)?.toString() ?: ""
++i
continue
}
// Handle path separator
if (c == SEPARATOR_CHARACTER) {
// Represent an absolute path by setting the first fragment as the
// root
if (i == 0) {
fragmentList += PathFragment.Root
} else {
fragmentList += AbsolutePathFragment.Id(fragmentId)
fragmentId = ""
}
++i
continue
}
if (fragmentId == "") {
// Handle special fragments
for (spFragment in
listOf(
PathFragment.CurrentPath,
PathFragment.ParentPath,
AbsolutePathFragment.CollectionEnd,
AbsolutePathFragment.Wildcard,
AbsolutePathFragment.RecursiveWildcard
)) {
val spStr = fragmentToString(spFragment)
val spLen = spStr.length
if (
stringPath.substring(i, min(i + spLen, l)) == spStr &&
(i + spLen == l ||
stringPath.getOrNull(i + spLen) == SEPARATOR_CHARACTER)
) {
fragmentList += spFragment
i += spLen + 1
continue@mainLoop
}
}
}
fragmentId += c
++i
}
// Ignore trailing slash
if (fragmentId != "") {
fragmentList += AbsolutePathFragment.Id(fragmentId)
}
return fragmentList
}
/**
* Implementation of [parent]. Returns the parent of the path represented by the provided
* path [fragments].
*/
internal fun parentImpl(fragments: List): List =
when {
fragments.isEmpty() || fragments.last() == PathFragment.ParentPath ->
fragments + PathFragment.ParentPath
fragments.last() == PathFragment.Root -> fragments
else -> fragments.dropLast(1)
}
/**
* Implementation of [join]. Joins all lists of path fragments within an array
* [fragmentsArray] into a single list of path fragments.
*/
internal fun joinImpl(vararg fragmentsArray: List): List {
val joined = mutableListOf()
for (fragments in fragmentsArray) {
joined += fragments
}
return joined
}
/**
* Implementation of [resolve]. Resolves all provided lists of path fragments within an
* array [fragmentsArray] into a single list of path fragments, removing unnecessary
* fragments.
*/
internal fun resolveImpl(vararg fragmentsArray: List): List {
val resolved = mutableListOf()
for (fragments in fragmentsArray) {
for (fragment in fragments) {
when (fragment) {
is PathFragment.Root -> {
resolved.clear()
resolved += fragment
}
is PathFragment.ParentPath ->
when {
resolved.isEmpty() || resolved.last() == PathFragment.ParentPath ->
resolved += fragment
resolved.last() != PathFragment.Root ->
resolved.removeAt(resolved.lastIndex)
}
is PathFragment.CurrentPath -> {
/* Always unnecessary. */
}
is AbsolutePathFragment.RecursiveWildcard -> {
// Consecutive recursive wildcards are redundant and may hurt
// performance of matching algorithms
if (resolved.last() != fragment) {
resolved += fragment
}
}
else -> resolved += fragment
}
}
}
return resolved
}
}
/** Path serialiser, serialising paths as strings. */
public object Serializer : KSerializer {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("io.kform.Path", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Path): Unit =
encoder.encodeString(value.toString())
override fun deserialize(decoder: Decoder): Path = Path(decoder.decodeString())
}
}
/** Converts the receiver path into an absolute path, if it wasn't one already. */
public fun Path.toAbsolutePath(): AbsolutePath =
if (this is AbsolutePath) this else AbsolutePath(this)
/**
* Converts the receiver [Path] or [String] into a path, if it wasn't one already.
*
* @throws IllegalArgumentException if the receiver is neither a [Path] nor a [String].
*/
public fun PathOrString.toPath(): Path =
when (this) {
is Path -> this
is String -> Path(this)
else -> throw IllegalArgumentException("The receiver must be either a path or a string.")
}
/**
* Converts the receiver [Path] or [String] into an absolute path, if it wasn't one already.
*
* @throws IllegalArgumentException if the receiver is neither a [Path] nor a [String].
*/
public fun PathOrString.toAbsolutePath(): AbsolutePath =
when (this) {
is Path -> this.toAbsolutePath()
is String -> AbsolutePath(this)
else -> throw IllegalArgumentException("The receiver must be either a path or a string.")
}