Add FTL processor

This commit is contained in:
Alexander Nozik 2024-04-28 15:39:56 +03:00
parent b84cd79508
commit f70f1417a8
16 changed files with 286 additions and 69 deletions

View File

@ -1,20 +1,30 @@
---
type: markdown
order: 1
---
# Chapter ${section} # ${documentName}
${documentMeta.metaValue}
${documentMeta.get('metaValue')}
## Chapter ${section(1)}
Curabitur hendrerit hendrerit rutrum. Nullam elementum libero a nisi viverra aliquet. Sed ut urna a sem bibendum dictum. Cras non elit sit amet ex ultrices iaculis. Fusce lobortis lacinia fermentum. Fusce in metus id massa mollis consequat. Quisque non dolor quis orci gravida vulputate. Vivamus sed pellentesque orci. Sed aliquet malesuada rhoncus. Mauris id aliquet lorem. Curabitur hendrerit hendrerit rutrum. Nullam elementum libero a nisi viverra aliquet. Sed ut urna a sem bibendum dictum. Cras non elit sit amet ex ultrices iaculis. Fusce lobortis lacinia fermentum. Fusce in metus id massa mollis consequat. Quisque non dolor quis orci gravida vulputate. Vivamus sed pellentesque orci. Sed aliquet malesuada rhoncus. Mauris id aliquet lorem.
## Section ${section(2)} ### Section ${section(2)}
Maecenas at iaculis ipsum. Praesent maximus tristique magna eu faucibus. In tincidunt elementum pharetra. Nam scelerisque eros mattis, suscipit odio sit amet, efficitur mi. Etiam eleifend pulvinar erat a aliquet. Cras pellentesque tincidunt mi eget scelerisque. Proin eget ipsum a velit lobortis commodo. Nulla facilisi. Donec id pretium leo. Ut nec tortor sapien. Praesent vehicula dolor ut laoreet commodo. Pellentesque convallis, sapien et placerat luctus, tortor magna sodales sem, non tristique eros sem vel ipsum. Nulla vulputate accumsan nulla. Duis tempor, mi nec pharetra suscipit, sem odio sagittis mi, ut dignissim odio erat a dolor. Maecenas at iaculis ipsum. Praesent maximus tristique magna eu faucibus. In tincidunt elementum pharetra. Nam scelerisque eros mattis, suscipit odio sit amet, efficitur mi. Etiam eleifend pulvinar erat a aliquet. Cras pellentesque tincidunt mi eget scelerisque. Proin eget ipsum a velit lobortis commodo. Nulla facilisi. Donec id pretium leo. Ut nec tortor sapien. Praesent vehicula dolor ut laoreet commodo. Pellentesque convallis, sapien et placerat luctus, tortor magna sodales sem, non tristique eros sem vel ipsum. Nulla vulputate accumsan nulla. Duis tempor, mi nec pharetra suscipit, sem odio sagittis mi, ut dignissim odio erat a dolor.
## Section ${section(2)} ### Section ${section(2)}
In a quam nec turpis venenatis vehicula at ut lorem. Vestibulum tincidunt at velit laoreet sodales. Fusce fermentum enim sed lacinia fringilla. Nam et augue vitae felis sagittis consectetur in eget mauris. Fusce eget auctor turpis. Quisque at tristique nibh, id fringilla arcu. Cras sed finibus sapien. In a quam nec turpis venenatis vehicula at ut lorem. Vestibulum tincidunt at velit laoreet sodales. Fusce fermentum enim sed lacinia fringilla. Nam et augue vitae felis sagittis consectetur in eget mauris. Fusce eget auctor turpis. Quisque at tristique nibh, id fringilla arcu. Cras sed finibus sapien.
## Section ${section(2)} ### Section ${section(2)}
Praesent ullamcorper volutpat facilisis. Quisque posuere nisi sed nisl tempor, et sollicitudin justo imperdiet. Donec interdum auctor dui, quis dapibus lorem ullamcorper et. Sed aliquam, augue a tempus viverra, felis nulla convallis magna, a lacinia orci nisi id mauris. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut congue cursus quam, in vehicula nibh laoreet vel. Vivamus tincidunt sit amet dui quis mollis. Aliquam sed bibendum erat. Vivamus eget ante sed sapien volutpat luctus. Praesent ullamcorper volutpat facilisis. Quisque posuere nisi sed nisl tempor, et sollicitudin justo imperdiet. Donec interdum auctor dui, quis dapibus lorem ullamcorper et. Sed aliquam, augue a tempus viverra, felis nulla convallis magna, a lacinia orci nisi id mauris. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Ut congue cursus quam, in vehicula nibh laoreet vel. Vivamus tincidunt sit amet dui quis mollis. Aliquam sed bibendum erat. Vivamus eget ante sed sapien volutpat luctus.
## Section ${section(2)} ### Section ${section(2)}
Donec auctor quis libero eu cursus. Ut molestie varius massa, quis eleifend purus. Quisque elementum, magna id sollicitudin ullamcorper, arcu orci ullamcorper felis, ac tempus massa nisi vitae elit. In mollis porttitor orci. Integer gravida massa ut massa imperdiet, quis maximus libero varius. Phasellus vitae quam commodo, aliquet quam sed, euismod velit. Cras nibh ante, sodales non nulla sed, fringilla posuere orci. Donec auctor quis libero eu cursus. Ut molestie varius massa, quis eleifend purus. Quisque elementum, magna id sollicitudin ullamcorper, arcu orci ullamcorper felis, ac tempus massa nisi vitae elit. In mollis porttitor orci. Integer gravida massa ut massa imperdiet, quis maximus libero varius. Phasellus vitae quam commodo, aliquet quam sed, euismod velit. Cras nibh ante, sodales non nulla sed, fringilla posuere orci.

