Coverage Summary for Class: AutolinkPostProcessor (io.heapy.kotmark.ext.autolink.internal)

Class Method, % Branch, % Line, % Instruction, %
AutolinkPostProcessor 83.3% (5/6) 89.5% (17/19) 100% (32/32) 98.9% (185/187)
AutolinkPostProcessor$AutolinkVisitor 100% (3/3) 100% (2/2) 100% (6/6) 100% (31/31)
AutolinkPostProcessor$WhenMappings
Total 88.9% (8/9) 90.5% (19/21) 100% (38/38) 99.1% (216/218)


 package io.heapy.kotmark.ext.autolink.internal
 
 import io.heapy.kotmark.autolink.LinkSpan
 import io.heapy.kotmark.autolink.LinkType
 import io.heapy.kotmark.autolink.linkExtractor
 import io.heapy.kotmark.ext.autolink.AutolinkType
 import io.heapy.kotmark.node.AbstractVisitor
 import io.heapy.kotmark.node.Link
 import io.heapy.kotmark.node.Node
 import io.heapy.kotmark.node.SourceSpan
 import io.heapy.kotmark.node.Text
 import io.heapy.kotmark.parser.PostProcessor
 
 class AutolinkPostProcessor(
     linkTypes: Set<AutolinkType>,
 ) : PostProcessor {
     init {
         require(linkTypes.isNotEmpty()) { "linkTypes must not be empty" }
     }
 
     private val extractor = linkExtractor { this.linkTypes = linkTypes.map { LinkType.valueOf(it.name) }.toSet() }
 
     override fun process(node: Node): Node {
         node.accept(AutolinkVisitor())
         return node
     }
 
     private fun linkify(originalTextNode: Text) {
         val literal = originalTextNode.literal.orEmpty()
         val spans = extractor.extractSpans(literal).iterator()
         val sourceSpan = originalTextNode.getSourceSpans().singleOrNull()
         var lastNode: Node = originalTextNode
         while (spans.hasNext()) {
             val span = spans.next()
             if (lastNode === originalTextNode && !spans.hasNext() && span !is LinkSpan) return
             val textNode = createTextNode(literal, span.beginIndex, span.endIndex, sourceSpan)
             val replacement = if (span is LinkSpan) {
                 val destination = when (span.type) {
                     LinkType.EMAIL -> "mailto:${textNode.literal}"
                     LinkType.WWW -> "http://${textNode.literal}"
                     LinkType.URL -> textNode.literal.orEmpty()
                 }
                 Link(destination, null).also { link ->
                     link.appendChild(textNode)
                     link.setSourceSpans(textNode.getSourceSpans())
                 }
             } else {
                 textNode
             }
             lastNode = insertNode(replacement, lastNode)
         }
         originalTextNode.unlink()
     }
 
     private fun createTextNode(
         literal: String,
         beginIndex: Int,
         endIndex: Int,
         sourceSpan: SourceSpan?,
     ): Text =
         Text(literal.substring(beginIndex, endIndex)).also { text ->
             sourceSpan?.let { text.addSourceSpan(it.subSpan(beginIndex, endIndex)) }
         }
 
     private fun insertNode(node: Node, insertAfterNode: Node): Node {
         insertAfterNode.insertAfter(node)
         return node
     }
 
     private inner class AutolinkVisitor : AbstractVisitor() {
         private var inLink = 0
 
         override fun visit(link: Link) {
             inLink++
             super.visit(link)
             inLink--
         }
 
         override fun visit(text: Text) {
             if (inLink == 0) {
                 linkify(text)
             }
         }
     }
 }