forked from SPC/spc-site
Separating from KTor
This commit is contained in:
parent
e9f71cdab9
commit
a907c57134
@ -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
|
@ -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) {
|
||||||
|
@ -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")
|
||||||
|
@ -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),
|
||||||
|
61
src/main/kotlin/space/kscience/snark/RouteBuilder.kt
Normal file
61
src/main/kotlin/space/kscience/snark/RouteBuilder.kt
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
@ -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() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,43 @@
|
|||||||
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
|
||||||
}
|
}
|
||||||
@ -32,3 +45,9 @@ object SnarkMarkdownParser:SnarkParser<HtmlFragment> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal object ImageIOReader : IOReader<BufferedImage> {
|
||||||
|
override val type: KType get() = typeOf<BufferedImage>()
|
||||||
|
|
||||||
|
override fun readObject(input: Input): BufferedImage = ImageIO.read(input.asStream())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user