77 lines
2.7 KiB
Kotlin
77 lines
2.7 KiB
Kotlin
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<MarkdownParsingError, Document>
|
|
}
|
|
|
|
private typealias MetaMdPair = Pair<String, String>
|
|
|
|
@Singleton
|
|
internal class MarkdownServiceImpl(
|
|
private val parser: Parser,
|
|
private val renderer: HtmlRenderer,
|
|
) : MarkdownService {
|
|
private val yamlBoundPattern = "-{3}".toRegex()
|
|
private fun splitMetaFromDocument(input: String): Either<MarkdownParsingError.MissingMeta, MetaMdPair> {
|
|
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<MarkdownParsingError.InvalidMeta, NoteMetadata> {
|
|
val load: Map<String, Any> = 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<MarkdownParsingError, Document> {
|
|
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)
|