View File

@ -1,5 +1,9 @@
---
type: markdown
order: 2
---
# Chapter ${section} ## Chapter ${section(1)}
Fusce at tristique ex. Proin vehicula venenatis mattis. Fusce at congue sapien, sed interdum lacus. Vivamus scelerisque ligula pretium nisl accumsan, molestie commodo sem condimentum. Nam ullamcorper leo quis sapien commodo, feugiat pellentesque purus rhoncus. Cras feugiat, lorem sit amet sodales aliquet, ante ipsum aliquam felis, ac rhoncus risus felis non enim. Suspendisse bibendum ornare efficitur. Nam tortor dolor, imperdiet nec orci et, pellentesque elementum sem. Integer sapien urna, rhoncus et felis et, fringilla euismod elit. Quisque tellus quam, tincidunt sed velit at, aliquam mollis leo. Integer pellentesque leo in libero pretium pharetra vitae sed ipsum. Mauris auctor venenatis pharetra. Maecenas tincidunt nulla ullamcorper, faucibus ante id, bibendum turpis. In lacus risus, pretium vel accumsan non, rhoncus nec augue. Suspendisse potenti. Fusce at tristique ex. Proin vehicula venenatis mattis. Fusce at congue sapien, sed interdum lacus. Vivamus scelerisque ligula pretium nisl accumsan, molestie commodo sem condimentum. Nam ullamcorper leo quis sapien commodo, feugiat pellentesque purus rhoncus. Cras feugiat, lorem sit amet sodales aliquet, ante ipsum aliquam felis, ac rhoncus risus felis non enim. Suspendisse bibendum ornare efficitur. Nam tortor dolor, imperdiet nec orci et, pellentesque elementum sem. Integer sapien urna, rhoncus et felis et, fringilla euismod elit. Quisque tellus quam, tincidunt sed velit at, aliquam mollis leo. Integer pellentesque leo in libero pretium pharetra vitae sed ipsum. Mauris auctor venenatis pharetra. Maecenas tincidunt nulla ullamcorper, faucibus ante id, bibendum turpis. In lacus risus, pretium vel accumsan non, rhoncus nec augue. Suspendisse potenti.
@ -10,3 +14,10 @@ Integer et metus metus. Donec fringilla nec sem sit amet bibendum. Sed ornare lo
Cras finibus vel leo id mattis. Nulla tellus augue, bibendum in ipsum vitae, aliquet convallis nisl. Sed auctor urna sit amet ante pulvinar, sit amet venenatis nibh porta. Cras vitae ultrices nisi. Vestibulum eu sapien eu nulla rhoncus porttitor ut vitae odio. Curabitur scelerisque hendrerit elit vitae laoreet. Etiam eget accumsan nibh, non vehicula ex. Cras finibus vel leo id mattis. Nulla tellus augue, bibendum in ipsum vitae, aliquet convallis nisl. Sed auctor urna sit amet ante pulvinar, sit amet venenatis nibh porta. Cras vitae ultrices nisi. Vestibulum eu sapien eu nulla rhoncus porttitor ut vitae odio. Curabitur scelerisque hendrerit elit vitae laoreet. Etiam eget accumsan nibh, non vehicula ex.
Quisque ut ultricies nisi, eget vehicula ipsum. Quisque tortor mauris, sagittis vitae consectetur in, fermentum quis dui. Maecenas nec risus eu ipsum eleifend ornare. Nam tempus interdum mi, eget tincidunt enim interdum et. Nam a ultricies libero. Cras vehicula, quam a egestas semper, ipsum est cursus nunc, ac mollis est velit at enim. Sed nec nibh ut leo fermentum interdum. Donec aliquam elementum metus, non fermentum eros bibendum eu. Suspendisse ut odio vel dolor blandit condimentum. Vivamus malesuada accumsan magna, a suscipit tellus vehicula ut. Duis et orci arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Quisque ut ultricies nisi, eget vehicula ipsum. Quisque tortor mauris, sagittis vitae consectetur in, fermentum quis dui. Maecenas nec risus eu ipsum eleifend ornare. Nam tempus interdum mi, eget tincidunt enim interdum et. Nam a ultricies libero. Cras vehicula, quam a egestas semper, ipsum est cursus nunc, ac mollis est velit at enim. Sed nec nibh ut leo fermentum interdum. Donec aliquam elementum metus, non fermentum eros bibendum eu. Suspendisse ut odio vel dolor blandit condimentum. Vivamus malesuada accumsan magna, a suscipit tellus vehicula ut. Duis et orci arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae;
```snark
type: image
path: myImage.png
caption: Whatever
index: ${label(image)}
```

