Skip to content

Commit

Permalink
korlibs-io-vfs
Browse files Browse the repository at this point in the history
  • Loading branch information
soywiz committed Jul 26, 2024
1 parent e3ff1a5 commit 96b70d6
Show file tree
Hide file tree
Showing 16 changed files with 1,418 additions and 18 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# korlibs-library-template
# korlibs-io-vfs
File renamed without changes.
23 changes: 23 additions & 0 deletions korlibs-io-vfs/module.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
product:
type: lib
platforms: [jvm, js, wasm, android, linuxX64, linuxArm64, tvosArm64, tvosX64, tvosSimulatorArm64, macosX64, macosArm64, iosArm64, iosSimulatorArm64, iosX64, watchosArm64, watchosArm32, watchosDeviceArm64, watchosSimulatorArm64, mingwX64]

apply: [ ../common.module-template.yaml ]

aliases:
- posix: [linuxX64, linuxArm64, tvosArm64, tvosX64, tvosSimulatorArm64, macosX64, macosArm64, iosArm64, iosSimulatorArm64, iosX64, watchosArm64, watchosArm32, watchosDeviceArm64, watchosSimulatorArm64]
- jvmAndAndroid: [jvm, android]
- concurrent: [jvm, android, linuxX64, linuxArm64, tvosArm64, tvosX64, tvosSimulatorArm64, macosX64, macosArm64, iosArm64, iosSimulatorArm64, iosX64, watchosArm64, watchosArm32, watchosDeviceArm64, watchosSimulatorArm64, mingwX64]

dependencies:
- com.soywiz:korlibs-io-fs:6.0.0
- com.soywiz:korlibs-concurrent:6.0.0
- com.soywiz:korlibs-platform:6.0.0: exported
- com.soywiz:korlibs-io-stream:6.0.0: exported
- com.soywiz:korlibs-time-core:6.0.0: exported
- com.soywiz:korlibs-string:6.0.0: exported
- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC: exported

test-dependencies:
- org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0-RC
- com.soywiz:korlibs-time:6.0.0
12 changes: 12 additions & 0 deletions korlibs-io-vfs/src/korlibs/io/file/FinalVfsFile.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package korlibs.io.file

suspend fun VfsFile.getUnderlyingUnscapedFile(): FinalVfsFile = vfs.getUnderlyingUnscapedFile(this.path)
fun VfsFile.toUnscaped() = FinalVfsFile(this)
fun FinalVfsFile.toFile() = this.file

//inline class FinalVfsFile(val file: VfsFile) {
data class FinalVfsFile(val file: VfsFile) {
constructor(vfs: Vfs, path: String) : this(vfs[path])
val vfs: Vfs get() = file.vfs
val path: String get() = file.path
}
249 changes: 249 additions & 0 deletions korlibs-io-vfs/src/korlibs/io/file/PathInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
package korlibs.io.file

import korlibs.io.core.*
import kotlin.math.*

val File_pathSeparatorChar: Char get() = SyncSystemFS.pathSeparatorChar
val File_separatorChar: Char get() = SyncSystemFS.fileSeparatorChar

// @TODO: inline classes. Once done PathInfoExt won't be required to do clean allocation-free stuff.
inline class PathInfo(val fullPath: String)

fun PathInfo.relativePathTo(relative: PathInfo): String? {
val thisParts = this.parts().toMutableList()
val relativeParts = relative.parts().toMutableList()
val maxNumParts = min(thisParts.size, relativeParts.size)
val outputParts = arrayListOf<String>()
val commonCount = count { it < maxNumParts && thisParts[it] == relativeParts[it] }
while (relativeParts.size > commonCount) {
relativeParts.removeLast()
outputParts += ".."
}
outputParts += thisParts.slice(commonCount until thisParts.size)
return outputParts.joinToString("/")
}

val String.pathInfo get() = PathInfo(this)

/**
* /path\to/file.ext -> /path/to/file.ext
*/
val PathInfo.fullPathNormalized: String get() = fullPath.replace('\\', '/')

/**
* /path\to/file.ext -> /path\to
*/
val PathInfo.folder: String get() = fullPath.substring(0, fullPathNormalized.lastIndexOfOrNull('/') ?: 0)

