84 lines
2.9 KiB
Kotlin
84 lines
2.9 KiB
Kotlin
package be.simplenotes.domain.usecases.markdown
|
|
|
|
import arrow.core.Either
|
|
import arrow.core.extensions.fx
|
|
import arrow.core.left
|
|
import arrow.core.right
|
|
import be.simplenotes.domain.model.NoteMetadata
|
|
import be.simplenotes.domain.validation.NoteValidations
|
|
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension
|
|
import com.vladsch.flexmark.html.HtmlRenderer
|
|
import com.vladsch.flexmark.parser.Parser
|
|
import com.vladsch.flexmark.util.data.MutableDataSet
|
|
import io.konform.validation.ValidationErrors
|
|
import org.yaml.snakeyaml.Yaml
|
|
import org.yaml.snakeyaml.parser.ParserException
|
|
import org.yaml.snakeyaml.scanner.ScannerException
|
|
|
|
sealed class MarkdownParsingError
|
|
object MissingMeta : MarkdownParsingError()
|
|
object InvalidMeta : MarkdownParsingError()
|
|
class ValidationError(val validationErrors: ValidationErrors) : MarkdownParsingError()
|
|
|
|
data class Document(val metadata: NoteMetadata, val html: String)
|
|
|
|
typealias MetaMdPair = Pair<String, String>
|
|
|
|
interface MarkdownConverter {
|
|
fun renderDocument(input: String): Either<MarkdownParsingError, Document>
|
|
}
|
|
|
|
internal class MarkdownConverterImpl : MarkdownConverter {
|
|
private val yamlBoundPattern = "-{3}".toRegex()
|
|
private fun splitMetaFromDocument(input: String): Either<MissingMeta, MetaMdPair> {
|
|
val split = input.split(yamlBoundPattern, 3)
|
|
if (split.size < 3) return MissingMeta.left()
|
|
return (split[1].trim() to split[2].trim()).right()
|
|
}
|
|
|
|
private val yaml = Yaml()
|
|
private fun parseMeta(input: String): Either<InvalidMeta, NoteMetadata> {
|
|
val load: Map<String, Any> = try {
|
|
yaml.load(input)
|
|
} catch (e: ParserException) {
|
|
return InvalidMeta.left()
|
|
} catch (e: ScannerException) {
|
|
return InvalidMeta.left()
|
|
}
|
|
|
|
val title = when (val titleNode = load["title"]) {
|
|
is String, is Number -> titleNode.toString()
|
|
else -> return InvalidMeta.left()
|
|
}
|
|
|
|
val tagsNode = load["tags"]
|
|
val tags = if (tagsNode !is List<*>)
|
|
emptyList()
|
|
else
|
|
tagsNode.map { it.toString() }
|
|
|
|
return NoteMetadata(title, tags).right()
|
|
}
|
|
|
|
private val parser: Parser
|
|
private val renderer: HtmlRenderer
|
|
|
|
init {
|
|
val options = MutableDataSet()
|
|
options.set(Parser.EXTENSIONS, listOf(TaskListExtension.create()))
|
|
options.set(HtmlRenderer.SOFT_BREAK, "<br>")
|
|
parser = Parser.builder(options).build()
|
|
renderer = HtmlRenderer.builder(options).build()
|
|
}
|
|
|
|
private fun renderMarkdown(markdown: String) = parser.parse(markdown).run(renderer::render)
|
|
|
|
override fun renderDocument(input: String) = Either.fx<MarkdownParsingError, Document> {
|
|
val (meta, md) = !splitMetaFromDocument(input)
|
|
val parsedMeta = !parseMeta(meta)
|
|
!NoteValidations.validateMetadata(parsedMeta).toEither { }.swap()
|
|
val html = renderMarkdown(md)
|
|
Document(parsedMeta, html)
|
|
}
|
|
}
|