Coverage Summary for Class: LinkExtractor (io.heapy.kotmark.autolink)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| LinkExtractor |
100%
(3/3)
|
|
100%
(6/6)
|
100%
(23/23)
|
| LinkExtractor$Configuration |
100%
(3/3)
|
100%
(6/6)
|
100%
(8/8)
|
100%
(61/61)
|
| LinkExtractor$extractLinks$$inlined$Iterable$1 |
0%
(0/2)
|
|
| LinkExtractor$extractSpans$$inlined$Iterable$1 |
0%
(0/2)
|
|
| LinkExtractor$LinkIterator |
100%
(3/3)
|
93.3%
(14/15)
|
100%
(18/18)
|
91.3%
(94/103)
|
| LinkExtractor$SpanIterator |
100%
(3/3)
|
100%
(14/14)
|
100%
(13/13)
|
100%
(80/80)
|
| Total |
75%
(12/16)
|
97.1%
(34/35)
|
100%
(45/45)
|
96.6%
(258/267)
|
// Port of autolink-java 0.12.0. Copyright (c) 2015 Robin Stocker. MIT; see UPSTREAM.md.
package io.heapy.kotmark.autolink
import io.heapy.kotmark.autolink.internal.EmailScanner
import io.heapy.kotmark.autolink.internal.Scanner
import io.heapy.kotmark.autolink.internal.SpanImpl
import io.heapy.kotmark.autolink.internal.UrlScanner
import io.heapy.kotmark.autolink.internal.WwwScanner
/** Immutable, reusable extractor. Each iterable creates independent, lazy iterators. */
class LinkExtractor private constructor(
private val urlScanner: Scanner?,
private val wwwScanner: Scanner?,
private val emailScanner: Scanner?,
) {
fun extractLinks(input: CharSequence): Iterable<LinkSpan> = Iterable { LinkIterator(input) }
fun extractSpans(input: CharSequence): Iterable<Span> = Iterable { SpanIterator(input, LinkIterator(input)) }
@AutolinkDsl
class Configuration internal constructor() {
var linkTypes = LinkType.entries.toSet()
set(value) {
field = value.toSet()
}
var emailDomainMustHaveDot = true
internal fun create(): LinkExtractor = LinkExtractor(
if (LinkType.URL in linkTypes) UrlScanner() else null,
if (LinkType.WWW in linkTypes) WwwScanner() else null,
if (LinkType.EMAIL in linkTypes) EmailScanner(emailDomainMustHaveDot) else null,
)
}
private inner class LinkIterator(private val input: CharSequence) : Iterator<LinkSpan> {
private var nextLink: LinkSpan? = null
private var index = 0
private var rewindIndex = 0
override fun hasNext(): Boolean {
if (nextLink != null) return true
while (index < input.length) {
val scanner = when (input[index]) {
':' -> urlScanner
'@' -> emailScanner
'w' -> wwwScanner
else -> null
}
val link = scanner?.scan(input, index, rewindIndex)
if (link != null) {
nextLink = link
index = link.endIndex
rewindIndex = index
return true
}
index++
}
return false
}
override fun next(): LinkSpan {
if (!hasNext()) throw NoSuchElementException()
return checkNotNull(nextLink).also { nextLink = null }
}
}
private class SpanIterator(
private val input: CharSequence,
private val links: Iterator<LinkSpan>,
) : Iterator<Span> {
private var index = 0
private var nextLink: LinkSpan? = null
override fun hasNext(): Boolean = index < input.length
override fun next(): Span {
if (!hasNext()) throw NoSuchElementException()
if (nextLink == null && links.hasNext()) nextLink = links.next()
val link = nextLink
if (link == null || index < link.beginIndex) {
val end = link?.beginIndex ?: input.length
return SpanImpl(index, end).also { index = end }
}
index = link.endIndex
nextLink = null
return link
}
}
}
/** Creates [LinkExtractor] with the supplied configuration. */
fun linkExtractor(configure: LinkExtractor.Configuration.() -> Unit = {}): LinkExtractor =
LinkExtractor.Configuration().apply(configure).create()