Skip to content

05nelsonm/kmp-file

Repository files navigation

kmp-file

badge-license badge-latest-release

badge-kotlin

badge-platform-android badge-platform-jvm badge-platform-js-node badge-platform-linux badge-platform-macos badge-platform-ios badge-platform-tvos badge-platform-watchos badge-platform-windows badge-support-apple-silicon badge-support-js-ir badge-support-linux-arm

A very simple File API for Kotlin Multiplatform. It gets the job done.

For Jvm, File is typealias to java.io.File, and commonMain extensions point to kotlin.io extensions so that the footprint is very small for Java/Android only consumers.

The File implementation for nonJvm is operationally equivalent to Jvm for consistency across platforms. Please submit an issue if you discover any inconsistencies (e.g. path resolution).

import io.matthewnelson.kmp.file.*

fun commonMain(f: File) {
    // System directory separator character (`/` or `\`)
    SysDirSep
    // System temporary directory
    SysTempDir

    f.isAbsolute()
    f.exists()
    f.delete()
    f.mkdir()
    f.mkdirs()

    // ↓↓ Extension functions ↓↓
    f.name
    f.parentPath
    f.parentFile
    f.path
    f.absolutePath
    f.absoluteFile
    f.canonicalPath()
    f.canonicalFile()

    // equivalent to File("/some/path")
    val file = "/some/path".toFile()

    // resolve child paths
    val child = file.resolve("child")
    println(child.path) // >> `/some/path/child`
    println(child.resolve(file).path) // >> `/some/path` (file is rooted)

    // normalized File (e.g. removal of . and ..)
    f.normalize()

    // basic write functionality
    f.writeBytes(ByteArray(25) { it.toByte() })
    f.writeUtf8("Hello World!")

    // basic read functionality
    f.readBytes()
    val utf8: String = f.readUtf8()
    println(utf8) // prints >> Hello World!
}
import io.matthewnelson.kmp.file.*

fun nonJvmMain(f: File) {
    // File permissions (no-op for Windows) for Node.js/Native
    f.chmod("700")
}
import io.matthewnelson.kmp.file.*

fun jsMain(f1: File, f2: File) {
    // Node.js specific extension functions

    // Buffers for reading/writing
    val buffer = f1.read()
    val b = ByteArray(25) { i -> buffer.readInt8(i) }
    println(b.toList())

    // If APIs aren't available, simply unwrap
    val bufferDynamic = buffer.unwrap()
    val gzipBufferDynamic = try {
        // zlib might not work if you haven't declared
        // it, but for this example lets assume it's
        // available.
        js("require('zlib')").gzipSync(bufferDynamic)
    } catch (t: Throwable) {
        // helper for converting exceptions to IOException
        throw t.toIOException()
    }

    // Can re-wrap the dynamic buffer for a "safer" API
    val gzipBuffer = Buffer.wrap(gzipBufferDynamic)

    f2.write(gzipBuffer)

    // File stats
    val lstats = f1.lstat()
    val stats = f1.stat()
    
    // If APIs aren't available, simply unwrap to use.
    val statsDynamic = stats.unwrap()
    statsDynamic.nlink as Int
}
import io.matthewnelson.kmp.file.*

@OptIn(DelicateFileApi::class, ExperimentalForeignApi::class)
fun nativeMain(f1: File, f2: File) {
    // Native specific extension functions

    // Use a CPointer<FILE> with auto-closure on completion
    f1.fOpen(flags = "rb") { file1 ->
        f2.fOpen(flags = "wb") { file2 ->
            val buf = ByteArray(4096)

            while (true) {
                // posix.fread helper extension for CPointer<FILE>
                val read = file1.fRead(buf)
                if (read < 0) throw errnoToIOException(errno)
                if (read == 0) break

                // posix.fwrite helper extension for CPointer<FILE>
                if (file2.fWrite(buf, len = read) < 0) {

                    // helper for converting an error to an IOException
                    throw errnoToIOException(errno)
                }
            }
        }
    }
}

Get Started

dependencies {
    implementation("io.matthewnelson.kmp-file:file:0.1.0-beta03")
}