/**
* /path\to/file.ext -> /path/to/
*/
val PathInfo.folderWithSlash: String
get() = fullPath.substring(0, fullPathNormalized.lastIndexOfOrNull('/')?.plus(1) ?: 0)

/**
* /path\to/file.ext -> file.ext
*/
val PathInfo.baseName: String get() = fullPathNormalized.substringAfterLast('/')

/**
* /path\to/file.ext -> /path\to
*/
val PathInfo.parent: PathInfo get() = PathInfo(folder)

/**
* /path\to/file.ext -> /path\to/file
*/
val PathInfo.fullPathWithoutExtension: String
get() {
val startIndex = fullPathNormalized.lastIndexOfOrNull('/')?.plus(1) ?: 0
return fullPath.substring(0, fullPathNormalized.indexOfOrNull('.', startIndex) ?: fullPathNormalized.length)
}

/**
* /path\to/file.ext -> /path\to/file.newext
*/
fun PathInfo.fullPathWithExtension(ext: String): String =
if (ext.isEmpty()) fullPathWithoutExtension else "$fullPathWithoutExtension.$ext"

/**
* /path\to/file.1.ext -> file.1
*/
val PathInfo.baseNameWithoutExtension: String get() = baseName.substringBeforeLast('.',
baseName
)

/**
* /path\to/file.1.ext -> file
*/
val PathInfo.baseNameWithoutCompoundExtension: String get() = baseName.substringBefore('.',
baseName
)

/**
* /path\to/file.1.ext -> /path\to/file.1
*/
val PathInfo.fullNameWithoutExtension: String get() = "$folderWithSlash$baseNameWithoutExtension"

/**
* /path\to/file.1.ext -> file
*/
val PathInfo.fullNameWithoutCompoundExtension: String get() = "$folderWithSlash$baseNameWithoutCompoundExtension"

/**
* /path\to/file.1.ext -> file.1.newext
*/
fun PathInfo.baseNameWithExtension(ext: String): String =
if (ext.isEmpty()) baseNameWithoutExtension else "$baseNameWithoutExtension.$ext"

/**
* /path\to/file.1.ext -> file.newext
*/
fun PathInfo.baseNameWithCompoundExtension(ext: String): String =
if (ext.isEmpty()) baseNameWithoutCompoundExtension else "$baseNameWithoutCompoundExtension.$ext"

/**
* /path\to/file.1.EXT -> EXT
*/
val PathInfo.extension: String get() = baseName.substringAfterLast('.', "")

/**
* /path\to/file.1.EXT -> ext
*/
val PathInfo.extensionLC: String get() = extension.lowercase()

/**
* /path\to/file.1.EXT -> 1.EXT
*/
val PathInfo.compoundExtension: String get() = baseName.substringAfter('.', "")

/**
* /path\to/file.1.EXT -> 1.ext
*/
val PathInfo.compoundExtensionLC: String get() = compoundExtension.lowercase()

/**
* /path\to/file.1.ext -> listOf("", "path", "to", "file.1.ext")
*/
fun PathInfo.getPathComponents(): List<String> = fullPathNormalized.split('/')

/**
* /path\to/file.1.ext -> listOf("/path", "/path/to", "/path/to/file.1.ext")
*/
fun PathInfo.getPathFullComponents(): List<String> {
val out = arrayListOf<String>()
for (n in 0 until fullPathNormalized.length) {
when (fullPathNormalized[n]) {
'/', '\\' -> {
out += fullPathNormalized.substring(0, n)
}
}
}
out += fullPathNormalized
return out
}

/**
* /path\to/file.1.ext -> /path\to/file.1.ext
*/
val PathInfo.fullName: String get() = fullPath

interface Path {
val pathInfo: PathInfo
}

