diff --git a/examples/document/data/chapter1.md b/examples/document/data/chapter1.md index 00c72ed..fb186b3 100644 --- a/examples/document/data/chapter1.md +++ b/examples/document/data/chapter1.md @@ -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. -## 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. -## 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. -## 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. -## 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. \ No newline at end of file diff --git a/examples/document/data/chapter2.md b/examples/document/data/chapter2.md index 7a4d259..07fedc5 100644 --- a/examples/document/data/chapter2.md +++ b/examples/document/data/chapter2.md @@ -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. @@ -9,4 +13,11 @@ 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. -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; \ No newline at end of file +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)} +``` \ No newline at end of file diff --git a/examples/document/data/chapter3.md b/examples/document/data/chapter3.md index a5f46d9..b61dafb 100644 --- a/examples/document/data/chapter3.md +++ b/examples/document/data/chapter3.md @@ -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. diff --git a/examples/document/src/jvmMain/kotlin/main.kt b/examples/document/src/jvmMain/kotlin/main.kt index 964ec9c..09cd4bd 100644 --- a/examples/document/src/jvmMain/kotlin/main.kt +++ b/examples/document/src/jvmMain/kotlin/main.kt @@ -1,51 +1,24 @@ import io.ktor.server.application.Application import io.ktor.server.cio.CIO import io.ktor.server.engine.embeddedServer -import io.ktor.server.routing.routing -import space.kscience.dataforge.context.Context -import space.kscience.dataforge.context.request -import space.kscience.dataforge.names.Name +import space.kscience.dataforge.meta.Meta 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.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") -fun Application.documents(context: Context) { - val snark = context.request(SnarkHtml) - 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("chapter2") - fragment("chapter3") - } - } +fun Application.documents() = snarkApplication { + document("loremIpsum".asName(), Meta { "metaValue" put "Hello world!" }) { + fragment("chapter1") + fragment("chapter2") + fragment("chapter3") } } fun main() { - val context = Context { - plugin(SnarkHtml) - } embeddedServer(CIO) { - documents(context) + documents() }.start(true) } \ No newline at end of file diff --git a/examples/document/src/jvmMain/kotlin/snarkApplication.kt b/examples/document/src/jvmMain/kotlin/snarkApplication.kt new file mode 100644 index 0000000..34790eb --- /dev/null +++ b/examples/document/src/jvmMain/kotlin/snarkApplication.kt @@ -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) + } +} \ No newline at end of file diff --git a/snark-html/build.gradle.kts b/snark-html/build.gradle.kts index 074b351..1749d91 100644 --- a/snark-html/build.gradle.kts +++ b/snark-html/build.gradle.kts @@ -17,7 +17,9 @@ kscience{ api("io.ktor:ktor-http:$ktorVersion") 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") + } } diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/MarkdownReader.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/MarkdownReader.kt index e58b727..4660e5c 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/MarkdownReader.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/MarkdownReader.kt @@ -7,6 +7,7 @@ import org.intellij.markdown.MarkdownElementTypes import org.intellij.markdown.ast.ASTNode import org.intellij.markdown.ast.findChildOfType import org.intellij.markdown.ast.getTextInNode +import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor import org.intellij.markdown.flavours.space.SFMFlavourDescriptor import org.intellij.markdown.html.* import org.intellij.markdown.parser.LinkMap @@ -59,15 +60,14 @@ private class SnarkImageGeneratingProvider( } } -public object SnarkFlavorDescriptor : SFMFlavourDescriptor(false) { - override fun createHtmlGeneratingProviders(linkMap: LinkMap, baseURI: URI?): Map { - return super.createHtmlGeneratingProviders(linkMap, baseURI) + mapOf( +public object SnarkFlavorDescriptor : GFMFlavourDescriptor(false) { + override fun createHtmlGeneratingProviders(linkMap: LinkMap, baseURI: URI?): Map = + super.createHtmlGeneratingProviders(linkMap, baseURI) + mapOf( MarkdownElementTypes.INLINE_LINK to SnarkInlineLinkGeneratingProvider(baseURI, absolutizeAnchorLinks) .makeXssSafe(useSafeLinks), MarkdownElementTypes.IMAGE to SnarkImageGeneratingProvider(linkMap, baseURI) .makeXssSafe(useSafeLinks), ) - } } public object MarkdownReader : SnarkHtmlReader { diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/MetaMaskData.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/MetaMaskData.kt index f5e65d3..673058f 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/MetaMaskData.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/MetaMaskData.kt @@ -9,7 +9,7 @@ import space.kscience.dataforge.meta.copy private class MetaMaskData(val origin: Data, override val meta: Meta) : Data 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 Data.withMeta(newMeta: Meta): Data = if (this is MetaMaskData) { MetaMaskData(origin, newMeta) diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/PageContext.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/PageContext.kt index e5789b2..e9d02af 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/PageContext.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/PageContext.kt @@ -3,11 +3,16 @@ package space.kscience.snark.html import io.ktor.http.URLBuilder import io.ktor.http.Url 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.meta.Meta import space.kscience.dataforge.meta.get 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.SnarkContext @@ -23,10 +28,13 @@ public fun Name.toWebPath(): String = tokens.joinToString(separator = "/") { * A context for building a single page */ @SnarkBuilder -public interface PageContext : SnarkContext { +public interface PageContext : SnarkContext, ContextAware { public val site: SiteContext + override val context: Context get() = site.context + + public val host: Url /** diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/SiteContext.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/SiteContext.kt index dab3889..87c8db7 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/SiteContext.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/SiteContext.kt @@ -1,5 +1,7 @@ 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.io.Binary 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 */ @SnarkBuilder -public interface SiteContext : SnarkContext { +public interface SiteContext : SnarkContext, ContextAware { public val parent: SiteContext? diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/SnarkHtml.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/SnarkHtml.kt index 5ab561a..94d3793 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/SnarkHtml.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/SnarkHtml.kt @@ -18,6 +18,7 @@ import space.kscience.dataforge.io.JsonMetaFormat import space.kscience.dataforge.io.yaml.YamlMetaFormat import space.kscience.dataforge.io.yaml.YamlPlugin import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.set import space.kscience.dataforge.meta.string import space.kscience.dataforge.misc.DFExperimental @@ -28,6 +29,7 @@ import space.kscience.dataforge.workspace.* import space.kscience.snark.ReWrapAction import space.kscience.snark.Snark import space.kscience.snark.SnarkReader +import space.kscience.snark.TextProcessor import java.net.URLConnection import kotlin.io.path.Path import kotlin.io.path.extension @@ -62,12 +64,31 @@ public class SnarkHtml : WorkspacePlugin() { URLConnection.guessContentTypeFromName(filePath) ?: Path(filePath).extension } - public val prepareHeaderAction: ReWrapAction = ReWrapAction.removeExtensions("html", "md") { name -> val contentType = getContentType(name, this) 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().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 = ReWrapAction.removeIndex() public val parseAction: Action = ParseAction(this) diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/DocumentBuilder.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/DocumentBuilder.kt index a365438..5b700fb 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/DocumentBuilder.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/DocumentBuilder.kt @@ -4,7 +4,9 @@ import kotlinx.coroutines.runBlocking import kotlinx.html.body import kotlinx.html.head import kotlinx.html.title +import space.kscience.dataforge.context.request import space.kscience.dataforge.data.* +import space.kscience.dataforge.meta.Laminate import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.string @@ -24,9 +26,9 @@ public interface DocumentBuilder : SnarkContext { 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) } @@ -44,19 +46,50 @@ public suspend fun DocumentBuilder.fragment(fragmentName: String) { private class PageBasedDocumentBuilder(val page: PageContextWithData) : DocumentBuilder { override val documentName: Name get() = page.pageRoute override val documentMeta: Meta get() = page.pageMeta - override val data: DataTree<*> get() = page.data + override val documentData: DataTree<*> get() = page.data val fragments = mutableListOf() - override suspend fun fragment(fragment: DocumentFragment, overrideMeta: Meta?) { - TODO("Not yet implemented") + fun fragment(pageFragment: PageFragment) { + 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) { - typeOf() -> fragments.add(fragment.await() as PageFragment) - typeOf() -> fragment(fragment.await() as DocumentFragment, data.meta) - typeOf() -> fragment(TextDocumentFragment(fragment.await() as String, fragment.meta)) + typeOf() -> fragment(fragment.await() as PageFragment) + typeOf() -> fragment( + fragment.await() as DocumentFragment, + Laminate(overrideMeta, documentData.meta) + ) + + typeOf() -> fragment( + MarkupDocumentFragment(fragment.await() as String, fragment.meta), + overrideMeta + ) + else -> error("Unsupported data type: ${fragment.type}") } } @@ -72,7 +105,7 @@ public fun SiteContextWithData.document( title(documentMeta["title"].string ?: "Snark document") } body { - postprocess(DocumentTextProcessor(documentBuilder)) { + postprocess(FtlDocumentProcessor(this@document.context, documentBuilder)) { documentBuilder.fragments.forEach { fragment(it) } diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/DocumentFragment.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/DocumentFragment.kt index 4fb2f0d..c4b39e6 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/DocumentFragment.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/DocumentFragment.kt @@ -2,13 +2,18 @@ package space.kscience.snark.html.document import kotlinx.io.files.Path import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.names.Name public sealed interface DocumentFragment{ 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 CompositeDocumentFragment(public val fragments: List, override val meta: Meta) : DocumentFragment +public class DataDocumentFragment(public val dataName: Name, override val meta: Meta) : DocumentFragment + +public class ListDocumentFragment(public val fragments: List, override val meta: Meta) : DocumentFragment + +public class LayoutDocumentFragment(public val fragments: Map, override val meta: Meta) : DocumentFragment diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/FtlDocumentProcessor.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/FtlDocumentProcessor.kt new file mode 100644 index 0000000..12a5b9c --- /dev/null +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/FtlDocumentProcessor.kt @@ -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 = 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 -> + val counter = args.getOrNull(0)?.toString() ?: "@default" + val value = getAndIncrementCounter(counter) + val ref = ref(counter, value) + //language=HTML + """$value""" + }, + + "section" to TemplateMethodModelEx { args: List -> + val level: Int = args.getOrNull(0)?.toString()?.toIntOrNull() ?: 1 + val counter = getAndIncrementCounter("section[$level]") + val ref = ref("section[$level]", counter) + //language=HTML + """$counter""" + }, + + "documentMeta" to document.documentMeta.toMap().let { + it.plus( + "get" to TemplateMethodModelEx { args: List -> + 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() + } +} \ No newline at end of file diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/DocumentTextProcessor.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/RegexDocumentProcessor.kt similarity index 95% rename from snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/DocumentTextProcessor.kt rename to snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/RegexDocumentProcessor.kt index 796df77..944a952 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/DocumentTextProcessor.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/document/RegexDocumentProcessor.kt @@ -6,7 +6,7 @@ import space.kscience.dataforge.names.* import space.kscience.snark.TextProcessor import java.net.URI -public class DocumentTextProcessor(public val document: DocumentBuilder) : TextProcessor { +public class RegexDocumentProcessor(public val document: DocumentBuilder) : TextProcessor { private val counters = mutableMapOf() @@ -67,7 +67,7 @@ public class DocumentTextProcessor(public val document: DocumentBuilder) : TextP } "documentMeta.get" -> { - val nameString = match.groups["name"]?.value + val nameString = match.groups["arg1"]?.value ?: error("resolvePageRef requires a string (quoted) argument") document.documentMeta[nameString.parseAsName()].string ?: "@null" } diff --git a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/static/StaticSiteContext.kt b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/static/StaticSiteContext.kt index dd40488..6dd3e5f 100644 --- a/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/static/StaticSiteContext.kt +++ b/snark-html/src/jvmMain/kotlin/space/kscience/snark/html/static/StaticSiteContext.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.io.asSink import kotlinx.io.buffered +import space.kscience.dataforge.context.Context import space.kscience.dataforge.data.* import space.kscience.dataforge.io.Binary 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] */ internal class StaticSiteContext( + override val context: Context, override val siteMeta: Meta, private val baseUrl: Url, override val path: List, @@ -127,6 +129,7 @@ internal class StaticSiteContext( override fun route(route: Name, data: DataTree<*>?, siteMeta: Meta, content: HtmlSite) { val siteContextWithData = SiteContextWithData( StaticSiteContext( + context = context, siteMeta = Laminate(siteMeta, this@StaticSiteContext.siteMeta), baseUrl = baseUrl, path = emptyList(), @@ -146,6 +149,7 @@ internal class StaticSiteContext( override fun site(route: Name, data: DataTree<*>?, siteMeta: Meta, content: HtmlSite) { val siteContextWithData = SiteContextWithData( StaticSiteContext( + context = context, siteMeta = Laminate(siteMeta, this@StaticSiteContext.siteMeta), baseUrl = baseUrl, 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. * */ -@Suppress("UnusedReceiverParameter") public suspend fun SnarkHtml.staticSite( data: DataTree<*>?, outputPath: Path, @@ -178,7 +181,7 @@ public suspend fun SnarkHtml.staticSite( content: HtmlSite, ) { val siteContextWithData = SiteContextWithData( - StaticSiteContext(siteMeta, siteUrl, emptyList(), Name.EMPTY, null, outputPath), + StaticSiteContext(context, siteMeta, siteUrl, emptyList(), Name.EMPTY, null, outputPath), data ?: DataTree.EMPTY ) with(content) {