View File

@ -1,5 +1,9 @@
---
type: markdown
order: 3
---
# Chapter ${section} ## Chapter ${section(1)}
Vestibulum mauris urna, sagittis in nibh placerat, vehicula suscipit leo. Maecenas lacinia varius tellus vel laoreet. Vestibulum sollicitudin nibh et nibh ullamcorper, at pretium orci molestie. Donec vulputate, nibh tempus maximus pretium, urna arcu consequat metus, vel ullamcorper est lectus id purus. Aliquam rutrum eu arcu nec convallis. Cras nec nisl ut massa tempus fringilla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam non augue vel nisi commodo blandit a a augue. Sed aliquet semper ipsum nec maximus. Aliquam elementum tellus eu lectus tempus eleifend. Donec sapien nisi, maximus vitae ante nec, dictum vestibulum justo. Nam at ex est. Pellentesque nisi lacus, congue non felis posuere, pulvinar cursus justo. Fusce tincidunt dui vel pharetra fringilla. Vestibulum mauris urna, sagittis in nibh placerat, vehicula suscipit leo. Maecenas lacinia varius tellus vel laoreet. Vestibulum sollicitudin nibh et nibh ullamcorper, at pretium orci molestie. Donec vulputate, nibh tempus maximus pretium, urna arcu consequat metus, vel ullamcorper est lectus id purus. Aliquam rutrum eu arcu nec convallis. Cras nec nisl ut massa tempus fringilla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam non augue vel nisi commodo blandit a a augue. Sed aliquet semper ipsum nec maximus. Aliquam elementum tellus eu lectus tempus eleifend. Donec sapien nisi, maximus vitae ante nec, dictum vestibulum justo. Nam at ex est. Pellentesque nisi lacus, congue non felis posuere, pulvinar cursus justo. Fusce tincidunt dui vel pharetra fringilla.

View File

@ -1,51 +1,24 @@
import io.ktor.server.application.Application import io.ktor.server.application.Application
import io.ktor.server.cio.CIO import io.ktor.server.cio.CIO
import io.ktor.server.engine.embeddedServer import io.ktor.server.engine.embeddedServer
import io.ktor.server.routing.routing import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.request
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.workspace.directory
import space.kscience.snark.html.SnarkHtml
import space.kscience.snark.html.document.document import space.kscience.snark.html.document.document
import space.kscience.snark.html.document.fragment import space.kscience.snark.html.document.fragment
import space.kscience.snark.html.readSiteData
import space.kscience.snark.ktor.site
import kotlin.io.path.Path
import kotlin.io.path.exists
@Suppress("unused") @Suppress("unused")
fun Application.documents(context: Context) { fun Application.documents() = snarkApplication {
val snark = context.request(SnarkHtml) document("loremIpsum".asName(), Meta { "metaValue" put "Hello world!" }) {
val dataDirectory = Path("data")
if(!dataDirectory.exists()){
error("Data directory at $dataDirectory is not resolved")
}
val siteData = snark.readSiteData(context) {
directory(snark.io, Name.EMPTY, dataDirectory)
}
routing {
site(context, siteData){
document("loremIpsum".asName()){
fragment("chapter1") fragment("chapter1")
fragment("chapter2") fragment("chapter2")
fragment("chapter3") fragment("chapter3")
} }
} }
}
}
fun main() { fun main() {
val context = Context {
plugin(SnarkHtml)
}
embeddedServer(CIO) { embeddedServer(CIO) {
documents(context) documents()
}.start(true) }.start(true)
} }

