Templates/Parts now have access to an EmbeddableText object.

This commit is contained in:
Jesse Brault 2023-02-11 11:24:14 +01:00
parent a1f1871d1a
commit 34d9cd52b0
12 changed files with 197 additions and 53 deletions

View File

@ -7,6 +7,9 @@ repositories {
}
dependencies {
// https://mvnrepository.com/artifact/org.jetbrains/annotations
api 'org.jetbrains:annotations:24.0.0'
/**
* Logging
*/

View File

@ -69,21 +69,14 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
}
logger.debug('found template: {}', template)
// Render the text (i.e., transform text to html)
def textRenderResult = it.type.renderer.render(it, globals)
String renderedText
if (textRenderResult.v1.size() > 0) {
logger.debug('diagnostics for rendering {}: {}', it.path, textRenderResult.v1)
diagnostics.addAll(textRenderResult.v1)
logger.trace(exit, '')
return
} else {
renderedText = textRenderResult.v2
logger.debug('renderedText: {}', renderedText)
}
// Render the template using the result of rendering the text earlier
def templateRenderResult = template.type.renderer.render(template, frontMatter, renderedText, parts, globals)
def templateRenderResult = template.type.renderer.render(
template,
frontMatter,
it,
parts,
globals
)
String renderedTemplate
if (templateRenderResult.v1.size() > 0) {
diagnostics.addAll(templateRenderResult.v1)

View File

@ -1,8 +1,10 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.text.EmbeddableText
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
import org.jetbrains.annotations.Nullable
@TupleConstructor(includeFields = true, defaults = false)
@NullCheck
@ -13,8 +15,16 @@ class EmbeddablePart {
private final Map globals
private final Closure onDiagnostics
@Nullable
private final EmbeddableText text
String render(Map binding = [:]) {
def result = part.type.renderer.render(this.part, binding, this.globals)
def result = part.type.renderer.render(
this.part,
binding,
this.globals,
this.text
)
if (result.v1.size() > 0) {
this.onDiagnostics.call(result.v1)
''

View File

@ -1,18 +1,26 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.text.EmbeddableText
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import org.jetbrains.annotations.Nullable
@NullCheck
@EqualsAndHashCode(includeFields = true)
class EmbeddablePartsMap {
@Delegate
private final Map<String, EmbeddablePart> partsMap = [:]
EmbeddablePartsMap(Collection<Part> parts, Map globals, Closure onDiagnostics) {
EmbeddablePartsMap(
Collection<Part> parts,
Map globals,
Closure onDiagnostics,
@Nullable EmbeddableText text = null
) {
Objects.requireNonNull(parts)
Objects.requireNonNull(globals)
Objects.requireNonNull(onDiagnostics)
parts.each {
this.put(it.path, new EmbeddablePart(it, globals, onDiagnostics))
this.put(it.path, new EmbeddablePart(it, globals, onDiagnostics, text))
}
}

View File

@ -1,9 +1,11 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.text.EmbeddableText
import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode
import org.jetbrains.annotations.Nullable
@EqualsAndHashCode
class GspPartRenderer implements PartRenderer {
@ -11,15 +13,27 @@ class GspPartRenderer implements PartRenderer {
private static final TemplateEngine engine = new GStringTemplateEngine()
@Override
Tuple2<Collection<Diagnostic>, String> render(Part part, Map binding, Map globals) {
Tuple2<Collection<Diagnostic>, String> render(
Part part,
Map binding,
Map globals,
@Nullable EmbeddableText text = null
) {
Objects.requireNonNull(part)
Objects.requireNonNull(binding)
Objects.requireNonNull(globals)
try {
def result = engine.createTemplate(part.text).make([
binding: binding,
globals: globals
globals: globals,
text: text
])
new Tuple2<>([], result.toString())
} catch (Exception e) {
new Tuple2<>([new Diagnostic("An exception occurred while rendering part ${ part.path }:\n${ e }", e)], '')
new Tuple2<>(
[new Diagnostic("An exception occurred while rendering part ${ part.path }:\n${ e }", e)],
''
)
}
}

View File

@ -1,7 +1,16 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.text.EmbeddableText
import org.jetbrains.annotations.Nullable
interface PartRenderer {
Tuple2<Collection<Diagnostic>, String> render(Part part, Map binding, Map globals)
Tuple2<Collection<Diagnostic>, String> render(
Part part,
Map binding,
Map globals,
@Nullable EmbeddableText text
)
}

View File

@ -3,7 +3,9 @@ package com.jessebrault.ssg.template
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.part.EmbeddablePartsMap
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.text.EmbeddableText
import com.jessebrault.ssg.text.FrontMatter
import com.jessebrault.ssg.text.Text
import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode
@ -19,19 +21,21 @@ class GspTemplateRenderer implements TemplateRenderer {
Tuple2<Collection<Diagnostic>, String> render(
Template template,
FrontMatter frontMatter,
String text,
Text text,
Collection<Part> parts,
Map globals
) {
try {
Collection<Diagnostic> diagnostics = []
def onDiagnostics = { Collection<Diagnostic> partDiagnostics ->
diagnostics.addAll(partDiagnostics)
}
def embeddableText = new EmbeddableText(text, globals, onDiagnostics)
def result = engine.createTemplate(template.text).make([
frontMatter: frontMatter,
globals: globals,
parts: new EmbeddablePartsMap(parts, globals, { Collection<Diagnostic> partDiagnostics ->
diagnostics.addAll(partDiagnostics)
}),
text: text
parts: new EmbeddablePartsMap(parts, globals, onDiagnostics, embeddableText),
text: embeddableText
])
new Tuple2<>(diagnostics, result.toString())
} catch (Exception e) {

View File

@ -3,13 +3,17 @@ package com.jessebrault.ssg.template
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.text.FrontMatter
import com.jessebrault.ssg.text.Text
interface TemplateRenderer {
/**
* TODO: get rid of frontMatter param since we can obtain it from the text
*/
Tuple2<Collection<Diagnostic>, String> render(
Template template,
FrontMatter frontMatter,
String text,
Text text,
Collection<Part> parts,
Map globals
)

View File

@ -61,7 +61,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
@Test
void simple() {
new File(this.textsDir, 'test.md').write('---\ntemplate: test.gsp\n---\n**Hello, World!**')
new File(this.templatesDir, 'test.gsp').write('<%= text %>')
new File(this.templatesDir, 'test.gsp').write('<%= text.render() %>')
def result = this.ssg.generate(this.build)
@ -81,7 +81,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
}
}
new File(this.templatesDir, 'nested.gsp').write('<%= text %>')
new File(this.templatesDir, 'nested.gsp').write('<%= text.render() %>')
def result = this.ssg.generate(this.build)

View File

@ -1,18 +1,34 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.text.*
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertTrue
import static org.mockito.ArgumentMatchers.any
import static org.mockito.Mockito.mock
import static org.mockito.Mockito.when
class GspPartRendererTests {
/**
* TODO: move to a fixture
*/
private static Text mockRenderableText(String text) {
def textRenderer = mock(TextRenderer)
when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], text))
def frontMatterGetter = mock(FrontMatterGetter)
def excerptGetter = mock(ExcerptGetter)
new Text('', '', new TextType([], textRenderer, frontMatterGetter, excerptGetter))
}
private final PartRenderer renderer = new GspPartRenderer()
@Test
void rendersWithNoBindingOrGlobals() {
def part = new Part('', null, 'Hello, World!')
def r = this.renderer.render(part, [:], [:])
def r = this.renderer.render(part, [:], [:], null)
assertTrue(r.v1.size() == 0)
assertEquals('Hello, World!', r.v2)
}
@ -20,7 +36,7 @@ class GspPartRendererTests {
@Test
void rendersWithBinding() {
def part = new Part('', null, "<%= binding['greeting'] %>")
def r = this.renderer.render(part, [greeting: 'Hello, World!'], [:])
def r = this.renderer.render(part, [greeting: 'Hello, World!'], [:], null)
assertTrue(r.v1.size() == 0)
assertEquals('Hello, World!', r.v2)
}
@ -28,9 +44,28 @@ class GspPartRendererTests {
@Test
void rendersWithGlobals() {
def part = new Part(null, null, "<%= globals['greeting'] %>")
def r = this.renderer.render(part, [:], [greeting: 'Hello, World!'])
def r = this.renderer.render(part, [:], [greeting: 'Hello, World!'], null)
assertTrue(r.v1.size() == 0)
assertEquals('Hello, World!', r.v2)
}
@Test
void textAvailable() {
def part = new Part('', null, '<%= text.render() %>')
def textDiagnostics = []
def r = this.renderer.render(
part,
[:],
[:],
new EmbeddableText(
mockRenderableText('Hello, World!'),
[:],
{ Collection<Diagnostic> diagnostics -> textDiagnostics.addAll(diagnostics) }
)
)
assertTrue(textDiagnostics.isEmpty())
assertTrue(r.v1.isEmpty())
assertEquals('Hello, World!', r.v2)
}
}

View File

@ -3,11 +3,7 @@ package com.jessebrault.ssg.specialpage
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.part.PartRenderer
import com.jessebrault.ssg.part.PartType
import com.jessebrault.ssg.text.FrontMatterGetter
import com.jessebrault.ssg.text.MarkdownExcerptGetter
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.text.TextRenderer
import com.jessebrault.ssg.text.TextType
import com.jessebrault.ssg.text.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
@ -35,7 +31,7 @@ class GspSpecialPageRendererTests {
@Test
void rendersPartWithNoBinding(@Mock PartRenderer partRenderer) {
when(partRenderer.render(any(), any(), any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
when(partRenderer.render(any(), any(), any(), any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
def partType = new PartType([], partRenderer)
def part = new Part('test', partType , '')
@ -47,11 +43,16 @@ class GspSpecialPageRendererTests {
@Test
void rendersPartWithBinding(@Mock PartRenderer partRenderer) {
when(partRenderer.render(any(), argThat { Map m -> m.get('greeting') == 'Hello, World!'}, any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
when(partRenderer.render(any(), argThat { Map m -> m.get('greeting') == 'Hello, World!'}, any(), any()))
.thenReturn(new Tuple2<>([], 'Hello, World!'))
def partType = new PartType([], partRenderer)
def part = new Part('test', partType, '')
def specialPage = new SpecialPage("<%= parts['test'].render([greeting: 'Hello, World!'])", null, null)
def specialPage = new SpecialPage(
"<%= parts['test'].render([greeting: 'Hello, World!'])",
null,
null
)
def r = this.renderer.render(specialPage, [], [part], [:])
assertTrue(r.v1.size() == 0)
assertEquals('Hello, World!', r.v2)
@ -59,11 +60,16 @@ class GspSpecialPageRendererTests {
@Test
void rendersText(@Mock TextRenderer textRenderer, @Mock FrontMatterGetter frontMatterGetter) {
when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], '<p><strong>Hello, World!</strong></p>\n'))
when(textRenderer.render(any(), any()))
.thenReturn(new Tuple2<>([], '<p><strong>Hello, World!</strong></p>\n'))
def textType = new TextType([], textRenderer, frontMatterGetter, new MarkdownExcerptGetter())
def text = new Text('', 'test', textType)
def specialPage = new SpecialPage("<%= texts.find { it.path == 'test' }.render() %>", null, null)
def specialPage = new SpecialPage(
"<%= texts.find { it.path == 'test' }.render() %>",
null,
null
)
def r = this.renderer.render(specialPage, [text], [], [:])
assertTrue(r.v1.size() == 0)
assertEquals('<p><strong>Hello, World!</strong></p>\n', r.v2)

View File

@ -3,7 +3,12 @@ package com.jessebrault.ssg.template
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.part.PartRenderer
import com.jessebrault.ssg.part.PartType
import com.jessebrault.ssg.text.ExcerptGetter
import com.jessebrault.ssg.text.FrontMatter
import com.jessebrault.ssg.text.FrontMatterGetter
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.text.TextRenderer
import com.jessebrault.ssg.text.TextType
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
@ -13,11 +18,33 @@ import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertTrue
import static org.mockito.ArgumentMatchers.any
import static org.mockito.ArgumentMatchers.argThat
import static org.mockito.Mockito.mock
import static org.mockito.Mockito.when
@ExtendWith(MockitoExtension)
class GspTemplateRendererTests {
/**
* TODO: move to a fixture
*/
private static Text mockBlankText() {
def textRenderer = mock(TextRenderer)
def frontMatterGetter = mock(FrontMatterGetter)
def excerptGetter = mock(ExcerptGetter)
new Text('', '', new TextType([], textRenderer, frontMatterGetter, excerptGetter))
}
/**
* TODO: move to a fixture
*/
private static Text mockRenderableText(String text) {
def textRenderer = mock(TextRenderer)
when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], text))
def frontMatterGetter = mock(FrontMatterGetter)
def excerptGetter = mock(ExcerptGetter)
new Text('', '', new TextType([], textRenderer, frontMatterGetter, excerptGetter))
}
private final TemplateRenderer renderer = new GspTemplateRenderer()
@Test
@ -28,11 +55,17 @@ class GspTemplateRendererTests {
null
)
when(partRenderer.render(any(), any(), any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
when(partRenderer.render(any(), any(), any(), any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
def partType = new PartType([], partRenderer)
def part = new Part('test', partType, null)
def r = this.renderer.render(template, new FrontMatter(null, [:]), '', [part], [:])
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
mockBlankText(),
[part],
[:]
)
assertTrue(r.v1.size() == 0)
assertEquals('Hello, World!', r.v2)
}
@ -45,11 +78,18 @@ class GspTemplateRendererTests {
null
)
when(partRenderer.render(any(), argThat { Map m -> m.get('person') == 'World' }, any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
when(partRenderer.render(any(), argThat { Map m -> m.get('person') == 'World' }, any(), any()))
.thenReturn(new Tuple2<>([], 'Hello, World!'))
def partType = new PartType([], partRenderer)
def part = new Part('greeting', partType, null)
def r = this.renderer.render(template, new FrontMatter(null, [:]), '', [part], [:])
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
mockBlankText(),
[part],
[:]
)
assertTrue(r.v1.size() == 0)
assertEquals('Hello, World!', r.v2)
}
@ -57,7 +97,13 @@ class GspTemplateRendererTests {
@Test
void rendersFrontMatter() {
def template = new Template("<%= frontMatter['title'] %>", null, null)
def r = this.renderer.render(template, new FrontMatter(null, [title: ['Hello!']]), '', [], [:])
def r = this.renderer.render(
template,
new FrontMatter(null, [title: ['Hello!']]),
mockBlankText(),
[],
[:]
)
assertTrue(r.v1.size() == 0)
assertEquals('Hello!', r.v2)
}
@ -65,15 +111,27 @@ class GspTemplateRendererTests {
@Test
void rendersGlobal() {
def template = new Template("<%= globals['test'] %>", null, null)
def r = this.renderer.render(template, new FrontMatter(null, [:]), '', [], [test: 'Hello, World!'])
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
mockBlankText(),
[],
[test: 'Hello, World!']
)
assertTrue(r.v1.size() == 0)
assertEquals('Hello, World!', r.v2)
}
@Test
void rendersText() {
def template = new Template('<%= text %>', null, null)
def r = this.renderer.render(template, new FrontMatter(null, [:]), 'Hello, World!', [], [:])
void textAvailableToRender() {
def template = new Template('<%= text.render() %>', null, null)
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
mockRenderableText('Hello, World!'),
[],
[:]
)
assertTrue(r.v1.size() == 0)
assertEquals('Hello, World!', r.v2)
}