Separating from KTor

This commit is contained in:
Alexander Nozik 2022-06-20 14:11:41 +03:00
parent e9f71cdab9
commit a907c57134
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
8 changed files with 111 additions and 55 deletions

View File

@ -1,3 +1,3 @@
kotlin.code.style=official kotlin.code.style=official
toolsVersion=0.11.5-kotlin-1.6.21 toolsVersion=0.11.7-kotlin-1.7.0

View File

@ -296,7 +296,7 @@ internal fun Application.spcMaster(context: Context, dataPath: Path, prefix: Str
val snark = context.fetch(SnarkPlugin) val snark = context.fetch(SnarkPlugin)
val magProgPageContext: PageContext = snark.parse(prefix, dataPath.resolve("content")) val magProgPageContext: PageContext = snark.read(dataPath.resolve("content"), prefix)
routing { routing {
route(prefix) { route(prefix) {

View File

@ -5,8 +5,6 @@ import io.ktor.server.application.Application
import io.ktor.server.application.call import io.ktor.server.application.call
import io.ktor.server.application.log import io.ktor.server.application.log
import io.ktor.server.html.respondHtml import io.ktor.server.html.respondHtml
import io.ktor.server.http.content.files
import io.ktor.server.http.content.static
import io.ktor.server.routing.* import io.ktor.server.routing.*
import kotlinx.html.* import kotlinx.html.*
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
@ -304,26 +302,17 @@ internal fun Application.spcHome(context: Context, rootPath: Path, prefix: Strin
val snark = context.fetch(SnarkPlugin) val snark = context.fetch(SnarkPlugin)
val homePageContext = snark.parse(prefix, rootPath.resolve("content")) val homePageContext = snark.read(rootPath.resolve("content"), prefix)
routing { routing {
route(prefix) { route(prefix) {
snark(homePageContext) {
staticDirectory("assets", rootPath.resolve("assets"))
staticDirectory("images", rootPath.resolve("images"))
page { spcHome() }
}
with(homePageContext) { with(homePageContext) {
static("assets") {
files(rootPath.resolve("assets").toFile())
}
static("images") {
files(rootPath.resolve("images").toFile())
}
get {
withRequest(call.request) {
call.respondHtml {
spcHome()
}
}
}
spcDirectory("consulting") spcDirectory("consulting")
spcPage("ru/consulting") spcPage("ru/consulting")

View File

@ -63,12 +63,15 @@ internal val Data<*>.published: Boolean get() = meta["published"].string != "fal
fun PageContext(rootUrl: String, data: DataSet<*>): PageContext = PageContext(rootUrl, data.meta, data) fun PageContext(rootUrl: String, data: DataSet<*>): PageContext = PageContext(rootUrl, data.meta, data)
fun SnarkPlugin.parse(rootUrl: String, path: Path): PageContext { fun SnarkPlugin.read(path: Path, rootUrl: String = "/"): PageContext {
val parsedData: DataSet<Any> = readDirectory(path) val parsedData: DataSet<Any> = readDirectory(path)
return PageContext(rootUrl, parsedData) return PageContext(rootUrl, parsedData)
} }
/**
* Substitute uri in [PageContext] with uri in the call to properly resolve relative refs. Only host properties are substituted.
*/
context(PageContext) inline fun withRequest(request: ApplicationRequest, block: context(PageContext) () -> Unit) { context(PageContext) inline fun withRequest(request: ApplicationRequest, block: context(PageContext) () -> Unit) {
val uri = URLBuilder( val uri = URLBuilder(
protocol = URLProtocol.createOrDefault(request.origin.scheme), protocol = URLProtocol.createOrDefault(request.origin.scheme),

View File

@ -0,0 +1,61 @@
package space.kscience.snark
import io.ktor.server.application.call
import io.ktor.server.html.respondHtml
import io.ktor.server.http.content.file
import io.ktor.server.http.content.files
import io.ktor.server.http.content.static
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.route
import kotlinx.html.HTML
import java.nio.file.Path
interface RouteBuilder {
val pageContext: PageContext
fun staticFile(remotePath: String, file: Path)
fun staticDirectory(remotePath: String, directory: Path)
fun page(route: String = "", content: context(PageContext) HTML.() -> Unit)
fun route(route: String, block: RouteBuilder.() -> Unit)
}
class KtorRouteBuilder(override val pageContext: PageContext, private val ktorRoute: Route) : RouteBuilder {
override fun staticFile(remotePath: String, file: Path) {
ktorRoute.file(remotePath, file.toFile())
}
override fun staticDirectory(remotePath: String, directory: Path) {
ktorRoute.static(remotePath) {
files(directory.toFile())
}
}
override fun page(route: String, content: context(PageContext) HTML.() -> Unit) {
ktorRoute.get(route) {
with(pageContext) {
withRequest(call.request) {
val innerContext = this
call.respondHtml {
content(innerContext, this)
}
}
}
}
}
override fun route(route: String, block: RouteBuilder.() -> Unit) {
ktorRoute.route(route) {
block(KtorRouteBuilder(pageContext, this))
}
}
}
fun Route.snark(pageContext: PageContext, block: context(PageContext) RouteBuilder.() -> Unit) {
with(pageContext){
block(KtorRouteBuilder(pageContext, this@snark))
}
}

View File

@ -1,20 +0,0 @@
package space.kscience.snark
import io.ktor.http.ContentType
import io.ktor.utils.io.core.Input
import kotlinx.html.div
import kotlinx.html.unsafe
import kotlin.reflect.KType
import kotlin.reflect.typeOf
object SnarkHtmlParser : SnarkParser<HtmlFragment> {
override val contentType: ContentType = ContentType.Text.Html
override val fileExtensions: Set<String> = setOf("html")
override val type: KType = typeOf<HtmlFragment>()
override fun readObject(input: Input): HtmlFragment = {
div {
unsafe { +input.readText() }
}
}
}

View File

@ -1,6 +1,5 @@
package space.kscience.snark package space.kscience.snark
import io.ktor.http.ContentType
import io.ktor.util.extension import io.ktor.util.extension
import io.ktor.utils.io.core.readBytes import io.ktor.utils.io.core.readBytes
import space.kscience.dataforge.context.* import space.kscience.dataforge.context.*
@ -24,10 +23,11 @@ import kotlin.reflect.KClass
import kotlin.reflect.KType import kotlin.reflect.KType
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
/**
* A parser of binary content including priority flag and file extensions
*/
@Type(SnarkParser.TYPE) @Type(SnarkParser.TYPE)
interface SnarkParser<out R : Any> : IOReader<R> { interface SnarkParser<out R : Any> : IOReader<R> {
val contentType: ContentType
val fileExtensions: Set<String> val fileExtensions: Set<String>
val priority: Int get() = DEFAULT_PRIORITY val priority: Int get() = DEFAULT_PRIORITY
@ -46,16 +46,17 @@ interface SnarkParser<out R : Any> : IOReader<R> {
internal class SnarkParserWrapper<R : Any>( internal class SnarkParserWrapper<R : Any>(
val reader: IOReader<R>, val reader: IOReader<R>,
override val type: KType, override val type: KType,
override val contentType: ContentType,
override val fileExtensions: Set<String>, override val fileExtensions: Set<String>,
) : SnarkParser<R>, IOReader<R> by reader ) : SnarkParser<R>, IOReader<R> by reader
/**
* Create a generic parser from reader
*/
@Suppress("FunctionName") @Suppress("FunctionName")
inline fun <reified R : Any> SnarkParser( inline fun <reified R : Any> SnarkParser(
reader: IOReader<R>, reader: IOReader<R>,
contentType: ContentType,
vararg fileExtensions: String, vararg fileExtensions: String,
): SnarkParser<R> = SnarkParserWrapper(reader, typeOf<R>(), contentType, fileExtensions.toSet()) ): SnarkParser<R> = SnarkParserWrapper(reader, typeOf<R>(), fileExtensions.toSet())
@OptIn(DFExperimental::class) @OptIn(DFExperimental::class)
class SnarkPlugin : AbstractPlugin() { class SnarkPlugin : AbstractPlugin() {
@ -88,8 +89,11 @@ class SnarkPlugin : AbstractPlugin() {
SnarkParser.TYPE -> mapOf( SnarkParser.TYPE -> mapOf(
"html".asName() to SnarkHtmlParser, "html".asName() to SnarkHtmlParser,
"markdown".asName() to SnarkMarkdownParser, "markdown".asName() to SnarkMarkdownParser,
"json".asName() to SnarkParser(JsonMetaFormat, ContentType.Application.Json, "json"), "json".asName() to SnarkParser(JsonMetaFormat, "json"),
"yaml".asName() to SnarkParser(YamlMetaFormat, ContentType.Application.Json, "yaml", "yml") "yaml".asName() to SnarkParser(YamlMetaFormat, "yaml", "yml"),
"png".asName() to SnarkParser(ImageIOReader, "png"),
"jpg".asName() to SnarkParser(ImageIOReader, "jpg", "jpeg"),
"gif".asName() to SnarkParser(ImageIOReader, "jpg", "jpeg"),
) )
else -> super.content(target) else -> super.content(target)
} }

View File

@ -1,34 +1,53 @@
package space.kscience.snark package space.kscience.snark
import io.ktor.http.ContentType import io.ktor.util.asStream
import io.ktor.utils.io.core.Input import io.ktor.utils.io.core.Input
import kotlinx.html.div import kotlinx.html.div
import kotlinx.html.unsafe import kotlinx.html.unsafe
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
import org.intellij.markdown.html.HtmlGenerator import org.intellij.markdown.html.HtmlGenerator
import org.intellij.markdown.parser.MarkdownParser import org.intellij.markdown.parser.MarkdownParser
import space.kscience.dataforge.io.IOReader
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import kotlin.reflect.KType import kotlin.reflect.KType
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
object SnarkMarkdownParser:SnarkParser<HtmlFragment> { internal object SnarkHtmlParser : SnarkParser<HtmlFragment> {
override val contentType: ContentType = ContentType.Text.Html override val fileExtensions: Set<String> = setOf("html")
override val type: KType = typeOf<HtmlFragment>()
override fun readObject(input: Input): HtmlFragment = {
div {
unsafe { +input.readText() }
}
}
}
internal object SnarkMarkdownParser : SnarkParser<HtmlFragment> {
override val fileExtensions: Set<String> = setOf("markdown", "mdown", "mkdn", "mkd", "md") override val fileExtensions: Set<String> = setOf("markdown", "mdown", "mkdn", "mkd", "md")
override val type: KType = typeOf<HtmlFragment>() override val type: KType = typeOf<HtmlFragment>()
private val markdownFlavor = CommonMarkFlavourDescriptor() private val markdownFlavor = CommonMarkFlavourDescriptor()
private val markdownParser = MarkdownParser(markdownFlavor) private val markdownParser = MarkdownParser(markdownFlavor)
override fun readObject(input: Input): HtmlFragment { override fun readObject(input: Input): HtmlFragment {
val src = input.readText() val src = input.readText()
val parsedTree = markdownParser.buildMarkdownTreeFromString(src) val parsedTree = markdownParser.buildMarkdownTreeFromString(src)
val htmlString = HtmlGenerator(src, parsedTree, markdownFlavor).generateHtml() val htmlString = HtmlGenerator(src, parsedTree, markdownFlavor).generateHtml()
return { return {
div{ div {
unsafe { unsafe {
+htmlString +htmlString
} }
} }
} }
} }
} }
internal object ImageIOReader : IOReader<BufferedImage> {
override val type: KType get() = typeOf<BufferedImage>()
override fun readObject(input: Input): BufferedImage = ImageIO.read(input.asStream())
}