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 interface MarkdownConverter { fun renderDocument(input: String): Either } internal class MarkdownConverterImpl : MarkdownConverter { private val yamlBoundPattern = "-{3}".toRegex() private fun splitMetaFromDocument(input: String): Either { 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 { val load: Map = 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, "
") 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 { val (meta, md) = !splitMetaFromDocument(input) val parsedMeta = !parseMeta(meta) !NoteValidations.validateMetadata(parsedMeta).toEither { }.swap() val html = renderMarkdown(md) Document(parsedMeta, html) } }