val Path.fullPathNormalized: String get() = pathInfo.fullPathNormalized
val Path.folder: String get() = pathInfo.folder
val Path.folderWithSlash: String get() = pathInfo.folderWithSlash
val Path.baseName: String get() = pathInfo.baseName
val Path.fullPathWithoutExtension: String get() = pathInfo.fullPathWithoutExtension
fun Path.fullPathWithExtension(ext: String): String = pathInfo.fullPathWithExtension(ext)
val Path.fullNameWithoutExtension: String get() = pathInfo.fullNameWithoutExtension
val Path.baseNameWithoutExtension: String get() = pathInfo.baseNameWithoutExtension
val Path.fullNameWithoutCompoundExtension: String get() = pathInfo.fullNameWithoutCompoundExtension
val Path.baseNameWithoutCompoundExtension: String get() = pathInfo.baseNameWithoutCompoundExtension
fun Path.baseNameWithExtension(ext: String): String = pathInfo.baseNameWithExtension(ext)
fun Path.baseNameWithCompoundExtension(ext: String): String = pathInfo.baseNameWithCompoundExtension(ext)
val Path.extension: String get() = pathInfo.extension
val Path.extensionLC: String get() = pathInfo.extensionLC
val Path.compoundExtension: String get() = pathInfo.compoundExtension
val Path.compoundExtensionLC: String get() = pathInfo.compoundExtensionLC
fun Path.getPathComponents(): List<String> = pathInfo.getPathComponents()
fun Path.getPathFullComponents(): List<String> = pathInfo.getPathFullComponents()
val Path.fullName: String get() = pathInfo.fullPath

open class VfsNamed(override val pathInfo: PathInfo) : Path


fun PathInfo.parts(): List<String> = fullPath.split('/')
fun PathInfo.normalize(removeEndSlash: Boolean = true): String {
val path = this.fullPath
val schemeIndex = path.indexOf(":")
return if (schemeIndex >= 0) {
val take = if (path.substring(schemeIndex).startsWith("://")) 3 else 1
path.substring(0, schemeIndex + take) + path.substring(schemeIndex + take).pathInfo.normalize(removeEndSlash = removeEndSlash)
} else {
val path2 = path.replace('\\', '/')
val out = ArrayList<String>()
val path2PathLength: Int
path2.split("/").also { path2PathLength = it.size }.fastForEachWithIndex { index, part ->
when (part) {
"" -> if (out.isEmpty() || !removeEndSlash) out += ""
"." -> if (index == path2PathLength - 1 && !removeEndSlash) out += ""
".." -> if (out.isNotEmpty() && index != 1) out.removeAt(out.size - 1)
else -> out += part
}
}
out.joinToString("/")
}
}

fun PathInfo.combine(access: PathInfo): PathInfo {
val base = this.fullPath
val access = access.fullPath
return (if (access.pathInfo.isAbsolute()) access.pathInfo.normalize() else "$base/$access"
.pathInfo.normalize()).pathInfo
}

fun PathInfo.lightCombine(access: PathInfo): PathInfo {
val base = this.fullPath
val access = access.fullPath
val res = if (base.isNotEmpty()) base.trimEnd('/') + "/" + access.trim('/') else access
return res.pathInfo
}

fun PathInfo.isAbsolute(): Boolean {
val base = this.fullPath
if (base.isEmpty()) return false
val b = base.replace('\\', '/').substringBefore('/')
if (b.isEmpty()) return true
if (b.contains(':')) return true
return false
}

fun PathInfo.normalizeAbsolute(): PathInfo {
val path = this.fullPath
//val res = path.replace('/', File.separatorChar).trim(File.separatorChar)
//return if (OS.isUnix) "/$res" else res
return PathInfo(path.replace('/', File_separatorChar))
}

private fun String.indexOfOrNull(char: Char, startIndex: Int = 0): Int? =
this.indexOf(char, startIndex).takeIf { it >= 0 }

private fun String.lastIndexOfOrNull(char: Char, startIndex: Int = lastIndex): Int? =
this.lastIndexOf(char, startIndex).takeIf { it >= 0 }

private inline fun count(cond: (index: Int) -> Boolean): Int {
var counter = 0
while (cond(counter)) counter++
return counter
}

private inline fun <T> List<T>.fastForEachWithIndex(callback: (index: Int, value: T) -> Unit) {
var n = 0
while (n < size) {
callback(n, this[n])
n++
}
}
Loading

0 comments on commit 96b70d6

Please sign in to comment.