View File

@ -0,0 +1,47 @@
import io.ktor.server.application.Application
import io.ktor.server.application.log
import io.ktor.server.routing.routing
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextBuilder
import space.kscience.dataforge.context.request
import space.kscience.dataforge.data.forEach
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.workspace.FileData
import space.kscience.dataforge.workspace.directory
import space.kscience.snark.html.HtmlSite
import space.kscience.snark.html.SnarkHtml
import space.kscience.snark.html.readSiteData
import space.kscience.snark.ktor.site
import kotlin.io.path.Path
import kotlin.io.path.exists
fun Application.snarkApplication(contextBuilder: ContextBuilder.() -> Unit = {}, site: HtmlSite) {
val context = Context {
plugin(SnarkHtml)
contextBuilder()
}
val snark = context.request(SnarkHtml)
val dataDirectoryString = environment.config.propertyOrNull("snark.dataDirectory")?.getString() ?: "data"
val dataDirectory = Path(dataDirectoryString)
if (!dataDirectory.exists()) {
error("Data directory at $dataDirectory is not resolved")
}
val siteData = snark.readSiteData(context) {
directory(snark.io, Name.EMPTY, dataDirectory)
}
siteData.forEach { namedData ->
log.debug("Loading data {} from {}", namedData.name, namedData.meta[FileData.FILE_PATH_KEY])
}
routing {
site(context, siteData, content = site)
}
}

View File

@ -17,7 +17,9 @@ kscience{
api("io.ktor:ktor-http:$ktorVersion") api("io.ktor:ktor-http:$ktorVersion")
api("space.kscience:dataforge-io-yaml:$dataforgeVersion") api("space.kscience:dataforge-io-yaml:$dataforgeVersion")
api("org.jetbrains:markdown:0.6.1") api("org.jetbrains:markdown:0.7.0")
api("org.freemarker:freemarker:2.3.32")
} }
} }

View File

