package be.simplenotes.domain import arrow.core.Either import arrow.core.computations.either import arrow.core.left import arrow.core.right import be.simplenotes.domain.validation.NoteValidations import be.simplenotes.types.NoteMetadata import com.vladsch.flexmark.html.HtmlRenderer import com.vladsch.flexmark.parser.Parser import io.konform.validation.ValidationErrors import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.parser.ParserException import org.yaml.snakeyaml.scanner.ScannerException import javax.inject.Singleton interface MarkdownService { fun renderDocument(input: String): Either } private typealias MetaMdPair = Pair @Singleton internal class MarkdownServiceImpl( private val parser: Parser, private val renderer: HtmlRenderer, ) : MarkdownService { private val yamlBoundPattern = "-{3}".toRegex() private fun splitMetaFromDocument(input: String): Either { val split = input.split(yamlBoundPattern, 3) if (split.size < 3) return MarkdownParsingError.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 MarkdownParsingError.InvalidMeta.left() } catch (e: ScannerException) { return MarkdownParsingError.InvalidMeta.left() } val title = when (val titleNode = load["title"]) { is String, is Number -> titleNode.toString() else -> return MarkdownParsingError.InvalidMeta.left() } val tagsNode = load["tags"] val tags = if (tagsNode !is List<*>) emptyList() else tagsNode.map { it.toString() } return NoteMetadata(title, tags).right() } private fun renderMarkdown(markdown: String) = parser.parse(markdown).run(renderer::render) override fun renderDocument(input: String) = either.eager { val (meta, md) = !splitMetaFromDocument(input) val parsedMeta = !parseMeta(meta) !Either.fromNullable(NoteValidations.validateMetadata(parsedMeta)).swap() val html = renderMarkdown(md) Document(parsedMeta, html) } } sealed class MarkdownParsingError { object MissingMeta : MarkdownParsingError() object InvalidMeta : MarkdownParsingError() class ValidationError(val validationErrors: ValidationErrors) : MarkdownParsingError() } data class Document(val metadata: NoteMetadata, val html: String)