Skip to content

Commit

Permalink
Drop okio (#9)
Browse files Browse the repository at this point in the history
okio -> CharArray
  • Loading branch information
jisungbin authored Jul 1, 2024
1 parent 01f49fe commit 39e8d22
Show file tree
Hide file tree
Showing 24 changed files with 127 additions and 339 deletions.
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
## Compose Markdown

```kotlin
Buffer().markdown {
markdown {
H1("Hello, Compose-Markdown!")
Quote(
modifier = Modifier.clickable(link = "https://github.com/jisungbin/compose-markdown"),
modifier = Modifier.clickable("https://github.com/jisungbin/compose-markdown"),
text = buildAnnotatedString {
append("Build ")
withStyle(TextStyle(italic = true)) { append("Markdown") }
Expand All @@ -17,7 +17,3 @@ Buffer().markdown {
```

WIP

### Limitations

- Text larger than 8 KB might not output properly.
2 changes: 0 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ kotlin-coroutines = "1.9.0-RC" # K2

compose-runtime = "1.6.8"
androidx-annotation = "1.8.0"
okio = "3.9.0"

test-assertk = "0.28.1"

Expand All @@ -30,7 +29,6 @@ compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "
compose-uiutil = { module = "androidx.compose.ui:ui-util", version.ref = "compose-runtime" }

androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }

test-assertk = { module = "com.willowtreeapps.assertk:assertk", version.ref = "test-assertk" }
test-kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" }
Expand Down
1 change: 0 additions & 1 deletion markdown-runtime/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ dependencies {
implementation(libs.androidx.annotation)
implementation(libs.compose.runtime)

api(libs.okio)
api(libs.kotlin.coroutines)

testImplementation(kotlin("test-junit5"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,5 @@ public suspend fun markdown(
return buildString {
appendLine(root.draw(options))
append(footnotes.draw(options))
}.also {
root.close()
footnotes.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,15 @@
package land.sungbin.markdown.runtime

import androidx.compose.runtime.collection.MutableVector
import okio.Buffer
import okio.BufferedSource
import okio.Closeable
import org.jetbrains.annotations.TestOnly

// TODO documentation
public class MarkdownNode(
private val source: ((MarkdownOptions) -> BufferedSource)? = null,
private val source: ((MarkdownOptions) -> String)? = null,
private val kind: MarkdownKind = MarkdownKind.TEXT,
private val contentKind: MarkdownKind? = null,
private val contentTag: ((index: Int, MarkdownOptions) -> String)? = null,
) : Closeable, Cloneable {
) {
internal val children = MutableVectorWithMutationTracking(MutableVector<MarkdownNode>(capacity = 20)) {
check(kind.layout) { "Children can be added only to a group or footnote node." }
}
Expand Down Expand Up @@ -71,42 +68,39 @@ public class MarkdownNode(
return contentTag!!.invoke(index, options)
}

internal fun draw(options: MarkdownOptions): BufferedSource = Buffer().apply {
when {
text -> writeStringMarkdown(options)
group -> writeGroupMarkdown(options)
footnote -> writeFootnoteMarkdown(options)
}
}

private fun Buffer.writeStringMarkdown(options: MarkdownOptions) {
writeAll(source!!.invoke(options))
internal fun draw(options: MarkdownOptions): String = when {
text -> drawStringMarkdown(options)
group -> drawGroupMarkdown(options)
footnote -> drawFootnoteMarkdown(options)
else -> runtimeError { "[draw] unreachable code: $kind" }
}

private fun Buffer.writeFootnoteMarkdown(options: MarkdownOptions) {
var tag: String? = null
private fun drawStringMarkdown(options: MarkdownOptions): String =
source!!.invoke(options)

children.forEach { child ->
if (tag == null) tag = tag(child.index, options)
val source = child.draw(options)
while (!source.exhausted()) {
val line = source.readUtf8Line() ?: break
writeUtf8(tag!!).writeUtf8(line).writeByte(NEW_LINE)
private fun drawFootnoteMarkdown(options: MarkdownOptions) = buildString {
var tag = tag(children.vector.firstOrNull()?.index ?: return@buildString, options)
children.vector.forEach { child ->
val source = child.draw(options).lineSequence()
for (line in source) {
append(tag).append(line).append(NEW_LINE)
if (tag != FOOTNOTE_INDENT) tag = FOOTNOTE_INDENT
}
}
}

private fun Buffer.writeGroupMarkdown(options: MarkdownOptions) {
children.forEach { child ->
val source = child.draw(options)
private fun drawGroupMarkdown(options: MarkdownOptions) = buildString {
val lastChildIndex = children.vector.lastIndex
children.vector.forEachIndexed { index, child ->
val tag = tag(child.index, options)
var touched = false
while (!source.exhausted()) {
val line = source.readUtf8Line() ?: break
val source = child.draw(options).lineSequence().iterator()
while (source.hasNext()) {
val line = source.next()
val prefix = (contentKind!! + child.kind).prefix(tag = tag, forceConcat = !touched)
writeUtf8(prefix).writeUtf8(line).writeByte(NEW_LINE)
touched = true
append(prefix).append(line)
if (index != lastChildIndex || source.hasNext()) append(NEW_LINE)
if (!touched) touched = true
}
}
}
Expand All @@ -118,19 +112,10 @@ public class MarkdownNode(
else -> runtimeError { "[prefix] unreachable code: $this" }
}

override fun toString(): String = clone().draw(MarkdownOptions.Default).readUtf8()

public override fun clone(): MarkdownNode = MarkdownNode(source, kind, contentKind, contentTag).also { clone ->
children.forEach { child -> clone.children.add(child) }
}

override fun close() {
children.forEach { it.close() }
children.clear()
}
override fun toString(): String = draw(MarkdownOptions.Default)

private companion object {
private const val NEW_LINE = '\n'.code
private const val NEW_LINE = '\n'
private const val FOOTNOTE_INDENT = " "
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,4 @@ internal class MutableVectorWithMutationTracking<T>(
private val onVectorMutated: () -> Unit,
) {
inline fun add(element: T) = vector.add(element).also { onVectorMutated() }
inline fun clear() = if (vector.isNotEmpty()) vector.clear().also { onVectorMutated() } else Unit
inline fun forEach(block: (T) -> Unit) = vector.forEach(block)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,17 @@ import assertk.assertFailure
import assertk.assertThat
import assertk.assertions.hasMessage
import assertk.assertions.isEqualTo
import kotlin.test.AfterTest
import kotlin.test.Test
import land.sungbin.markdown.runtime.MarkdownKind
import land.sungbin.markdown.runtime.MarkdownNode
import land.sungbin.markdown.runtime.MarkdownOptions
import okio.Buffer

class MarkdownNodeTest {
private val worldSource = Buffer().writeUtf8("Hello, World!\nMorning, World!\nBye, World!")
private val worldSource2 = Buffer().writeUtf8("Hello, World2!\nMorning, World2!\nBye, World2!")
private val worldSource = "Hello, World!\nMorning, World!\nBye, World!"
private val worldSource2 = "Hello, World2!\nMorning, World2!\nBye, World2!"

private lateinit var node: MarkdownNode

@AfterTest fun cleanup() {
if (::node.isInitialized) try {
node.close()
} catch (_: Exception) {
}
}

@Test fun markdownNodeShouldHaveTextOrLayoutKind() {
assertFailure { node = MarkdownNode(kind = MarkdownKind.REPEATATION_PARENT_TAG) }
.hasMessage(
Expand All @@ -48,17 +39,17 @@ class MarkdownNodeTest {
}

@Test fun noneLayoutNodeShouldntHaveContentKind() {
assertFailure { node = MarkdownNode(source = { Buffer() }, contentKind = MarkdownKind.TEXT) }
assertFailure { node = MarkdownNode(source = { "" }, contentKind = MarkdownKind.TEXT) }
.hasMessage("A node that is not a group or a footnote cannot have a 'contentKind'.")
}

@Test fun noneLayoutNodeShouldntHaveContentTag() {
assertFailure { node = MarkdownNode(source = { Buffer() }, contentTag = { _, _ -> "" }) }
assertFailure { node = MarkdownNode(source = { "" }, contentTag = { _, _ -> "" }) }
.hasMessage("A node that is not a group or a footnote cannot have a 'contentTag'.")
}

@Test fun layoutNodeShouldntHaveSource() {
assertFailure { node = MarkdownNode(source = { Buffer() }, kind = MarkdownKind.GROUP) }
assertFailure { node = MarkdownNode(source = { "" }, kind = MarkdownKind.GROUP) }
.hasMessage(
"A node that is a group or a footnote cannot have its own 'source'. " +
"Only children are allowed to be the source of a 'source'.",
Expand All @@ -77,16 +68,16 @@ class MarkdownNodeTest {

@Test fun childrenCanBeAddedOnlyToLayoutNode() {
assertFailure {
node = MarkdownNode(source = { Buffer() })
node.children.add(MarkdownNode(source = { Buffer() }))
node = MarkdownNode(source = { "" })
node.children.add(MarkdownNode(source = { "" }))
}
.hasMessage("Children can be added only to a group or footnote node.")
}

@Test fun drawingText() {
node = MarkdownNode(source = { worldSource })

assertThat(node.draw(MarkdownOptions()).readUtf8())
assertThat(node.draw(MarkdownOptions()))
.isEqualTo("Hello, World!\nMorning, World!\nBye, World!")
}

Expand All @@ -101,7 +92,7 @@ class MarkdownNodeTest {
contentTag = { _, _ -> "[^T]: " },
)

assertThat(node.draw(MarkdownOptions()).readUtf8())
assertThat(node.draw(MarkdownOptions()))
.isEqualTo(
"[^T]: Hello, World!\n Morning, World!\n Bye, World!\n" +
" Hello, World2!\n Morning, World2!\n Bye, World2!\n",
Expand All @@ -119,10 +110,10 @@ class MarkdownNodeTest {
contentTag = { _, _ -> "P. " },
)

assertThat(node.draw(MarkdownOptions()).readUtf8())
assertThat(node.draw(MarkdownOptions()))
.isEqualTo(
"P. Hello, World!\nP. Morning, World!\nP. Bye, World!\n" +
"P. Hello, World2!\nP. Morning, World2!\nP. Bye, World2!\n",
"P. Hello, World2!\nP. Morning, World2!\nP. Bye, World2!",
)
}

Expand All @@ -145,7 +136,7 @@ class MarkdownNodeTest {
contentTag = { _, _ -> "> " },
)

assertThat(node.draw(MarkdownOptions()).readUtf8())
assertThat(node.draw(MarkdownOptions()))
.isEqualTo(
"""
> Hello, World!
Expand All @@ -154,15 +145,14 @@ class MarkdownNodeTest {
> - Hello, World2!
> - Morning, World2!
> - Bye, World2!
""".trimIndent(),
)
}

@Test fun drawingGroupInNestedGroup() {
val nestedChildrenNodes = MarkdownNode(
children = List(3) { actualIndex ->
MarkdownNode(source = { Buffer().writeUtf8("My ordered list!\nMy ordered list's new line!") })
MarkdownNode(source = { "My ordered list!\nMy ordered list's new line!" })
.apply { index = actualIndex }
},
kind = MarkdownKind.GROUP + MarkdownKind.REPEATATION_PARENT_TAG,
Expand All @@ -181,15 +171,15 @@ class MarkdownNodeTest {
)
node = MarkdownNode(
children = listOf(
MarkdownNode(source = { Buffer().writeUtf8("Hello!") }),
MarkdownNode(source = { "Hello!" }),
childrenNodes,
),
kind = MarkdownKind.GROUP,
contentKind = MarkdownKind.ANY,
contentTag = { _, _ -> "" },
)

assertThat(node.draw(MarkdownOptions()).readUtf8())
assertThat(node.draw(MarkdownOptions()))
.isEqualTo(
"""
Hello!
Expand All @@ -205,7 +195,6 @@ class MarkdownNodeTest {
> Hello, World2!
> Morning, World2!
> Bye, World2!
""".trimIndent(),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
package land.sungbin.markdown.ui

import androidx.compose.runtime.Updater
import okio.Buffer

internal val bufferCursor = Buffer.UnsafeCursor()

@PublishedApi
internal data object EmptyUpdater : (Updater<out Any>) -> Unit {
Expand Down
31 changes: 13 additions & 18 deletions markdown-ui/src/main/kotlin/land/sungbin/markdown/ui/image/Image.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,36 @@

package land.sungbin.markdown.ui.image

import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import land.sungbin.markdown.runtime.MarkdownComposable
import land.sungbin.markdown.ui.modifier.Modifier
import land.sungbin.markdown.ui.text.AbstractText
import land.sungbin.markdown.ui.text.Text
import land.sungbin.markdown.ui.unit.Size

@PublishedApi
internal class ImageText(
@VisibleForTesting
internal fun buildImageText(
url: String,
size: Size? = null,
alt: String? = null,
) : AbstractText() {
init {
sink {
writeUtf8("<img ")
writeUtf8("src=\"$url\" ")
if (size != null) {
val (width, height) = size
writeUtf8("width=\"$width\" height=\"$height\" ")
}
if (alt != null) writeUtf8("alt=\"$alt\" ")
writeUtf8("/>")
}
): String = buildString {
append("<img ")
append("src=\"$url\" ")
if (size != null) {
val (width, height) = size
append("width=\"$width\" height=\"$height\" ")
}
if (alt != null) append("alt=\"$alt\" ")
append("/>")
}

@Suppress("NOTHING_TO_INLINE")
@[Composable NonRestartableComposable MarkdownComposable]
public inline fun Image(
public fun Image(
url: String,
size: Size? = null,
alt: String? = null,
modifier: Modifier = Modifier,
) {
Text(modifier = modifier, value = ImageText(url, size, alt))
Text(modifier = modifier, value = buildImageText(url, size, alt))
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public inline fun List(
MarkdownNode(
kind = MarkdownKind.GROUP,
contentKind = MarkdownKind.ANY,
contentTag = { index, _ -> if (ordered) "${index + 1}. " else "* " },
contentTag = { index, _ -> if (ordered) "${index + 1}. " else "- " },
)
},
update = EmptyUpdater,
Expand Down
Loading

0 comments on commit 39e8d22

Please sign in to comment.