Gemtext: add a sweet streaming parser
This commit is contained in:
parent
3e42f2bace
commit
f2eae54234
64
app/src/main/java/dev/lowrespalmtree/comet/Gemtext.kt
Normal file
64
app/src/main/java/dev/lowrespalmtree/comet/Gemtext.kt
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package dev.lowrespalmtree.comet
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.CharBuffer
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
open class Line
|
||||||
|
|
||||||
|
class EmptyLine : Line()
|
||||||
|
class ParagraphLine(val text: String) : Line()
|
||||||
|
class TitleLine(val level: Int, val text: String) : Line()
|
||||||
|
class LinkLine(val url: String, val label: String) : Line()
|
||||||
|
class PreFenceLine(val caption: String) : Line()
|
||||||
|
class PreTextLine(val text: String) : Line()
|
||||||
|
class BlockquoteLine(val text: String) : Line()
|
||||||
|
class ListItemLine(val text: String) : Line()
|
||||||
|
|
||||||
|
fun parseData(
|
||||||
|
inChannel: Channel<ByteArray>,
|
||||||
|
charset: Charset,
|
||||||
|
scope: CoroutineScope
|
||||||
|
): Channel<Line> {
|
||||||
|
val channel = Channel<Line>()
|
||||||
|
scope.launch {
|
||||||
|
var isPref = false
|
||||||
|
var buffer = ByteArray(0)
|
||||||
|
for (data in inChannel) {
|
||||||
|
buffer += data
|
||||||
|
var nextLineFeed: Int = -1
|
||||||
|
while (buffer.isNotEmpty() && buffer.indexOf(0x0A).also { nextLineFeed = it } > -1) {
|
||||||
|
val lineData = buffer.sliceArray(0 until nextLineFeed)
|
||||||
|
buffer = buffer.sliceArray(nextLineFeed + 1 until buffer.size)
|
||||||
|
val lineString = charset.decode(ByteBuffer.wrap(lineData))
|
||||||
|
val line = parseLine(lineString, isPref)
|
||||||
|
when (line) {
|
||||||
|
is PreFenceLine -> isPref = !isPref
|
||||||
|
}
|
||||||
|
channel.send(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return channel
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseLine(line: CharBuffer, isPreformatted: Boolean): Line =
|
||||||
|
when {
|
||||||
|
line.isEmpty() -> EmptyLine()
|
||||||
|
line.startsWith("###") -> TitleLine(3, getCharsFrom(line, 3))
|
||||||
|
line.startsWith("##") -> TitleLine(2, getCharsFrom(line, 2))
|
||||||
|
line.startsWith("#") -> TitleLine(1, getCharsFrom(line, 1))
|
||||||
|
line.startsWith(">") -> BlockquoteLine(getCharsFrom(line, 1))
|
||||||
|
line.startsWith("```") -> PreFenceLine(getCharsFrom(line, 3))
|
||||||
|
line.startsWith("* ") -> ListItemLine(getCharsFrom(line, 2))
|
||||||
|
line.startsWith("=>") -> getCharsFrom(line, 2).split(" ", limit = 2)
|
||||||
|
.run { LinkLine(get(0), if (size == 2) get(1) else "") }
|
||||||
|
else -> if (isPreformatted) PreTextLine(line.toString()) else ParagraphLine(line.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCharsFrom(line: CharBuffer, index: Int) = line.substring(index).removePrefix(" ")
|
||||||
|
|
||||||
|
private const val TAG = "Gemtext"
|
|
@ -89,9 +89,17 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private suspend fun handleRequestSuccess(response: Response) {
|
private suspend fun handleRequestSuccess(response: Response) {
|
||||||
val charset = Charset.defaultCharset()
|
val charset = Charset.defaultCharset()
|
||||||
for (data in response.data) {
|
for (line in parseData(response.data, charset, viewModelScope)) {
|
||||||
val decoded = charset.decode(ByteBuffer.wrap(data)).toString()
|
when (line) {
|
||||||
source += decoded
|
is EmptyLine -> { source += "\n" }
|
||||||
|
is ParagraphLine -> { source += line.text + "\n" }
|
||||||
|
is TitleLine -> { source += "TTL-${line.level} ${line.text}\n" }
|
||||||
|
is LinkLine -> { source += "LNK ${line.url} + ${line.label}\n" }
|
||||||
|
is PreFenceLine -> { source += "PRE ${line.caption}\n" }
|
||||||
|
is PreTextLine -> { source += line.text + "\n" }
|
||||||
|
is BlockquoteLine -> { source += "QUO ${line.text}\n" }
|
||||||
|
is ListItemLine -> { source += "LST ${line.text}\n" }
|
||||||
|
}
|
||||||
sourceLiveData.postValue(source)
|
sourceLiveData.postValue(source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue