Skip to content

Commit e8565bd

Browse files
committed
Support JS target
1 parent 6a172a7 commit e8565bd

File tree

8 files changed

+660
-9
lines changed

8 files changed

+660
-9
lines changed

core/build.gradle.kts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ kotlin {
2020
}
2121
}
2222

23+
js(IR) {
24+
nodejs {
25+
testTask {
26+
useMocha {
27+
timeout = "30s"
28+
}
29+
}
30+
}
31+
}
32+
2333
configureNativePlatforms()
2434
sourceSets {
2535
val commonMain by getting

core/common/test/files/SmokeFileTest.kt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55

66
package kotlinx.io.files
77

8-
import kotlinx.io.createTempFile
9-
import kotlinx.io.deleteFile
10-
import kotlinx.io.readUtf8Line
11-
import kotlinx.io.writeUtf8
8+
import kotlinx.io.*
129
import kotlin.test.AfterTest
1310
import kotlin.test.BeforeTest
1411
import kotlin.test.Test
@@ -31,6 +28,7 @@ class SmokeFileTest {
3128
@Test
3229
fun testBasicFile() {
3330
val path = Path(tempFile!!)
31+
3432
path.sink().use {
3533
it.writeUtf8("example")
3634
}
@@ -39,4 +37,20 @@ class SmokeFileTest {
3937
assertEquals("example", it.readUtf8Line())
4038
}
4139
}
40+
41+
@OptIn(ExperimentalStdlibApi::class)
42+
@Test
43+
fun testReadWriteMultipleSegments() {
44+
val path = Path(tempFile!!)
45+
46+
val data = ByteArray((Segment.SIZE * 2.5).toInt()) { it.toByte() }
47+
48+
path.sink().use {
49+
it.write(data)
50+
}
51+
52+
path.source().use {
53+
assertArrayEquals(data, it.readByteArray())
54+
}
55+
}
4256
}

core/js/src/-PlatfromJs.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.io
7+
8+
import kotlinx.io.internal.commonAsUtf8ToByteArray
9+
10+
internal actual fun String.asUtf8ToByteArray(): ByteArray = commonAsUtf8ToByteArray()
11+
12+
public actual open class IOException actual constructor(
13+
message: String?,
14+
cause: Throwable?
15+
) : Exception(message, cause) {
16+
public actual constructor(message: String?) : this(message, null)
17+
}
18+
19+
public actual open class EOFException actual constructor(message: String?) : IOException(message)

core/js/src/RawSink.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.io
7+
8+
@OptIn(ExperimentalStdlibApi::class)
9+
public actual interface RawSink : AutoCloseableAlias {
10+
public actual fun write(source: Buffer, byteCount: Long)
11+
12+
public actual fun flush()
13+
14+
actual override fun close()
15+
}

core/js/src/SegmentPool.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.io
7+
8+
internal actual object SegmentPool {
9+
actual val MAX_SIZE: Int = 0
10+
11+
actual val byteCount: Int = 0
12+
13+
actual fun take(): Segment = Segment()
14+
15+
actual fun recycle(segment: Segment) {
16+
}
17+
}

core/js/src/files/PathsJs.kt

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.io.files
7+
8+
import kotlinx.io.*
9+
10+
private val fs: dynamic
11+
get(): dynamic {
12+
return try {
13+
js("require('fs')")
14+
} catch (t: Throwable) {
15+
null
16+
}
17+
}
18+
19+
private val buffer: dynamic
20+
get(): dynamic {
21+
return try {
22+
js("require('buffer')")
23+
} catch (t: Throwable) {
24+
null
25+
}
26+
}
27+
28+
public actual class Path internal constructor(private val path: String, any: Any?) {
29+
override fun toString(): String = path
30+
}
31+
32+
public actual fun Path(path: String): Path {
33+
return Path(path, null)
34+
}
35+
36+
public actual fun Path.source(): Source {
37+
check(fs !== null) { "Module 'fs' was not found" }
38+
return FileSource(this).buffered()
39+
}
40+
41+
public actual fun Path.sink(): Sink {
42+
check(fs !== null) { "Module 'fs' was not found" }
43+
check(buffer !== null) { "Module 'buffer' was not found" }
44+
return FileSink(this).buffered()
45+
}
46+
47+
private class FileSource(private val path: Path) : RawSource {
48+
private var buffer: dynamic = null
49+
private var closed = false
50+
private var offset = 0
51+
52+
@OptIn(ExperimentalUnsignedTypes::class)
53+
override fun readAtMostTo(sink: Buffer, byteCount: Long): Long {
54+
check(!closed) { "Source is closed." }
55+
if (byteCount == 0L) {
56+
return 0
57+
}
58+
if (buffer === null) {
59+
buffer = fs.readFileSync(path.toString(), null)
60+
}
61+
val len: Int = buffer.length as Int
62+
if (offset >= len) {
63+
return -1L
64+
}
65+
val bytesToRead = minOf(byteCount, (len - offset))
66+
for (i in 0 until bytesToRead) {
67+
sink.writeByte(buffer.readInt8(offset++) as Byte)
68+
}
69+
70+
return bytesToRead
71+
}
72+
73+
override fun close() {
74+
closed = true
75+
}
76+
}
77+
78+
private class FileSink(private val path: Path) : RawSink {
79+
private var closed = false
80+
private var append = false
81+
82+
override fun write(source: Buffer, byteCount: Long) {
83+
check(!closed) { "Sink is closed." }
84+
if (byteCount == 0L) {
85+
return
86+
}
87+
88+
var remainingBytes = minOf(byteCount, source.size)
89+
while (remainingBytes > 0) {
90+
val head = source.head!!
91+
val segmentBytes = head.limit - head.pos
92+
val buf = buffer.Buffer.allocUnsafe(segmentBytes)
93+
buf.fill(head.data, head.pos, segmentBytes)
94+
if (append) {
95+
fs.appendFileSync(path.toString(), buf)
96+
} else {
97+
fs.writeFileSync(path.toString(), buf)
98+
append = true
99+
}
100+
101+
source.skip(segmentBytes.toLong())
102+
remainingBytes -= segmentBytes
103+
}
104+
}
105+
106+
override fun flush() = Unit
107+
108+
override fun close() {
109+
closed = true
110+
}
111+
}

core/js/test/utils.kt

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.io
7+
8+
import kotlin.random.Random
9+
import kotlin.random.nextULong
10+
11+
private val os: dynamic
12+
get(): dynamic {
13+
return try {
14+
js("require('os')")
15+
} catch (t: Throwable) {
16+
null
17+
}
18+
}
19+
20+
private val fs: dynamic
21+
get(): dynamic {
22+
return try {
23+
js("require('fs')")
24+
} catch (t: Throwable) {
25+
null
26+
}
27+
}
28+
29+
private val nodePath: dynamic
30+
get(): dynamic {
31+
return try {
32+
js("require('path')")
33+
} catch (t: Throwable) {
34+
null
35+
}
36+
}
37+
38+
actual fun createTempFile(): String {
39+
while (true) {
40+
val tmpdir = os.tmpdir()
41+
val filename = Random.nextULong().toString()
42+
val fullpath = "$tmpdir${nodePath.sep}$filename.txt"
43+
44+
if (fs.existsSync(fullpath)) {
45+
continue
46+
}
47+
return fullpath
48+
}
49+
}
50+
51+
actual fun deleteFile(path: String) {
52+
if (!fs.existsSync(path)) {
53+
throw IOException("File does not exist: $path")
54+
}
55+
fs.rmSync(path)
56+
}

0 commit comments

Comments
 (0)