@ -7,6 +7,7 @@ import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.ast.ASTNode import org.intellij.markdown.ast.ASTNode
import org.intellij.markdown.ast.findChildOfType import org.intellij.markdown.ast.findChildOfType
import org.intellij.markdown.ast.getTextInNode import org.intellij.markdown.ast.getTextInNode
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
import org.intellij.markdown.flavours.space.SFMFlavourDescriptor import org.intellij.markdown.flavours.space.SFMFlavourDescriptor
import org.intellij.markdown.html.* import org.intellij.markdown.html.*
import org.intellij.markdown.parser.LinkMap import org.intellij.markdown.parser.LinkMap
@ -59,16 +60,15 @@ private class SnarkImageGeneratingProvider(
} }
} }
public object SnarkFlavorDescriptor : SFMFlavourDescriptor(false) { public object SnarkFlavorDescriptor : GFMFlavourDescriptor(false) {
override fun createHtmlGeneratingProviders(linkMap: LinkMap, baseURI: URI?): Map<IElementType, GeneratingProvider> { override fun createHtmlGeneratingProviders(linkMap: LinkMap, baseURI: URI?): Map<IElementType, GeneratingProvider> =
return super.createHtmlGeneratingProviders(linkMap, baseURI) + mapOf( super.createHtmlGeneratingProviders(linkMap, baseURI) + mapOf(
MarkdownElementTypes.INLINE_LINK to SnarkInlineLinkGeneratingProvider(baseURI, absolutizeAnchorLinks) MarkdownElementTypes.INLINE_LINK to SnarkInlineLinkGeneratingProvider(baseURI, absolutizeAnchorLinks)
.makeXssSafe(useSafeLinks), .makeXssSafe(useSafeLinks),
MarkdownElementTypes.IMAGE to SnarkImageGeneratingProvider(linkMap, baseURI) MarkdownElementTypes.IMAGE to SnarkImageGeneratingProvider(linkMap, baseURI)
.makeXssSafe(useSafeLinks), .makeXssSafe(useSafeLinks),
) )
} }
}
public object MarkdownReader : SnarkHtmlReader { public object MarkdownReader : SnarkHtmlReader {
override val types: Set<String> = setOf("text/markdown", "md", "markdown") override val types: Set<String> = setOf("text/markdown", "md", "markdown")

View File

@ -9,7 +9,7 @@ import space.kscience.dataforge.meta.copy
private class MetaMaskData<T>(val origin: Data<T>, override val meta: Meta) : Data<T> by origin private class MetaMaskData<T>(val origin: Data<T>, override val meta: Meta) : Data<T> by origin
/** /**
* A data with overriden meta. It reflects original data computed state. * Data with overridden meta. It reflects original data computed state.
*/ */
public fun <T> Data<T>.withMeta(newMeta: Meta): Data<T> = if (this is MetaMaskData) { public fun <T> Data<T>.withMeta(newMeta: Meta): Data<T> = if (this is MetaMaskData) {
MetaMaskData(origin, newMeta) MetaMaskData(origin, newMeta)

View File

@ -3,11 +3,16 @@ package space.kscience.snark.html
import io.ktor.http.URLBuilder import io.ktor.http.URLBuilder
import io.ktor.http.Url import io.ktor.http.Url
import io.ktor.http.appendPathSegments import io.ktor.http.appendPathSegments
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.data.DataTree
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.hasIndex
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.plus
import space.kscience.snark.SnarkBuilder import space.kscience.snark.SnarkBuilder
import space.kscience.snark.SnarkContext import space.kscience.snark.SnarkContext
@ -23,10 +28,13 @@ public fun Name.toWebPath(): String = tokens.joinToString(separator = "/") {
* A context for building a single page * A context for building a single page
*/ */
@SnarkBuilder @SnarkBuilder
public interface PageContext : SnarkContext { public interface PageContext : SnarkContext, ContextAware {
public val site: SiteContext public val site: SiteContext
override val context: Context get() = site.context
public val host: Url public val host: Url
/** /**

View File

@ -1,5 +1,7 @@
package space.kscience.snark.html package space.kscience.snark.html
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.data.* import space.kscience.dataforge.data.*
import space.kscience.dataforge.io.Binary import space.kscience.dataforge.io.Binary
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
@ -14,7 +16,7 @@ import space.kscience.snark.SnarkContext
* An abstraction, which is used to render sites to the different rendering engines * An abstraction, which is used to render sites to the different rendering engines
*/ */
@SnarkBuilder @SnarkBuilder
public interface SiteContext : SnarkContext { public interface SiteContext : SnarkContext, ContextAware {
public val parent: SiteContext? public val parent: SiteContext?

View File

@ -18,6 +18,7 @@ import space.kscience.dataforge.io.JsonMetaFormat
import space.kscience.dataforge.io.yaml.YamlMetaFormat import space.kscience.dataforge.io.yaml.YamlMetaFormat
import space.kscience.dataforge.io.yaml.YamlPlugin import space.kscience.dataforge.io.yaml.YamlPlugin
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.set import space.kscience.dataforge.meta.set
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
@ -28,6 +29,7 @@ import space.kscience.dataforge.workspace.*
import space.kscience.snark.ReWrapAction import space.kscience.snark.ReWrapAction
import space.kscience.snark.Snark import space.kscience.snark.Snark
import space.kscience.snark.SnarkReader import space.kscience.snark.SnarkReader
import space.kscience.snark.TextProcessor
import java.net.URLConnection import java.net.URLConnection
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.extension import kotlin.io.path.extension
@ -62,12 +64,31 @@ public class SnarkHtml : WorkspacePlugin() {
URLConnection.guessContentTypeFromName(filePath) ?: Path(filePath).extension URLConnection.guessContentTypeFromName(filePath) ?: Path(filePath).extension
} }
public val prepareHeaderAction: ReWrapAction<Any> = ReWrapAction.removeExtensions<Any>("html", "md") { name -> public val prepareHeaderAction: ReWrapAction<Any> = ReWrapAction.removeExtensions<Any>("html", "md") { name ->
val contentType = getContentType(name, this) val contentType = getContentType(name, this)
set(CONTENT_TYPE_KEY, contentType) set(CONTENT_TYPE_KEY, contentType)
} }
public fun parse(name: Name, markup: String, meta: Meta): PageFragment {
val contentType = getContentType(name, meta)
val parser = snark.readers.values.filterIsInstance<SnarkHtmlReader>().filter { parser ->
contentType in parser.types
}.maxByOrNull {
it.priority
} ?: error("Parser for name $name and meta $meta not found")
//ignore data for which parser is not found
val preprocessor = meta[TextProcessor.TEXT_PREPROCESSOR_KEY]?.let { snark.preprocessor(it) }
return if (preprocessor == null) {
parser.readFrom(markup)
} else {
parser.readFrom(preprocessor.process(markup))
}
}
public val removeIndexAction: ReWrapAction<Any> = ReWrapAction.removeIndex<Any>() public val removeIndexAction: ReWrapAction<Any> = ReWrapAction.removeIndex<Any>()
public val parseAction: Action<Binary, PageFragment> = ParseAction(this) public val parseAction: Action<Binary, PageFragment> = ParseAction(this)

View File

@ -4,7 +4,9 @@ import kotlinx.coroutines.runBlocking
import kotlinx.html.body import kotlinx.html.body
import kotlinx.html.head import kotlinx.html.head
import kotlinx.html.title import kotlinx.html.title
import space.kscience.dataforge.context.request
import space.kscience.dataforge.data.* import space.kscience.dataforge.data.*
import space.kscience.dataforge.meta.Laminate
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string
@ -24,9 +26,9 @@ public interface DocumentBuilder : SnarkContext {
public val documentMeta: Meta public val documentMeta: Meta
public val data: DataTree<*> public val documentData: DataTree<*>
public suspend fun fragment(fragment: Data<*>) public suspend fun fragment(fragment: Data<*>, overrideMeta: Meta? = null)
public suspend fun fragment(fragment: DocumentFragment, overrideMeta: Meta? = null) public suspend fun fragment(fragment: DocumentFragment, overrideMeta: Meta? = null)
} }
@ -44,19 +46,50 @@ public suspend fun DocumentBuilder.fragment(fragmentName: String) {
private class PageBasedDocumentBuilder(val page: PageContextWithData) : DocumentBuilder { private class PageBasedDocumentBuilder(val page: PageContextWithData) : DocumentBuilder {
override val documentName: Name get() = page.pageRoute override val documentName: Name get() = page.pageRoute
override val documentMeta: Meta get() = page.pageMeta override val documentMeta: Meta get() = page.pageMeta
override val data: DataTree<*> get() = page.data override val documentData: DataTree<*> get() = page.data
val fragments = mutableListOf<PageFragment>() val fragments = mutableListOf<PageFragment>()
override suspend fun fragment(fragment: DocumentFragment, overrideMeta: Meta?) { fun fragment(pageFragment: PageFragment) {
TODO("Not yet implemented") fragments.add(pageFragment)
} }
override suspend fun fragment(fragment: Data<*>) { override suspend fun fragment(fragment: DocumentFragment, overrideMeta: Meta?) {
when (fragment) {
is ListDocumentFragment -> {
val meta = Laminate(overrideMeta, fragment.meta)
fragment.fragments.forEach { fragment(it, meta) }
}
is ImageDocumentFragment -> TODO()
is MarkupDocumentFragment -> {
val snarkHtml = page.context.request(SnarkHtml)
TODO()
}
is DataDocumentFragment -> {
val data = documentData[fragment.dataName]
?: error("Can't find data with name ${fragment.dataName} for $fragment")
fragment(data)
}
is LayoutDocumentFragment -> TODO()
}
}
override suspend fun fragment(fragment: Data<*>, overrideMeta: Meta?) {
when (fragment.type) { when (fragment.type) {
typeOf<PageFragment>() -> fragments.add(fragment.await() as PageFragment) typeOf<PageFragment>() -> fragment(fragment.await() as PageFragment)
typeOf<DocumentFragment>() -> fragment(fragment.await() as DocumentFragment, data.meta) typeOf<DocumentFragment>() -> fragment(
typeOf<String>() -> fragment(TextDocumentFragment(fragment.await() as String, fragment.meta)) fragment.await() as DocumentFragment,
Laminate(overrideMeta, documentData.meta)
)
typeOf<String>() -> fragment(
MarkupDocumentFragment(fragment.await() as String, fragment.meta),
overrideMeta
)
else -> error("Unsupported data type: ${fragment.type}") else -> error("Unsupported data type: ${fragment.type}")
} }
} }
@ -72,7 +105,7 @@ public fun SiteContextWithData.document(
title(documentMeta["title"].string ?: "Snark document") title(documentMeta["title"].string ?: "Snark document")
} }
body { body {
postprocess(DocumentTextProcessor(documentBuilder)) { postprocess(FtlDocumentProcessor(this@document.context, documentBuilder)) {
documentBuilder.fragments.forEach { documentBuilder.fragments.forEach {
fragment(it) fragment(it)
} }

View File

@ -2,13 +2,18 @@ package space.kscience.snark.html.document
import kotlinx.io.files.Path import kotlinx.io.files.Path
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.Name
public sealed interface DocumentFragment{ public sealed interface DocumentFragment{
public val meta: Meta public val meta: Meta
} }
public class TextDocumentFragment(public val text: String, override val meta: Meta) : DocumentFragment public class MarkupDocumentFragment(public val text: String, override val meta: Meta) : DocumentFragment
public class ImageDocumentFragment(public val image: Path, override val meta: Meta) : DocumentFragment public class ImageDocumentFragment(public val image: Path, override val meta: Meta) : DocumentFragment
public class CompositeDocumentFragment(public val fragments: List<DocumentFragment>, override val meta: Meta) : DocumentFragment public class DataDocumentFragment(public val dataName: Name, override val meta: Meta) : DocumentFragment
public class ListDocumentFragment(public val fragments: List<DocumentFragment>, override val meta: Meta) : DocumentFragment
public class LayoutDocumentFragment(public val fragments: Map<String,DocumentFragment>, override val meta: Meta) : DocumentFragment

View File

@ -0,0 +1,98 @@
package space.kscience.snark.html.document
import freemarker.template.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.context.error
import space.kscience.dataforge.context.logger
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.meta.toMap
import space.kscience.dataforge.names.*
import space.kscience.snark.TextProcessor
import java.io.StringReader
import java.io.StringWriter
public class FtlDocumentProcessor(
override val context: Context,
private val document: DocumentBuilder,
counters: Map<String, Int> = emptyMap(),
) : TextProcessor, ContextAware {
private val counters = counters.toMutableMap()
private fun getCounter(counter: String): Int = counters[counter] ?: 1
private fun getAndIncrementCounter(counter: String): Int {
val currentCounter = counters[counter] ?: document.documentMeta[NameToken("counter", counter).asName()].int ?: 1
counters[counter] = currentCounter + 1
return currentCounter
}
private fun resetAndGet(counter: String, value: Int = 1): Int {
counters[counter] = value
return value
}
private fun ref(counter: String, value: Int): String {
//TODO replace by name parse in future
val token = Name.parse(counter).last()
return when (token.body) {
"section" -> {
val level = token.index?.toIntOrNull() ?: 1
(1..level).joinToString(separator = "_") {
if (it == level) value.toString() else getCounter("section[$it]").toString()
}
}
else -> "snark_counter_${counter}_$value"
}
}
private val ftlConfig = Configuration(Configuration.VERSION_2_3_32).apply {
defaultEncoding = "UTF-8"
templateExceptionHandler = TemplateExceptionHandler { te: TemplateException, env, out ->
logger.error(te) { "An exception while rendering template" }
}
}
private val data = mapOf(
"documentName" to document.documentName.toStringUnescaped(),
"label" to TemplateMethodModelEx { args: List<Any?> ->
val counter = args.getOrNull(0)?.toString() ?: "@default"
val value = getAndIncrementCounter(counter)
val ref = ref(counter, value)
//language=HTML
"""<a class="snark-label" id="$ref" href="#$ref">$value</a>"""
},
"section" to TemplateMethodModelEx { args: List<Any?> ->
val level: Int = args.getOrNull(0)?.toString()?.toIntOrNull() ?: 1
val counter = getAndIncrementCounter("section[$level]")
val ref = ref("section[$level]", counter)
//language=HTML
"""<a class="snark-section snark-label" id="$ref" href = "#$ref">$counter</a>"""
},
"documentMeta" to document.documentMeta.toMap().let {
it.plus(
"get" to TemplateMethodModelEx { args: List<Any?> ->
val nameString = args.getOrNull(0)?.toString() ?: ""
document.documentMeta[nameString.parseAsName()].string ?: "@null"
}
)
}
)
override fun process(text: CharSequence): String {
val template = Template("fragment", StringReader(text.toString()), ftlConfig)
return StringWriter().also {
template.process(data, it)
}.toString()
}
}

View File

@ -6,7 +6,7 @@ import space.kscience.dataforge.names.*
import space.kscience.snark.TextProcessor import space.kscience.snark.TextProcessor
import java.net.URI import java.net.URI
public class DocumentTextProcessor(public val document: DocumentBuilder) : TextProcessor { public class RegexDocumentProcessor(public val document: DocumentBuilder) : TextProcessor {
private val counters = mutableMapOf<String, Int>() private val counters = mutableMapOf<String, Int>()
@ -67,7 +67,7 @@ public class DocumentTextProcessor(public val document: DocumentBuilder) : TextP
} }
"documentMeta.get" -> { "documentMeta.get" -> {
val nameString = match.groups["name"]?.value val nameString = match.groups["arg1"]?.value
?: error("resolvePageRef requires a string (quoted) argument") ?: error("resolvePageRef requires a string (quoted) argument")
document.documentMeta[nameString.parseAsName()].string ?: "@null" document.documentMeta[nameString.parseAsName()].string ?: "@null"
} }

View File

@ -5,6 +5,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.io.asSink import kotlinx.io.asSink
import kotlinx.io.buffered import kotlinx.io.buffered
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.data.* import space.kscience.dataforge.data.*
import space.kscience.dataforge.io.Binary import space.kscience.dataforge.io.Binary
import space.kscience.dataforge.io.writeBinary import space.kscience.dataforge.io.writeBinary
@ -22,6 +23,7 @@ import kotlin.reflect.typeOf
* An implementation of [SiteContext] to render site as a static directory [outputPath] * An implementation of [SiteContext] to render site as a static directory [outputPath]
*/ */
internal class StaticSiteContext( internal class StaticSiteContext(
override val context: Context,
override val siteMeta: Meta, override val siteMeta: Meta,
private val baseUrl: Url, private val baseUrl: Url,
override val path: List<String>, override val path: List<String>,
@ -127,6 +129,7 @@ internal class StaticSiteContext(
override fun route(route: Name, data: DataTree<*>?, siteMeta: Meta, content: HtmlSite) { override fun route(route: Name, data: DataTree<*>?, siteMeta: Meta, content: HtmlSite) {
val siteContextWithData = SiteContextWithData( val siteContextWithData = SiteContextWithData(
StaticSiteContext( StaticSiteContext(
context = context,
siteMeta = Laminate(siteMeta, this@StaticSiteContext.siteMeta), siteMeta = Laminate(siteMeta, this@StaticSiteContext.siteMeta),
baseUrl = baseUrl, baseUrl = baseUrl,
path = emptyList(), path = emptyList(),
@ -146,6 +149,7 @@ internal class StaticSiteContext(
override fun site(route: Name, data: DataTree<*>?, siteMeta: Meta, content: HtmlSite) { override fun site(route: Name, data: DataTree<*>?, siteMeta: Meta, content: HtmlSite) {
val siteContextWithData = SiteContextWithData( val siteContextWithData = SiteContextWithData(
StaticSiteContext( StaticSiteContext(
context = context,
siteMeta = Laminate(siteMeta, this@StaticSiteContext.siteMeta), siteMeta = Laminate(siteMeta, this@StaticSiteContext.siteMeta),
baseUrl = baseUrl, baseUrl = baseUrl,
path = emptyList(), path = emptyList(),
@ -165,11 +169,10 @@ internal class StaticSiteContext(
} }
/** /**
* Create a static site using given [SnarkEnvironment] in provided [outputPath]. * Create a static site using given [content] in provided [outputPath].
* Use [siteUrl] as a base for all resolved URLs. By default, use [outputPath] absolute path as a base. * Use [siteUrl] as a base for all resolved URLs. By default, use [outputPath] absolute path as a base.
* *
*/ */
@Suppress("UnusedReceiverParameter")
public suspend fun SnarkHtml.staticSite( public suspend fun SnarkHtml.staticSite(
data: DataTree<*>?, data: DataTree<*>?,
outputPath: Path, outputPath: Path,
@ -178,7 +181,7 @@ public suspend fun SnarkHtml.staticSite(
content: HtmlSite, content: HtmlSite,
) { ) {
val siteContextWithData = SiteContextWithData( val siteContextWithData = SiteContextWithData(
StaticSiteContext(siteMeta, siteUrl, emptyList(), Name.EMPTY, null, outputPath), StaticSiteContext(context, siteMeta, siteUrl, emptyList(), Name.EMPTY, null, outputPath),
data ?: DataTree.EMPTY data ?: DataTree.EMPTY
) )
with(content) { with(content) {