Add FTL processor
This commit is contained in:
parent
b84cd79508
commit
f70f1417a8
@ -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.
|
@ -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)}
|
||||||
|
```
|
@ -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.
|
||||||
|
|
||||||
|
@ -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")
|
fragment("chapter1")
|
||||||
|
fragment("chapter2")
|
||||||
if(!dataDirectory.exists()){
|
fragment("chapter3")
|
||||||
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("chapter2")
|
|
||||||
fragment("chapter3")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
val context = Context {
|
|
||||||
plugin(SnarkHtml)
|
|
||||||
}
|
|
||||||
|
|
||||||
embeddedServer(CIO) {
|
embeddedServer(CIO) {
|
||||||
documents(context)
|
documents()
|
||||||
}.start(true)
|
}.start(true)
|
||||||
}
|
}
|
47
examples/document/src/jvmMain/kotlin/snarkApplication.kt
Normal file
47
examples/document/src/jvmMain/kotlin/snarkApplication.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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,15 +60,14 @@ 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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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?
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
||||||
}
|
}
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user